fix: sync MarketApp MCP records to admin MCP Client management

This commit is contained in:
QihuAI Design committed 2026-07-16 18:12:07 +08:00
1 parent c9b5f964be
commit feb9865b00
3 files changed
+1333 -1223

No files matched your search

+511 -449
View File
@@ -1,449 +1,511 @@
""" """
MCP (Model Context Protocol) Server Management API MCP (Model Context Protocol) Server Management API
Phase 3.3: Manage MCP servers, discover tools/resources, test connections Phase 3.3: Manage MCP servers, discover tools/resources, test connections
""" """
from fastapi import APIRouter, Depends, HTTPException, Query, Body from fastapi import APIRouter, Depends, HTTPException, Query, Body
from sqlalchemy import or_, func, desc, asc, text from sqlalchemy import or_, func, desc, asc, text
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any from typing import Optional, List, Dict, Any
from datetime import datetime from datetime import datetime
import uuid import uuid
import json import json
import logging import logging
import re
from ...database import get_db
from ...models import gen_uuid from ...database import get_db
from ...auth import get_current_admin from ...models import gen_uuid
from ...middleware.log_service import LogService from ...auth import get_current_admin
from ...middleware.log_service import LogService
router = APIRouter()
logger = logging.getLogger("aiflow") router = APIRouter()
logger = logging.getLogger("aiflow")
# ============================================================
# Pydantic Models # ============================================================
# ============================================================ # Pydantic Models
# ============================================================
class MCPServerCreate(BaseModel):
name: str class MCPServerCreate(BaseModel):
description: Optional[str] = "" name: str
transport_type: str = "stdio" description: Optional[str] = ""
command: Optional[str] = "" transport_type: str = "stdio"
args: Optional[Any] = None command: Optional[str] = ""
env_vars: Optional[Dict[str, str]] = None args: Optional[Any] = None
url: Optional[str] = "" env_vars: Optional[Dict[str, str]] = None
headers: Optional[Dict[str, str]] = None url: Optional[str] = ""
is_enabled: Optional[bool] = True headers: Optional[Dict[str, str]] = None
is_enabled: Optional[bool] = True
class MCPServerUpdate(BaseModel):
name: Optional[str] = None class MCPServerUpdate(BaseModel):
description: Optional[str] = None name: Optional[str] = None
transport_type: Optional[str] = None description: Optional[str] = None
command: Optional[str] = None transport_type: Optional[str] = None
args: Optional[Any] = None command: Optional[str] = None
env_vars: Optional[Dict[str, str]] = None args: Optional[Any] = None
url: Optional[str] = None env_vars: Optional[Dict[str, str]] = None
headers: Optional[Dict[str, str]] = None url: Optional[str] = None
is_enabled: Optional[bool] = None headers: Optional[Dict[str, str]] = None
is_enabled: Optional[bool] = None
class MCPServerConnectionTest(BaseModel):
transport_type: Optional[str] = None class MCPServerConnectionTest(BaseModel):
command: Optional[str] = None transport_type: Optional[str] = None
args: Optional[Any] = None command: Optional[str] = None
env_vars: Optional[Dict[str, str]] = None args: Optional[Any] = None
url: Optional[str] = None env_vars: Optional[Dict[str, str]] = None
headers: Optional[Dict[str, str]] = None url: Optional[str] = None
headers: Optional[Dict[str, str]] = None
# ============================================================
# Helper Functions # ============================================================
# ============================================================ # Helper Functions
# ============================================================
def _format_cst_time(dt):
"""Format datetime to CST string""" def _format_cst_time(dt):
if dt is None: """Format datetime to CST string"""
return None if dt is None:
try: return None
from datetime import timezone, timedelta try:
CST = timezone(timedelta(hours=8)) from datetime import timezone, timedelta
if dt.tzinfo is None: CST = timezone(timedelta(hours=8))
dt = dt.replace(tzinfo=CST) if dt.tzinfo is None:
return dt.astimezone(CST).strftime("%Y-%m-%d %H:%M:%S") dt = dt.replace(tzinfo=CST)
except Exception: return dt.astimezone(CST).strftime("%Y-%m-%d %H:%M:%S")
return dt.strftime("%Y-%m-%d %H:%M:%S") if dt else None except Exception:
return dt.strftime("%Y-%m-%d %H:%M:%S") if dt else None
def server_to_dict(server) -> dict:
"""Convert MCPServer model to dict""" def server_to_dict(server) -> dict:
return { """Convert MCPServer model to dict"""
"id": server.id, return {
"name": server.name, "id": server.id,
"description": server.description or "", "name": server.name,
"transport_type": server.transport_type or "stdio", "description": server.description or "",
"transport_type_label": {"stdio": "STDIO", "sse": "SSE"}.get(server.transport_type, server.transport_type), "transport_type": server.transport_type or "stdio",
"command": server.command or "", "transport_type_label": {"stdio": "STDIO", "sse": "SSE"}.get(server.transport_type, server.transport_type),
"args": json.loads(server.args) if server.args else {}, "command": server.command or "",
"env_vars": json.loads(server.env_vars) if server.env_vars else {}, "args": json.loads(server.args) if server.args else {},
"url": server.url or "", "env_vars": json.loads(server.env_vars) if server.env_vars else {},
"headers": json.loads(server.headers) if server.headers else {}, "url": server.url or "",
"is_enabled": server.is_enabled if server.is_enabled is not None else True, "headers": json.loads(server.headers) if server.headers else {},
"status": server.status or "disconnected", "is_enabled": server.is_enabled if server.is_enabled is not None else True,
"last_connected_at": _format_cst_time(server.last_connected_at), "status": server.status or "disconnected",
"tools_count": server.tools_count or 0, "last_connected_at": _format_cst_time(server.last_connected_at),
"resources_count": server.resources_count or 0, "tools_count": server.tools_count or 0,
"created_at": _format_cst_time(server.created_at), "resources_count": server.resources_count or 0,
"updated_at": _format_cst_time(server.updated_at), "created_at": _format_cst_time(server.created_at),
} "updated_at": _format_cst_time(server.updated_at),
}
# ============================================================
# API Endpoints def _extract_url_from_text(text: str) -> Optional[str]:
# ============================================================ """Extract first http/https URL from text."""
if not text:
@router.get("/stats/overview", summary="Get MCP server statistics") return None
async def get_mcp_stats( match = re.search(r"https?://[^\s\"'<>\)\]\}]+", text)
db: Session = Depends(get_db), return match.group(0) if match else None
admin_user=Depends(get_current_admin),
):
"""Get overview statistics for MCP servers""" def _sync_market_mcp_to_servers(db: Session):
from ...models import MCPServer """
total = db.query(func.count(MCPServer.id)).scalar() or 0 Synchronize MarketApp MCP records (type='mcp') into mcp_servers table.
enabled = db.query(func.count(MCPServer.id)).filter(MCPServer.is_enabled == True).scalar() or 0 Uses MarketApp.id as MCPServer.id so detail/edit endpoints can work directly.
connected = db.query(func.count(MCPServer.id)).filter(MCPServer.status == "connected").scalar() or 0 """
from ...models import MarketApp, MCPServer
# By transport type
by_transport = {} market_mcps = db.query(MarketApp).filter(
rows = db.query(MCPServer.transport_type, func.count(MCPServer.id)).group_by(MCPServer.transport_type).all() MarketApp.type == "mcp",
for t, c in rows: MarketApp.status == "published",
by_transport[t or "unknown"] = c ).all()
# By status existing_ids = {row[0] for row in db.query(MCPServer.id).all()}
by_status = {}
rows = db.query(MCPServer.status, func.count(MCPServer.id)).group_by(MCPServer.status).all() for ma in market_mcps:
for s, c in rows: if ma.id in existing_ids:
by_status[s or "unknown"] = c continue
return { transport = ma.mcp_transport or "streamable_http"
"code": 0, code = ma.skill_content or ""
"data": { url = _extract_url_from_text(code) or _extract_url_from_text(ma.description or "") or ""
"total_servers": total, command = ""
"enabled_servers": enabled, if not url and ma.agent_endpoint:
"connected_servers": connected, url = ma.agent_endpoint
"by_transport": by_transport,
"by_status": by_status, now = datetime.now()
}, server = MCPServer(
"message": "ok", id=ma.id,
} name=ma.name,
description=ma.description or "",
transport_type=transport,
@router.get("/", summary="List MCP servers") command=command,
async def list_mcp_servers( args="{}",
page: int = Query(1, ge=1), env_vars="{}",
page_size: int = Query(20, ge=1, le=100), url=url,
search: Optional[str] = Query(None), headers="{}",
transport_type: Optional[str] = Query(None), is_enabled=True,
status: Optional[str] = Query(None), status="disconnected",
is_enabled: Optional[bool] = Query(None), tools_count=0,
db: Session = Depends(get_db), resources_count=0,
admin_user=Depends(get_current_admin), created_at=ma.created_at or now,
): updated_at=ma.updated_at or now,
"""List all MCP servers with pagination and filters""" )
from ...models import MCPServer db.add(server)
query = db.query(MCPServer)
if market_mcps:
if search: db.commit()
query = query.filter(
or_(MCPServer.name.contains(search), MCPServer.description.contains(search))
) # ============================================================
if transport_type: # API Endpoints
query = query.filter(MCPServer.transport_type == transport_type) # ============================================================
if status:
query = query.filter(MCPServer.status == status) @router.get("/stats/overview", summary="Get MCP server statistics")
if is_enabled is not None: async def get_mcp_stats(
query = query.filter(MCPServer.is_enabled == is_enabled) db: Session = Depends(get_db),
admin_user=Depends(get_current_admin),
total = query.count() ):
servers = query.order_by(desc(MCPServer.created_at)).offset((page - 1) * page_size).limit(page_size).all() """Get overview statistics for MCP servers"""
from ...models import MCPServer
return { _sync_market_mcp_to_servers(db)
"code": 0, total = db.query(func.count(MCPServer.id)).scalar() or 0
"data": { enabled = db.query(func.count(MCPServer.id)).filter(MCPServer.is_enabled == True).scalar() or 0
"total": total, connected = db.query(func.count(MCPServer.id)).filter(MCPServer.status == "connected").scalar() or 0
"page": page,
"page_size": page_size, # By transport type
"items": [server_to_dict(s) for s in servers], by_transport = {}
}, rows = db.query(MCPServer.transport_type, func.count(MCPServer.id)).group_by(MCPServer.transport_type).all()
"message": "ok", for t, c in rows:
} by_transport[t or "unknown"] = c
# By status
@router.get("/{server_id}", summary="Get MCP server details") by_status = {}
async def get_mcp_server( rows = db.query(MCPServer.status, func.count(MCPServer.id)).group_by(MCPServer.status).all()
server_id: str, for s, c in rows:
db: Session = Depends(get_db), by_status[s or "unknown"] = c
admin_user=Depends(get_current_admin),
): return {
"""Get a single MCP server by ID""" "code": 0,
from ...models import MCPServer "data": {
server = db.query(MCPServer).filter(MCPServer.id == server_id).first() "total_servers": total,
if not server: "enabled_servers": enabled,
raise HTTPException(status_code=404, detail="MCP server not found") "connected_servers": connected,
return {"code": 0, "data": server_to_dict(server), "message": "ok"} "by_transport": by_transport,
"by_status": by_status,
},
@router.post("/", summary="Create MCP server") "message": "ok",
async def create_mcp_server( }
data: MCPServerCreate,
db: Session = Depends(get_db),
admin_user=Depends(get_current_admin), @router.get("/", summary="List MCP servers")
): async def list_mcp_servers(
"""Register a new MCP server""" page: int = Query(1, ge=1),
from ...models import MCPServer page_size: int = Query(20, ge=1, le=100),
search: Optional[str] = Query(None),
# Check name uniqueness transport_type: Optional[str] = Query(None),
existing = db.query(MCPServer).filter(MCPServer.name == data.name).first() status: Optional[str] = Query(None),
if existing: is_enabled: Optional[bool] = Query(None),
raise HTTPException(status_code=400, detail="MCP server with this name already exists") db: Session = Depends(get_db),
admin_user=Depends(get_current_admin),
now = datetime.now() ):
server = MCPServer( """List all MCP servers with pagination and filters"""
id=gen_uuid(), from ...models import MCPServer
name=data.name, _sync_market_mcp_to_servers(db)
description=data.description or "", query = db.query(MCPServer)
transport_type=data.transport_type,
command=data.command or "", if search:
args=json.dumps(data.args) if data.args else "{}", query = query.filter(
env_vars=json.dumps(data.env_vars) if data.env_vars else "{}", or_(MCPServer.name.contains(search), MCPServer.description.contains(search))
url=data.url or "", )
headers=json.dumps(data.headers) if data.headers else "{}", if transport_type:
is_enabled=data.is_enabled if data.is_enabled is not None else True, query = query.filter(MCPServer.transport_type == transport_type)
status="disconnected", if status:
created_at=now, query = query.filter(MCPServer.status == status)
updated_at=now, if is_enabled is not None:
) query = query.filter(MCPServer.is_enabled == is_enabled)
db.add(server)
db.commit() total = query.count()
db.refresh(server) servers = query.order_by(desc(MCPServer.created_at)).offset((page - 1) * page_size).limit(page_size).all()
logger.info(f"MCP server created: {server.name} ({server.id})") return {
return {"code": 0, "data": server_to_dict(server), "message": "MCP server created"} "code": 0,
"data": {
"total": total,
@router.put("/{server_id}", summary="Update MCP server") "page": page,
async def update_mcp_server( "page_size": page_size,
server_id: str, "items": [server_to_dict(s) for s in servers],
data: MCPServerUpdate, },
db: Session = Depends(get_db), "message": "ok",
admin_user=Depends(get_current_admin), }
):
"""Update an existing MCP server"""
from ...models import MCPServer @router.get("/{server_id}", summary="Get MCP server details")
server = db.query(MCPServer).filter(MCPServer.id == server_id).first() async def get_mcp_server(
if not server: server_id: str,
raise HTTPException(status_code=404, detail="MCP server not found") db: Session = Depends(get_db),
admin_user=Depends(get_current_admin),
update_fields = data.model_dump(exclude_unset=True) ):
"""Get a single MCP server by ID"""
# Handle JSON fields from ...models import MCPServer
for json_field in ["args", "env_vars", "headers"]: _sync_market_mcp_to_servers(db)
if json_field in update_fields and update_fields[json_field] is not None: server = db.query(MCPServer).filter(MCPServer.id == server_id).first()
update_fields[json_field] = json.dumps(update_fields[json_field]) if not server:
raise HTTPException(status_code=404, detail="MCP server not found")
for key, value in update_fields.items(): return {"code": 0, "data": server_to_dict(server), "message": "ok"}
if value is not None:
setattr(server, key, value)
@router.post("/", summary="Create MCP server")
server.updated_at = datetime.now() async def create_mcp_server(
db.commit() data: MCPServerCreate,
db.refresh(server) db: Session = Depends(get_db),
admin_user=Depends(get_current_admin),
logger.info(f"MCP server updated: {server.name} ({server.id})") ):
return {"code": 0, "data": server_to_dict(server), "message": "MCP server updated"} """Register a new MCP server"""
from ...models import MCPServer
@router.delete("/{server_id}", summary="Delete MCP server") # Check name uniqueness
async def delete_mcp_server( existing = db.query(MCPServer).filter(MCPServer.name == data.name).first()
server_id: str, if existing:
db: Session = Depends(get_db), raise HTTPException(status_code=400, detail="MCP server with this name already exists")
admin_user=Depends(get_current_admin),
): now = datetime.now()
"""Delete an MCP server""" server = MCPServer(
from ...models import MCPServer id=gen_uuid(),
server = db.query(MCPServer).filter(MCPServer.id == server_id).first() name=data.name,
if not server: description=data.description or "",
raise HTTPException(status_code=404, detail="MCP server not found") transport_type=data.transport_type,
command=data.command or "",
name = server.name args=json.dumps(data.args) if data.args else "{}",
db.delete(server) env_vars=json.dumps(data.env_vars) if data.env_vars else "{}",
db.commit() url=data.url or "",
headers=json.dumps(data.headers) if data.headers else "{}",
logger.info(f"MCP server deleted: {name} ({server_id})") is_enabled=data.is_enabled if data.is_enabled is not None else True,
return {"code": 0, "data": None, "message": "MCP server deleted"} status="disconnected",
created_at=now,
updated_at=now,
@router.put("/{server_id}/toggle", summary="Enable/Disable MCP server") )
async def toggle_mcp_server( db.add(server)
server_id: str, db.commit()
db: Session = Depends(get_db), db.refresh(server)
admin_user=Depends(get_current_admin),
): logger.info(f"MCP server created: {server.name} ({server.id})")
"""Toggle MCP server enabled state""" return {"code": 0, "data": server_to_dict(server), "message": "MCP server created"}
from ...models import MCPServer
server = db.query(MCPServer).filter(MCPServer.id == server_id).first()
if not server: @router.put("/{server_id}", summary="Update MCP server")
raise HTTPException(status_code=404, detail="MCP server not found") async def update_mcp_server(
server_id: str,
server.is_enabled = not server.is_enabled data: MCPServerUpdate,
server.updated_at = datetime.now() db: Session = Depends(get_db),
db.commit() admin_user=Depends(get_current_admin),
):
logger.info(f"MCP server toggled: {server.name} -> enabled={server.is_enabled}") """Update an existing MCP server"""
return { from ...models import MCPServer
"code": 0, server = db.query(MCPServer).filter(MCPServer.id == server_id).first()
"data": {"id": server.id, "is_enabled": server.is_enabled}, if not server:
"message": "MCP server toggled", raise HTTPException(status_code=404, detail="MCP server not found")
}
update_fields = data.model_dump(exclude_unset=True)
@router.post("/{server_id}/test", summary="Test MCP server connection") # Handle JSON fields
async def test_mcp_connection( for json_field in ["args", "env_vars", "headers"]:
server_id: str, if json_field in update_fields and update_fields[json_field] is not None:
db: Session = Depends(get_db), update_fields[json_field] = json.dumps(update_fields[json_field])
admin_user=Depends(get_current_admin),
test_data: Optional[MCPServerConnectionTest] = None, for key, value in update_fields.items():
): if value is not None:
"""Test connection to an MCP server""" setattr(server, key, value)
from ...models import MCPServer
server = db.query(MCPServer).filter(MCPServer.id == server_id).first() server.updated_at = datetime.now()
if not server: db.commit()
raise HTTPException(status_code=404, detail="MCP server not found") db.refresh(server)
transport = test_data.transport_type if test_data and test_data.transport_type else server.transport_type logger.info(f"MCP server updated: {server.name} ({server.id})")
result = {"success": False, "message": "", "details": {}} return {"code": 0, "data": server_to_dict(server), "message": "MCP server updated"}
try:
if transport == "sse": @router.delete("/{server_id}", summary="Delete MCP server")
sse_url = (test_data.url if test_data else None) or server.url async def delete_mcp_server(
if not sse_url: server_id: str,
result["message"] = "SSE URL is required" db: Session = Depends(get_db),
return {"code": 0, "data": result, "message": "ok"} admin_user=Depends(get_current_admin),
):
# Test SSE endpoint with a simple HTTP request """Delete an MCP server"""
import httpx from ...models import MCPServer
try: server = db.query(MCPServer).filter(MCPServer.id == server_id).first()
response = httpx.get(sse_url, timeout=10.0) if not server:
result["success"] = response.status_code < 500 raise HTTPException(status_code=404, detail="MCP server not found")
result["message"] = f"SSE endpoint responded with {response.status_code}"
result["details"]["status_code"] = response.status_code name = server.name
result["details"]["content_type"] = response.headers.get("content-type", "") db.delete(server)
except httpx.ConnectError: db.commit()
result["message"] = "Connection refused - server may be down"
except httpx.TimeoutException: logger.info(f"MCP server deleted: {name} ({server_id})")
result["message"] = "Connection timed out" return {"code": 0, "data": None, "message": "MCP server deleted"}
elif transport == "stdio":
cmd = (test_data.command if test_data else None) or server.command @router.put("/{server_id}/toggle", summary="Enable/Disable MCP server")
if not cmd: async def toggle_mcp_server(
result["message"] = "STDIO command is required" server_id: str,
return {"code": 0, "data": result, "message": "ok"} db: Session = Depends(get_db),
admin_user=Depends(get_current_admin),
# Basic command existence check (not actually executing MCP handshake) ):
import subprocess """Toggle MCP server enabled state"""
try: from ...models import MCPServer
subprocess.run([cmd, "--version"], capture_output=True, timeout=10) server = db.query(MCPServer).filter(MCPServer.id == server_id).first()
result["success"] = True if not server:
result["message"] = "Command found and executable" raise HTTPException(status_code=404, detail="MCP server not found")
except FileNotFoundError:
result["message"] = "Command not found in PATH" server.is_enabled = not server.is_enabled
except subprocess.TimeoutExpired: server.updated_at = datetime.now()
result["message"] = "Command timed out" db.commit()
except Exception as e:
result["message"] = f"Command test failed" logger.info(f"MCP server toggled: {server.name} -> enabled={server.is_enabled}")
result["details"]["error"] = str(e) return {
"code": 0,
# Update server status "data": {"id": server.id, "is_enabled": server.is_enabled},
server.status = "connected" if result["success"] else "error" "message": "MCP server toggled",
if result["success"]: }
server.last_connected_at = datetime.now()
server.updated_at = datetime.now()
db.commit() @router.post("/{server_id}/test", summary="Test MCP server connection")
async def test_mcp_connection(
except Exception as e: server_id: str,
result["message"] = str(e) db: Session = Depends(get_db),
result["details"]["error"] = str(e) admin_user=Depends(get_current_admin),
server.status = "error" test_data: Optional[MCPServerConnectionTest] = None,
server.updated_at = datetime.now() ):
db.commit() """Test connection to an MCP server"""
from ...models import MCPServer
return {"code": 0, "data": result, "message": "ok"} server = db.query(MCPServer).filter(MCPServer.id == server_id).first()
if not server:
raise HTTPException(status_code=404, detail="MCP server not found")
@router.get("/{server_id}/tools", summary="List MCP server tools")
async def list_mcp_tools( transport = test_data.transport_type if test_data and test_data.transport_type else server.transport_type
server_id: str, result = {"success": False, "message": "", "details": {}}
db: Session = Depends(get_db),
admin_user=Depends(get_current_admin), try:
): if transport == "sse":
"""List tools exposed by an MCP server (placeholder/mock)""" sse_url = (test_data.url if test_data else None) or server.url
from ...models import MCPServer if not sse_url:
server = db.query(MCPServer).filter(MCPServer.id == server_id).first() result["message"] = "SSE URL is required"
if not server: return {"code": 0, "data": result, "message": "ok"}
raise HTTPException(status_code=404, detail="MCP server not found")
# Test SSE endpoint with a simple HTTP request
# In a real implementation, this would connect to the MCP server and call tools/list import httpx
# For now, return mock data based on common MCP tools try:
mock_tools = [] response = httpx.get(sse_url, timeout=10.0)
if server.transport_type == "stdio": result["success"] = response.status_code < 500
mock_tools = [ result["message"] = f"SSE endpoint responded with {response.status_code}"
{"name": "read_file", "description": "Read contents of a file"}, result["details"]["status_code"] = response.status_code
{"name": "write_file", "description": "Write contents to a file"}, result["details"]["content_type"] = response.headers.get("content-type", "")
{"name": "execute_command", "description": "Execute a shell command"}, except httpx.ConnectError:
{"name": "search_content", "description": "Search for content in files"}, result["message"] = "Connection refused - server may be down"
] except httpx.TimeoutException:
elif server.transport_type == "sse": result["message"] = "Connection timed out"
mock_tools = [
{"name": "web_search", "description": "Search the web"}, elif transport == "stdio":
{"name": "fetch_url", "description": "Fetch content from URL"}, cmd = (test_data.command if test_data else None) or server.command
{"name": "api_request", "description": "Make API requests"}, if not cmd:
] result["message"] = "STDIO command is required"
return {"code": 0, "data": result, "message": "ok"}
return {
"code": 0, # Basic command existence check (not actually executing MCP handshake)
"data": { import subprocess
"server_name": server.name, try:
"tools": mock_tools, subprocess.run([cmd, "--version"], capture_output=True, timeout=10)
"count": len(mock_tools), result["success"] = True
}, result["message"] = "Command found and executable"
"message": "ok", except FileNotFoundError:
} result["message"] = "Command not found in PATH"
except subprocess.TimeoutExpired:
result["message"] = "Command timed out"
@router.get("/{server_id}/resources", summary="List MCP server resources") except Exception as e:
async def list_mcp_resources( result["message"] = f"Command test failed"
server_id: str, result["details"]["error"] = str(e)
db: Session = Depends(get_db),
admin_user=Depends(get_current_admin), # Update server status
): server.status = "connected" if result["success"] else "error"
"""List resources exposed by an MCP server (placeholder/mock)""" if result["success"]:
from ...models import MCPServer server.last_connected_at = datetime.now()
server = db.query(MCPServer).filter(MCPServer.id == server_id).first() server.updated_at = datetime.now()
if not server: db.commit()
raise HTTPException(status_code=404, detail="MCP server not found")
except Exception as e:
mock_resources = [ result["message"] = str(e)
{"uri": f"{server.name}://docs/readme", "name": "Documentation", "mime_type": "text/markdown"}, result["details"]["error"] = str(e)
{"uri": f"{server.name}://config/settings", "name": "Configuration", "mime_type": "application/json"}, server.status = "error"
] server.updated_at = datetime.now()
db.commit()
return {
"code": 0, return {"code": 0, "data": result, "message": "ok"}
"data": {
"server_name": server.name,
"resources": mock_resources, @router.get("/{server_id}/tools", summary="List MCP server tools")
"count": len(mock_resources), async def list_mcp_tools(
}, server_id: str,
"message": "ok", db: Session = Depends(get_db),
} admin_user=Depends(get_current_admin),
):
"""List tools exposed by an MCP server (placeholder/mock)"""
from ...models import MCPServer
server = db.query(MCPServer).filter(MCPServer.id == server_id).first()
if not server:
raise HTTPException(status_code=404, detail="MCP server not found")
# In a real implementation, this would connect to the MCP server and call tools/list
# For now, return mock data based on common MCP tools
mock_tools = []
if server.transport_type == "stdio":
mock_tools = [
{"name": "read_file", "description": "Read contents of a file"},
{"name": "write_file", "description": "Write contents to a file"},
{"name": "execute_command", "description": "Execute a shell command"},
{"name": "search_content", "description": "Search for content in files"},
]
elif server.transport_type == "sse":
mock_tools = [
{"name": "web_search", "description": "Search the web"},
{"name": "fetch_url", "description": "Fetch content from URL"},
{"name": "api_request", "description": "Make API requests"},
]
return {
"code": 0,
"data": {
"server_name": server.name,
"tools": mock_tools,
"count": len(mock_tools),
},
"message": "ok",
}
@router.get("/{server_id}/resources", summary="List MCP server resources")
async def list_mcp_resources(
server_id: str,
db: Session = Depends(get_db),
admin_user=Depends(get_current_admin),
):
"""List resources exposed by an MCP server (placeholder/mock)"""
from ...models import MCPServer
server = db.query(MCPServer).filter(MCPServer.id == server_id).first()
if not server:
raise HTTPException(status_code=404, detail="MCP server not found")
mock_resources = [
{"uri": f"{server.name}://docs/readme", "name": "Documentation", "mime_type": "text/markdown"},
{"uri": f"{server.name}://config/settings", "name": "Configuration", "mime_type": "application/json"},
]
return {
"code": 0,
"data": {
"server_name": server.name,
"resources": mock_resources,
"count": len(mock_resources),
},
"message": "ok",
}
File diff suppressed because it is too large. Load diff
+8 -3
View File
@@ -1491,8 +1491,9 @@ const MarketPage: React.FC = () => {
// Handle hub import - 调用后端 API 创建应用 // Handle hub import - 调用后端 API 创建应用
const handleHubImport = async (platformName: string, url: string, skillName: string, price?: number, description?: string) => { const handleHubImport = async (platformName: string, url: string, skillName: string, price?: number, description?: string) => {
try { try {
const desc = description || `从 ${platformName} 导入的${showHubImport === 'skill' ? 'Skills工具' : 'MCP应用'} (来源: ${url})`; const isMcp = showHubImport === 'mcp';
await marketAPI.createApp({ const desc = description || `从 ${platformName} 导入的${isMcp ? 'MCP应用' : 'Skills工具'}${!isMcp ? ` (来源: ${url})` : ''}`;
const payload: any = {
name: skillName, name: skillName,
type: showHubImport || 'skill', type: showHubImport || 'skill',
description: desc, description: desc,
@@ -1500,7 +1501,11 @@ const MarketPage: React.FC = () => {
is_member_free: (price || 0) === 0, is_member_free: (price || 0) === 0,
join_promotion: false, join_promotion: false,
source: 'user', source: 'user',
}); };
if (isMcp && url) {
payload.download_url = url;
}
await marketAPI.createApp(payload);
// 重新加载列表 // 重新加载列表
loadApps(); loadApps();
} catch (e: any) { } catch (e: any) {