Skip to content

Commit 6ccecfd

Browse files
committed
fix: improve MCP tool namespace handling and fix type errors
- Use URL encoding for MCP server name normalization to avoid collisions - Add deterministic sorting for ambiguous MCP tool name resolution - Add type assertion to fix Pylance type error - Simplify MCP server tools display (use raw tool names directly)
1 parent 4889e03 commit 6ccecfd

3 files changed

Lines changed: 12 additions & 15 deletions

File tree

astrbot/core/agent/mcp_client.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -380,12 +380,10 @@ def __init__(
380380
self, mcp_tool: mcp.Tool, mcp_client: MCPClient, mcp_server_name: str, **kwargs
381381
) -> None:
382382
# Add namespace prefix to avoid conflicts with plugin tools
383-
# Normalize server name: remove spaces and special characters
384-
normalized_server_name = mcp_server_name.replace(" ", "_").replace("-", "_")
385-
# Remove any other special characters, keep only alphanumeric and underscore
386-
normalized_server_name = "".join(
387-
c for c in normalized_server_name if c.isalnum() or c == "_"
388-
)
383+
# URL-encode the server name to create a safe and unique identifier part
384+
from urllib.parse import quote
385+
386+
normalized_server_name = quote(mcp_server_name, safe="")
389387
# Format: mcp_<normalized_server_name>__<tool_name>
390388
namespaced_name = f"mcp_{normalized_server_name}__{mcp_tool.name}"
391389

astrbot/core/provider/func_tool_manager.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,8 @@ def get_func(self, name) -> FuncTool | None:
327327
return mcp_matches[0]
328328
elif len(mcp_matches) > 1:
329329
# Multiple MCP servers provide the same tool name
330-
# Log warning and return the first one
330+
# Sort by namespaced name for deterministic selection
331+
mcp_matches.sort(key=lambda f: f.name)
331332
logger.warning(
332333
f"Multiple MCP tools found with original name '{name}': "
333334
f"{[f.name for f in mcp_matches]}. Using {mcp_matches[0].name}"
@@ -525,6 +526,8 @@ async def lifecycle() -> None:
525526

526527
lifecycle_task = asyncio.create_task(lifecycle(), name=f"mcp-client:{name}")
527528
async with self._runtime_lock:
529+
# After successful initialization, mcp_client is guaranteed to be non-None
530+
assert mcp_client is not None
528531
self._mcp_server_runtime[name] = _MCPServerRuntime(
529532
name=name,
530533
client=mcp_client,

astrbot/dashboard/routes/tools.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,13 @@ async def get_mcp_servers(self):
100100
if key != "active": # active 已经处理
101101
server_info[key] = value
102102

103-
# 如果MCP客户端已初始化,从客户端获取工具名称
103+
# If MCP client is initialized, get tool names from client
104+
# Note: mcp_client.tools contains raw MCP tools (mcp.Tool), not MCPTool
105+
# So we use tool.name directly (no namespace prefix to strip)
104106
for name_key, runtime in self.tool_mgr.mcp_server_runtime_view.items():
105107
if name_key == name:
106108
mcp_client = runtime.client
107-
# Display original tool names without namespace prefix
108-
server_info["tools"] = [
109-
tool.name.split("__", 1)[1]
110-
if tool.name.startswith(f"mcp_{name}__")
111-
else tool.name
112-
for tool in mcp_client.tools
113-
]
109+
server_info["tools"] = [tool.name for tool in mcp_client.tools]
114110
server_info["errlogs"] = mcp_client.server_errlogs
115111
break
116112
else:

0 commit comments

Comments
 (0)