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
+114 -4

No files matched your search

+62
View File
@@ -11,6 +11,7 @@ from datetime import datetime
import uuid import uuid
import json import json
import logging import logging
import re
from ...database import get_db from ...database import get_db
from ...models import gen_uuid from ...models import gen_uuid
@@ -99,6 +100,63 @@ def server_to_dict(server) -> dict:
} }
def _extract_url_from_text(text: str) -> Optional[str]:
"""Extract first http/https URL from text."""
if not text:
return None
match = re.search(r"https?://[^\s\"'<>\)\]\}]+", text)
return match.group(0) if match else None
def _sync_market_mcp_to_servers(db: Session):
"""
Synchronize MarketApp MCP records (type='mcp') into mcp_servers table.
Uses MarketApp.id as MCPServer.id so detail/edit endpoints can work directly.
"""
from ...models import MarketApp, MCPServer
market_mcps = db.query(MarketApp).filter(
MarketApp.type == "mcp",
MarketApp.status == "published",
).all()
existing_ids = {row[0] for row in db.query(MCPServer.id).all()}
for ma in market_mcps:
if ma.id in existing_ids:
continue
transport = ma.mcp_transport or "streamable_http"
code = ma.skill_content or ""
url = _extract_url_from_text(code) or _extract_url_from_text(ma.description or "") or ""
command = ""
if not url and ma.agent_endpoint:
url = ma.agent_endpoint
now = datetime.now()
server = MCPServer(
id=ma.id,
name=ma.name,
description=ma.description or "",
transport_type=transport,
command=command,
args="{}",
env_vars="{}",
url=url,
headers="{}",
is_enabled=True,
status="disconnected",
tools_count=0,
resources_count=0,
created_at=ma.created_at or now,
updated_at=ma.updated_at or now,
)
db.add(server)
if market_mcps:
db.commit()
# ============================================================ # ============================================================
# API Endpoints # API Endpoints
# ============================================================ # ============================================================
@@ -110,6 +168,7 @@ async def get_mcp_stats(
): ):
"""Get overview statistics for MCP servers""" """Get overview statistics for MCP servers"""
from ...models import MCPServer from ...models import MCPServer
_sync_market_mcp_to_servers(db)
total = db.query(func.count(MCPServer.id)).scalar() or 0 total = db.query(func.count(MCPServer.id)).scalar() or 0
enabled = db.query(func.count(MCPServer.id)).filter(MCPServer.is_enabled == True).scalar() or 0 enabled = db.query(func.count(MCPServer.id)).filter(MCPServer.is_enabled == True).scalar() or 0
connected = db.query(func.count(MCPServer.id)).filter(MCPServer.status == "connected").scalar() or 0 connected = db.query(func.count(MCPServer.id)).filter(MCPServer.status == "connected").scalar() or 0
@@ -152,6 +211,7 @@ async def list_mcp_servers(
): ):
"""List all MCP servers with pagination and filters""" """List all MCP servers with pagination and filters"""
from ...models import MCPServer from ...models import MCPServer
_sync_market_mcp_to_servers(db)
query = db.query(MCPServer) query = db.query(MCPServer)
if search: if search:
@@ -188,6 +248,7 @@ async def get_mcp_server(
): ):
"""Get a single MCP server by ID""" """Get a single MCP server by ID"""
from ...models import MCPServer from ...models import MCPServer
_sync_market_mcp_to_servers(db)
server = db.query(MCPServer).filter(MCPServer.id == server_id).first() server = db.query(MCPServer).filter(MCPServer.id == server_id).first()
if not server: if not server:
raise HTTPException(status_code=404, detail="MCP server not found") raise HTTPException(status_code=404, detail="MCP server not found")
@@ -447,3 +508,4 @@ async def list_mcp_resources(
}, },
"message": "ok", "message": "ok",
} }
+44 -1
View File
@@ -7,11 +7,12 @@ import uuid
from datetime import datetime from datetime import datetime
import httpx import httpx
from urllib.parse import urlparse from urllib.parse import urlparse
import re
from ..database import get_db from ..database import get_db
from ..models import ( from ..models import (
User, MarketApp, AppPurchase, AppReview, AppInstallation, Order, User, MarketApp, AppPurchase, AppReview, AppInstallation, Order,
AdBanner, gen_uuid AdBanner, MCPServer, gen_uuid
) )
from ..auth import get_current_user, get_current_user_id, get_current_user_id_optional from ..auth import get_current_user, get_current_user_id, get_current_user_id_optional
from ..middleware.log_service import LogService from ..middleware.log_service import LogService
@@ -19,6 +20,14 @@ from ..middleware.log_service import LogService
router = APIRouter() router = APIRouter()
def _extract_url_from_text(text: Optional[str]) -> Optional[str]:
"""Extract first http/https URL from text."""
if not text:
return None
match = re.search(r"https?://[^\s\"'<>\)\]\}]+", text)
return match.group(0) if match else None
# ===== Agent 接入前连通性 / 鉴权校验 ===== # ===== Agent 接入前连通性 / 鉴权校验 =====
def _normalize_dify_url(endpoint: str) -> str: def _normalize_dify_url(endpoint: str) -> str:
ep = endpoint.rstrip("/") ep = endpoint.rstrip("/")
@@ -375,6 +384,39 @@ def upload_app(
db.commit() db.commit()
db.refresh(app) db.refresh(app)
# MCP 应用同步注册到后台 MCP Client 管理(mcp_servers 表)
if app.type == "mcp":
transport = "streamable_http"
code = req.download_url or ""
if code:
app.skill_content = code
app.mcp_transport = transport
db.commit()
db.refresh(app)
existing_server = db.query(MCPServer).filter(MCPServer.id == app.id).first()
if not existing_server:
url = _extract_url_from_text(code) or _extract_url_from_text(req.description or "") or ""
server = MCPServer(
id=app.id,
name=app.name,
description=app.description or "",
transport_type=transport,
command="",
args="{}",
env_vars="{}",
url=url,
headers="{}",
is_enabled=True,
status="disconnected",
tools_count=0,
resources_count=0,
created_at=app.created_at or datetime.now(),
updated_at=app.updated_at or datetime.now(),
)
db.add(server)
db.commit()
# 用户创建/导入的应用自动标记为已安装 # 用户创建/导入的应用自动标记为已安装
existing_install = db.query(AppInstallation).filter( existing_install = db.query(AppInstallation).filter(
AppInstallation.user_id == current_user.id, AppInstallation.user_id == current_user.id,
@@ -769,3 +811,4 @@ def get_agent_resources(
}, },
"message": "ok", "message": "ok",
} }
+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) {