fix: sync MarketApp MCP records to admin MCP Client management
This commit is contained in:
1 parent
c9b5f964be
commit
feb9865b00
3 files changed
+114
-4
No files matched your search
@@ -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",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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) {
|
||||||
|
|||||||
Reference in new issue
Block a user