From 293e848e20e50ad410b956b8bb1bb61cb6a3540b Mon Sep 17 00:00:00 2001 From: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Date: Thu, 21 May 2026 02:50:20 +0800 Subject: [PATCH] fix: preserve ModelScope MCP transport --- astrbot/core/provider/func_tool_manager.py | 22 +++++++++++++++++++++- tests/unit/test_func_tool_manager.py | 19 ++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/astrbot/core/provider/func_tool_manager.py b/astrbot/core/provider/func_tool_manager.py index ab6dd037f4..003a9926c3 100644 --- a/astrbot/core/provider/func_tool_manager.py +++ b/astrbot/core/provider/func_tool_manager.py @@ -146,6 +146,24 @@ def _resolve_timeout( FuncTool = FunctionTool +def _modelscope_mcp_transport_from_url_info(url_info: Mapping[str, Any]) -> str: + for key in ("transport", "transport_type", "type", "protocol"): + raw_value = url_info.get(key) + if isinstance(raw_value, Mapping): + raw_value = raw_value.get("type") or raw_value.get("name") + if isinstance(raw_value, str): + value = raw_value.strip().lower().replace("-", "_").replace(" ", "_") + if value in {"streamable_http", "streamablehttp", "http"}: + return "streamable_http" + if value in {"sse", "server_sent_events"}: + return "sse" + + path = urllib.parse.urlparse(str(url_info.get("url", ""))).path.rstrip("/").lower() + if path.endswith("/sse"): + return "sse" + return "streamable_http" + + def _prepare_config(config: dict) -> dict: """准备配置,处理嵌套格式""" if config.get("mcpServers"): @@ -949,7 +967,9 @@ async def sync_modelscope_mcp_servers(self, access_token: str) -> None: # 添加到配置中(同名会覆盖) local_mcp_config["mcpServers"][server_name] = { "url": server_url, - "transport": "sse", + "transport": _modelscope_mcp_transport_from_url_info( + url_info + ), "active": True, "provider": "modelscope", } diff --git a/tests/unit/test_func_tool_manager.py b/tests/unit/test_func_tool_manager.py index d53ed3296f..6b1e5c4453 100644 --- a/tests/unit/test_func_tool_manager.py +++ b/tests/unit/test_func_tool_manager.py @@ -3,7 +3,10 @@ import pytest from astrbot.core import sp -from astrbot.core.provider.func_tool_manager import FunctionToolManager +from astrbot.core.provider.func_tool_manager import ( + FunctionToolManager, + _modelscope_mcp_transport_from_url_info, +) from astrbot.core.tools.computer_tools.shell import ExecuteShellTool from astrbot.core.tools.message_tools import SendMessageToUserTool from astrbot.core.tools.web_search_tools import ( @@ -293,6 +296,20 @@ def test_is_self_detached_command_handles_quotes_and_comments(command, expected) assert _is_self_detached_command(command) is expected +@pytest.mark.parametrize( + ("url_info", "expected"), + [ + ({"transport": "Streamable HTTP", "url": "https://example.com/mcp"}, "streamable_http"), + ({"transport_type": "sse", "url": "https://example.com/mcp"}, "sse"), + ({"type": "streamable_http", "url": "https://example.com/mcp"}, "streamable_http"), + ({"url": "https://example.com/mcp/sse"}, "sse"), + ({"url": "https://example.com/mcp"}, "streamable_http"), + ], +) +def test_modelscope_mcp_transport_from_url_info(url_info, expected): + assert _modelscope_mcp_transport_from_url_info(url_info) == expected + + @pytest.mark.asyncio async def test_execute_shell_reports_blank_exception_type(monkeypatch): from astrbot.core.tools.computer_tools import shell as shell_tools