Skip to content

Commit f059ca1

Browse files
author
LiryLee
committed
fix: prevent MCP tool name collision by prefixing with server name
When multiple MCP servers expose tools with the same name (e.g., `read_memory`), selecting one would inadvertently select the other because both backend registration and the frontend-facing API used the raw tool name without any server identifier. Changes: - Added `clean_tool_name()` helper: sanitizes server/tool names to [a-zA-Z0-9_-], uses __ as separator, truncates to 64 chars max - MCPTool.__init__: uses clean_tool_name() for tool registration - ToolsService.get_mcp_servers: uses clean_tool_name() for API return The actual MCP call still uses the original tool name (self.mcp_tool.name), so server communication is unaffected.
1 parent 372b9f5 commit f059ca1

2 files changed

Lines changed: 30 additions & 3 deletions

File tree

astrbot/core/agent/mcp_client.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,14 +679,38 @@ async def cleanup(self) -> None:
679679
self.running_event.set()
680680

681681

682+
683+
def clean_tool_name(server_name: str, tool_name: str, max_length: int = 64) -> str:
684+
"""构建前缀化的 MCP 工具名,确保兼容 LLM API 的命名规范。
685+
686+
LLM API(OpenAI、Anthropic、Gemini 等)的工具名只允许
687+
[a-zA-Z0-9_-] 字符,且通常限制 64 字符。
688+
689+
使用双下划线 __ 作为分隔符,并对非法字符做清洗而非报错,
690+
让工具在多数场景下仍可正常工作。
691+
"""
692+
safe_server = re.sub(r"[^a-zA-Z0-9_\-]", "_", server_name)
693+
safe_tool = re.sub(r"[^a-zA-Z0-9_\-]", "_", tool_name)
694+
prefixed = f"{safe_server}__{safe_tool}"
695+
if len(prefixed) > max_length:
696+
# 优先截断工具名部分
697+
excess = len(prefixed) - max_length
698+
if len(safe_tool) > excess + 3:
699+
safe_tool = safe_tool[:len(safe_tool) - excess - 3] + "..."
700+
else:
701+
safe_tool = safe_tool[:max(3, len(safe_tool) - excess)]
702+
prefixed = f"{safe_server}__{safe_tool}"
703+
return prefixed
704+
705+
682706
class MCPTool(FunctionTool, Generic[TContext]):
683707
"""A function tool that calls an MCP service."""
684708

685709
def __init__(
686710
self, mcp_tool: mcp.Tool, mcp_client: MCPClient, mcp_server_name: str, **kwargs
687711
) -> None:
688712
super().__init__(
689-
name=mcp_tool.name,
713+
name=clean_tool_name(mcp_server_name, mcp_tool.name),
690714
description=mcp_tool.description or "",
691715
parameters=_normalize_mcp_input_schema(mcp_tool.inputSchema),
692716
)

astrbot/dashboard/services/tools_service.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import Any
55

66
from astrbot.core import logger, sp
7-
from astrbot.core.agent.mcp_client import MCPTool, validate_mcp_stdio_config
7+
from astrbot.core.agent.mcp_client import MCPTool, clean_tool_name, validate_mcp_stdio_config
88
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
99
from astrbot.core.star import star_map
1010
from astrbot.core.tools.registry import get_builtin_tool_config_statuses
@@ -79,7 +79,10 @@ def get_mcp_servers(self) -> list[dict]:
7979
for name_key, runtime in self.tool_mgr.mcp_server_runtime_view.items():
8080
if name_key == name:
8181
mcp_client = runtime.client
82-
server_info["tools"] = [tool.name for tool in mcp_client.tools]
82+
server_info["tools"] = [
83+
clean_tool_name(name, tool.name)
84+
for tool in mcp_client.tools
85+
]
8386
server_info["errlogs"] = mcp_client.server_errlogs
8487
break
8588
else:

0 commit comments

Comments
 (0)