From 5523770629df16fe8c6ec8272ba4db87e49b3d67 Mon Sep 17 00:00:00 2001 From: VectorPeak <73048950+VectorPeak@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:26:01 +0800 Subject: [PATCH 1/2] fix: enable only synced ModelScope MCP servers --- astrbot/core/provider/func_tool_manager.py | 21 +++--- tests/unit/test_func_tool_manager.py | 76 ++++++++++++++++++++++ 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/astrbot/core/provider/func_tool_manager.py b/astrbot/core/provider/func_tool_manager.py index 8a2565c41d..20f931334d 100644 --- a/astrbot/core/provider/func_tool_manager.py +++ b/astrbot/core/provider/func_tool_manager.py @@ -1059,9 +1059,12 @@ async def sync_modelscope_mcp_servers(self, access_token: str) -> None: ) local_mcp_config = self.load_mcp_config() - synced_count = 0 + mcp_servers = local_mcp_config.setdefault("mcpServers", {}) + synced_servers: list[tuple[str, dict]] = [] for server in mcp_server_list: - server_name = server["name"] + server_name = server.get("name") + if not server_name: + continue operational_urls = server.get("operational_urls", []) if not operational_urls: continue @@ -1070,28 +1073,28 @@ async def sync_modelscope_mcp_servers(self, access_token: str) -> None: if not server_url: continue # 添加到配置中(同名会覆盖) - local_mcp_config["mcpServers"][server_name] = { + server_config = { "url": server_url, "transport": "sse", "active": True, "provider": "modelscope", } - synced_count += 1 + mcp_servers[server_name] = server_config + synced_servers.append((server_name, server_config)) - if synced_count > 0: + if synced_servers: self.save_mcp_config(local_mcp_config) tasks = [] - for server in mcp_server_list: - name = server["name"] + for name, config in synced_servers: tasks.append( self.enable_mcp_server( name=name, - config=local_mcp_config["mcpServers"][name], + config=config, ), ) await asyncio.gather(*tasks) logger.info( - f"从 ModelScope 同步了 {synced_count} 个 MCP 服务器", + f"从 ModelScope 同步了 {len(synced_servers)} 个 MCP 服务器", ) else: logger.warning("没有找到可用的 ModelScope MCP 服务器") diff --git a/tests/unit/test_func_tool_manager.py b/tests/unit/test_func_tool_manager.py index d53ed3296f..4d9cb82426 100644 --- a/tests/unit/test_func_tool_manager.py +++ b/tests/unit/test_func_tool_manager.py @@ -3,6 +3,7 @@ import pytest from astrbot.core import sp +from astrbot.core.provider import func_tool_manager as ftm from astrbot.core.provider.func_tool_manager import FunctionToolManager from astrbot.core.tools.computer_tools.shell import ExecuteShellTool from astrbot.core.tools.message_tools import SendMessageToUserTool @@ -345,3 +346,78 @@ def test_firecrawl_tools_are_registered_as_builtin_tools(): assert extract_tool.name == "firecrawl_extract_web_page" assert manager.is_builtin_tool("web_search_firecrawl") is True assert manager.is_builtin_tool("firecrawl_extract_web_page") is True + + +@pytest.mark.asyncio +async def test_modelscope_sync_enables_only_synced_servers(monkeypatch): + class FakeResponse: + status = 200 + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + return False + + async def json(self): + return { + "data": { + "mcp_server_list": [ + { + "name": "valid", + "operational_urls": [{"url": "https://example.com/mcp"}], + }, + {"name": "missing-url", "operational_urls": []}, + {"name": "empty-url", "operational_urls": [{}]}, + {"operational_urls": [{"url": "https://example.com/no-name"}]}, + ] + } + } + + class FakeSession: + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + return False + + def get(self, *_args, **_kwargs): + return FakeResponse() + + saved_configs = [] + enabled_servers = [] + manager = FunctionToolManager() + + async def fake_enable_mcp_server(name, config): + enabled_servers.append((name, config)) + + monkeypatch.setattr(ftm.aiohttp, "ClientSession", lambda: FakeSession()) + monkeypatch.setattr(manager, "load_mcp_config", lambda: {"mcpServers": {}}) + monkeypatch.setattr(manager, "save_mcp_config", saved_configs.append) + monkeypatch.setattr(manager, "enable_mcp_server", fake_enable_mcp_server) + + await manager.sync_modelscope_mcp_servers("token") + + assert saved_configs == [ + { + "mcpServers": { + "valid": { + "url": "https://example.com/mcp", + "transport": "sse", + "active": True, + "provider": "modelscope", + } + } + } + ] + assert enabled_servers == [ + ( + "valid", + { + "url": "https://example.com/mcp", + "transport": "sse", + "active": True, + "provider": "modelscope", + }, + ) + ] From cf176c019dbe8ac32a7db3baa5c6a8e6ba629702 Mon Sep 17 00:00:00 2001 From: VectorPeak <73048950+VectorPeak@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:42:23 +0800 Subject: [PATCH 2/2] fix: avoid mutating default MCP config --- astrbot/core/provider/func_tool_manager.py | 2 +- tests/unit/test_func_tool_manager.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/astrbot/core/provider/func_tool_manager.py b/astrbot/core/provider/func_tool_manager.py index 20f931334d..df49f66f47 100644 --- a/astrbot/core/provider/func_tool_manager.py +++ b/astrbot/core/provider/func_tool_manager.py @@ -1057,7 +1057,7 @@ async def sync_modelscope_mcp_servers(self, access_token: str) -> None: "mcp_server_list", [], ) - local_mcp_config = self.load_mcp_config() + local_mcp_config = copy.deepcopy(self.load_mcp_config()) mcp_servers = local_mcp_config.setdefault("mcpServers", {}) synced_servers: list[tuple[str, dict]] = [] diff --git a/tests/unit/test_func_tool_manager.py b/tests/unit/test_func_tool_manager.py index 4d9cb82426..94ea297a13 100644 --- a/tests/unit/test_func_tool_manager.py +++ b/tests/unit/test_func_tool_manager.py @@ -386,18 +386,20 @@ def get(self, *_args, **_kwargs): saved_configs = [] enabled_servers = [] + default_config = {"mcpServers": {}} manager = FunctionToolManager() async def fake_enable_mcp_server(name, config): enabled_servers.append((name, config)) monkeypatch.setattr(ftm.aiohttp, "ClientSession", lambda: FakeSession()) - monkeypatch.setattr(manager, "load_mcp_config", lambda: {"mcpServers": {}}) + monkeypatch.setattr(manager, "load_mcp_config", lambda: default_config) monkeypatch.setattr(manager, "save_mcp_config", saved_configs.append) monkeypatch.setattr(manager, "enable_mcp_server", fake_enable_mcp_server) await manager.sync_modelscope_mcp_servers("token") + assert default_config == {"mcpServers": {}} assert saved_configs == [ { "mcpServers": {