fix: 移除退役内置 MCP 默认项

This commit is contained in:
Wenjie Zhang 2026-06-03 22:12:13 +08:00
parent bf7a4821d1
commit 79ff9b14b8
5 changed files with 104 additions and 13 deletions

View File

@ -38,13 +38,6 @@ _UNSET = object()
# Default MCP Server configurations (Imported to DB on first run)
_DEFAULT_MCP_SERVERS = {
"sequentialthinking": {
"url": "https://remote.mcpservers.org/sequentialthinking/mcp",
"transport": "streamable_http",
"description": "顺序思考工具,帮助 AI 将复杂问题分解为多个步骤",
"icon": "🧠",
"tags": ["内置", "AI"],
},
"mcp-server-chart": {
"command": "npx",
"args": ["-y", "@antv/mcp-server-chart"],
@ -55,6 +48,8 @@ _DEFAULT_MCP_SERVERS = {
},
}
_RETIRED_BUILTIN_MCP_SERVER_SLUGS = ("sequentialthinking",)
_SYNCED_MCP_FIELDS = (
"description",
"transport",
@ -81,6 +76,17 @@ async def ensure_builtin_mcp_servers_in_db() -> None:
try:
async with pg_manager.get_async_session_context() as session:
any_changed = False
for slug in _RETIRED_BUILTIN_MCP_SERVER_SLUGS:
result = await session.execute(
select(MCPServer).filter(MCPServer.slug == slug, MCPServer.created_by == "system")
)
retired = result.scalar_one_or_none()
if retired:
await session.delete(retired)
clear_mcp_server_tools_cache(slug)
any_changed = True
logger.info(f"Removed retired built-in MCP server '{slug}' from database")
for slug, config in _DEFAULT_MCP_SERVERS.items():
result = await session.execute(select(MCPServer).filter(MCPServer.slug == slug))
existing = result.scalar_one_or_none()

View File

@ -49,7 +49,7 @@ def test_update_mcp_server_status(monkeypatch):
self.enabled = enabled
def to_dict(self):
return {"name": "sequentialthinking", "enabled": self.enabled}
return {"name": "demo-mcp", "enabled": self.enabled}
async def fake_set_server_enabled(db, name, enabled, updated_by=None):
captured["name"] = name
@ -60,13 +60,13 @@ def test_update_mcp_server_status(monkeypatch):
monkeypatch.setattr("server.routers.mcp_router.set_server_enabled", fake_set_server_enabled)
client = TestClient(_build_app())
resp = client.put("/api/system/mcp-servers/sequentialthinking/status", json={"enabled": False})
resp = client.put("/api/system/mcp-servers/demo-mcp/status", json={"enabled": False})
assert resp.status_code == 200, resp.text
payload = resp.json()
assert payload["success"] is True
assert payload["enabled"] is False
assert payload["data"]["enabled"] is False
assert captured == {"name": "sequentialthinking", "enabled": False, "updated_by": "admin"}
assert captured == {"name": "demo-mcp", "enabled": False, "updated_by": "admin"}
def test_update_mcp_server_status_not_found(monkeypatch):

View File

@ -2,7 +2,37 @@ from __future__ import annotations
from types import SimpleNamespace
import pytest_asyncio
from sqlalchemy import select
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from yuxi.agents.mcp import service as mcp_service
from yuxi.storage.postgres import manager as postgres_manager
from yuxi.storage.postgres.models_business import MCPServer
class _AsyncSessionContext:
def __init__(self, db):
self.db = db
async def __aenter__(self):
return self.db
async def __aexit__(self, *_args):
return False
@pytest_asyncio.fixture
async def mcp_session():
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
async with engine.begin() as conn:
await conn.run_sync(MCPServer.__table__.create)
session_factory = async_sessionmaker(engine, expire_on_commit=False)
async with session_factory() as session:
yield session
await engine.dispose()
class _FakeClient:
@ -13,6 +43,61 @@ class _FakeClient:
return self._tools
async def test_ensure_builtin_mcp_servers_removes_retired_system_server(monkeypatch, mcp_session):
retired_server = MCPServer(
slug="sequentialthinking",
name="sequentialthinking",
description="old builtin",
transport="streamable_http",
url="https://remote.mcpservers.org/sequentialthinking/mcp",
enabled=1,
created_by="system",
updated_by="system",
)
mcp_session.add(retired_server)
await mcp_session.commit()
monkeypatch.setattr(
postgres_manager.pg_manager,
"get_async_session_context",
lambda: _AsyncSessionContext(mcp_session),
)
await mcp_service.ensure_builtin_mcp_servers_in_db()
retired = await mcp_session.scalar(select(MCPServer).where(MCPServer.slug == "sequentialthinking"))
chart = await mcp_session.scalar(select(MCPServer).where(MCPServer.slug == "mcp-server-chart"))
assert retired is None
assert chart is not None
async def test_ensure_builtin_mcp_servers_preserves_user_server_with_retired_slug(monkeypatch, mcp_session):
user_server = MCPServer(
slug="sequentialthinking",
name="用户自定义 MCP",
description="user managed",
transport="streamable_http",
url="https://example.com/mcp",
enabled=1,
created_by="admin",
updated_by="admin",
)
mcp_session.add(user_server)
await mcp_session.commit()
monkeypatch.setattr(
postgres_manager.pg_manager,
"get_async_session_context",
lambda: _AsyncSessionContext(mcp_session),
)
await mcp_service.ensure_builtin_mcp_servers_in_db()
server = await mcp_session.scalar(select(MCPServer).where(MCPServer.slug == "sequentialthinking"))
assert server is not None
assert server.created_by == "admin"
async def test_get_enabled_mcp_tools_loads_latest_config_from_db(monkeypatch):
captured: list[dict] = []
@ -135,4 +220,3 @@ async def test_get_mcp_tools_sets_handle_tool_error(monkeypatch):
assert tools[0].handle_tool_error is True
mcp_service.clear_mcp_cache()

View File

@ -18,9 +18,9 @@ MCPModel Context Protocol是扩展智能体能力的重要方式。系统
```json
{
"name": "sequentialthinking",
"name": "custom-remote-mcp",
"transport": "streamable_http",
"url": "https://remote.mcpservers.org/sequentialthinking/mcp"
"url": "https://example.com/mcp"
}
```

View File

@ -38,6 +38,7 @@
### 0.7.0 开发记录
<!-- 0.7.0 的内容请放在这里 -->
- 调整内置 MCP 默认项:移除 `sequentialthinking` 的系统内置同步,启动同步时清理历史系统内置记录,保留用户手动创建的同名 MCP。
- 图片生成能力迁移为 SkillQwen-Image 从内置 Python 生成工具迁移到内置 Skill `image-gen`,模型调用与图片下载在 Agent 沙盒中完成,生成结果保存到 outputs 并通过 `present_artifacts` 展示,为多图片生成模型接入复用同一产物展示链路。
- 降低知识库路由与工具模块复杂度:示例问题生成迁移到知识库 utils文件上传统一 100 MB 限制URL 预处理入库路径与旧 `content_type=url` 行为收敛,并修复 uid、导出 MIME 与异常透传等路由问题。
- 重构智能体配置语义:用户可见的 `AgentConfig` 收敛为数据库持久化的一级 `Agent`,内置 Python Agent 改为智能体后端;新增 `/api/agent` 管理与运行接口,聊天、运行任务、恢复审批和文件预览均从线程绑定的 Agent 解析运行时上下文,前端只提交 `agent_id`,并在模型配置页新增“智能体”管理页签。