From 4b47d067e7245d0aff7a76b7fc6edfa3270e3f72 Mon Sep 17 00:00:00 2001 From: ChuwuYo <141227996+ChuwuYo@users.noreply.github.com> Date: Wed, 11 Mar 2026 01:37:13 +0800 Subject: [PATCH 1/8] fix: add namespace prefix to MCP tools to prevent conflicts with plugin tools - Add namespace prefix format: mcp___ to MCPTool - Store original tool name in MCPTool.original_tool_name for display and calling - Add backward compatibility in get_func() to support old persona tool references - Update tool list API to include display_name field showing original name - Update MCP server info to display original tool names without prefix - Fixes issue where MCP tools and plugin tools with same name would conflict --- astrbot/core/agent/mcp_client.py | 12 +++++-- astrbot/core/provider/func_tool_manager.py | 38 ++++++++++++++++++++++ astrbot/dashboard/routes/tools.py | 15 +++++++-- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/astrbot/core/agent/mcp_client.py b/astrbot/core/agent/mcp_client.py index c149792dcd..e8561b1b60 100644 --- a/astrbot/core/agent/mcp_client.py +++ b/astrbot/core/agent/mcp_client.py @@ -599,20 +599,28 @@ class MCPTool(FunctionTool, Generic[TContext]): def __init__( self, mcp_tool: mcp.Tool, mcp_client: MCPClient, mcp_server_name: str, **kwargs ) -> None: + # Add namespace prefix to avoid conflicts with plugin tools + # Format: mcp___ + namespaced_name = f"mcp_{mcp_server_name}__{mcp_tool.name}" + super().__init__( - name=mcp_tool.name, + name=namespaced_name, description=mcp_tool.description or "", parameters=mcp_tool.inputSchema, ) self.mcp_tool = mcp_tool self.mcp_client = mcp_client self.mcp_server_name = mcp_server_name + self.original_tool_name = ( + mcp_tool.name + ) # Store original name for display and calling async def call( self, context: ContextWrapper[TContext], **kwargs ) -> mcp.types.CallToolResult: + # Use original tool name when calling MCP server return await self.mcp_client.call_tool_with_reconnect( - tool_name=self.mcp_tool.name, + tool_name=self.original_tool_name, arguments=kwargs, read_timeout_seconds=timedelta(seconds=context.tool_call_timeout), ) diff --git a/astrbot/core/provider/func_tool_manager.py b/astrbot/core/provider/func_tool_manager.py index bf16a3ec96..0a38a94e6b 100644 --- a/astrbot/core/provider/func_tool_manager.py +++ b/astrbot/core/provider/func_tool_manager.py @@ -330,6 +330,24 @@ def get_func(self, name) -> FuncTool | None: for f in reversed(self.func_list): if f.name == name: return f + + # Fallback: try to find MCP tool by original name for backward compatibility + # This handles cases where personas reference tools by their original names + if isinstance(name, str): + mcp_matches = [] + for f in self.func_list: + if isinstance(f, MCPTool) and f.original_tool_name == name: + mcp_matches.append(f) + + if len(mcp_matches) == 1: + return mcp_matches[0] + elif len(mcp_matches) > 1: + logger.warning( + f"Multiple MCP tools found with original name '{name}': " + f"{[f.name for f in mcp_matches]}. Using {mcp_matches[0].name}" + ) + return mcp_matches[0] + if isinstance(name, str): try: builtin_tool = self.get_builtin_tool(name) @@ -374,6 +392,26 @@ def is_builtin_tool(self, name: str) -> bool: ensure_builtin_tools_loaded() return get_builtin_tool_class(name) is not None + # Fallback: try to find MCP tool by original name for backward compatibility + # This handles cases where personas reference tools by their original names + mcp_matches = [] + for f in self.func_list: + if isinstance(f, MCPTool) and f.original_tool_name == name: + mcp_matches.append(f) + + if len(mcp_matches) == 1: + return mcp_matches[0] + elif len(mcp_matches) > 1: + # Multiple MCP servers provide the same tool name + # Log warning and return the first one + logger.warning( + f"Multiple MCP tools found with original name '{name}': " + f"{[f.name for f in mcp_matches]}. Using {mcp_matches[0].name}" + ) + return mcp_matches[0] + + return None + def get_full_tool_set(self) -> ToolSet: """获取完整工具集 diff --git a/astrbot/dashboard/routes/tools.py b/astrbot/dashboard/routes/tools.py index 157b4d75bf..9a0186488e 100644 --- a/astrbot/dashboard/routes/tools.py +++ b/astrbot/dashboard/routes/tools.py @@ -105,7 +105,13 @@ async def get_mcp_servers(self): for name_key, runtime in self.tool_mgr.mcp_server_runtime_view.items(): if name_key == name: mcp_client = runtime.client - server_info["tools"] = [tool.name for tool in mcp_client.tools] + # Display original tool names without namespace prefix + server_info["tools"] = [ + tool.name.split("__", 1)[1] + if tool.name.startswith(f"mcp_{name}__") + else tool.name + for tool in mcp_client.tools + ] server_info["errlogs"] = mcp_client.server_errlogs break else: @@ -483,18 +489,23 @@ async def get_tool_list(self): elif isinstance(tool, MCPTool): origin = "mcp" origin_name = tool.mcp_server_name + # Add original tool name for MCP tools + display_name = getattr(tool, "original_tool_name", tool.name) elif tool.handler_module_path and star_map.get( tool.handler_module_path ): star = star_map[tool.handler_module_path] origin = "plugin" origin_name = star.name + display_name = tool.name else: origin = "unknown" origin_name = "unknown" + display_name = tool.name tool_info = { - "name": tool.name, + "name": tool.name, # Keep namespaced name for internal use + "display_name": display_name, # Original name for display "description": tool.description, "parameters": tool.parameters, "active": tool.active, From 1f83e5d30dfcefa10d89d29460028b9a600d2742 Mon Sep 17 00:00:00 2001 From: ChuwuYo <141227996+ChuwuYo@users.noreply.github.com> Date: Wed, 11 Mar 2026 02:25:58 +0800 Subject: [PATCH 2/8] fix: display friendly MCP tool names in UI - Update frontend components to use display_name for user-friendly display - Add display_name field to ToolItem TypeScript interface - Update tool list display in: * PersonaForm.vue - tool selection list and selected tools * PersonaQuickPreview.vue - tool preview display * PersonaManager.vue - persona detail view * ToolTable.vue - function tools management table - Add getToolDisplayName() helper methods in PersonaForm and PersonaManager --- astrbot/core/agent/mcp_client.py | 10 +++++-- astrbot/dashboard/routes/tools.py | 8 +++--- .../componentPanel/components/ToolTable.vue | 5 +++- .../extension/componentPanel/types.ts | 1 + .../src/components/shared/PersonaForm.vue | 10 +++++-- .../components/shared/PersonaQuickPreview.vue | 3 ++- .../src/views/persona/PersonaManager.vue | 27 ++++++++++++++++--- 7 files changed, 52 insertions(+), 12 deletions(-) diff --git a/astrbot/core/agent/mcp_client.py b/astrbot/core/agent/mcp_client.py index e8561b1b60..6bada78f7c 100644 --- a/astrbot/core/agent/mcp_client.py +++ b/astrbot/core/agent/mcp_client.py @@ -600,8 +600,14 @@ def __init__( self, mcp_tool: mcp.Tool, mcp_client: MCPClient, mcp_server_name: str, **kwargs ) -> None: # Add namespace prefix to avoid conflicts with plugin tools - # Format: mcp___ - namespaced_name = f"mcp_{mcp_server_name}__{mcp_tool.name}" + # Normalize server name: remove spaces and special characters + normalized_server_name = mcp_server_name.replace(" ", "_").replace("-", "_") + # Remove any other special characters, keep only alphanumeric and underscore + normalized_server_name = "".join( + c for c in normalized_server_name if c.isalnum() or c == "_" + ) + # Format: mcp___ + namespaced_name = f"mcp_{normalized_server_name}__{mcp_tool.name}" super().__init__( name=namespaced_name, diff --git a/astrbot/dashboard/routes/tools.py b/astrbot/dashboard/routes/tools.py index 9a0186488e..50acb9831e 100644 --- a/astrbot/dashboard/routes/tools.py +++ b/astrbot/dashboard/routes/tools.py @@ -489,8 +489,10 @@ async def get_tool_list(self): elif isinstance(tool, MCPTool): origin = "mcp" origin_name = tool.mcp_server_name - # Add original tool name for MCP tools - display_name = getattr(tool, "original_tool_name", tool.name) + # Format: _ for MCP tools + # Normalize server name for display + normalized_server = tool.mcp_server_name.replace(" ", "") + display_name = f"{normalized_server}_{tool.original_tool_name}" elif tool.handler_module_path and star_map.get( tool.handler_module_path ): @@ -505,7 +507,7 @@ async def get_tool_list(self): tool_info = { "name": tool.name, # Keep namespaced name for internal use - "display_name": display_name, # Original name for display + "display_name": display_name, # Friendly name for display "description": tool.description, "parameters": tool.parameters, "active": tool.active, diff --git a/dashboard/src/components/extension/componentPanel/components/ToolTable.vue b/dashboard/src/components/extension/componentPanel/components/ToolTable.vue index 9615e5ea8c..7202104d75 100644 --- a/dashboard/src/components/extension/componentPanel/components/ToolTable.vue +++ b/dashboard/src/components/extension/componentPanel/components/ToolTable.vue @@ -85,7 +85,10 @@ const enabledConfigTags = (tool: ToolItem): BuiltinToolConfigTag[] => { {{ tm('form.builtinToolDisabledHint') }} @@ -868,6 +868,12 @@ export default { // 检查服务器的所有工具是否都已选中 return Array.isArray(this.personaForm.tools) && server.tools.every(toolName => this.personaForm.tools.includes(toolName)); + }, + + getToolDisplayName(toolName) { + // Find tool in availableTools and return display_name if available + const tool = this.availableTools.find(t => t.name === toolName); + return tool?.display_name || toolName; } } } diff --git a/dashboard/src/components/shared/PersonaQuickPreview.vue b/dashboard/src/components/shared/PersonaQuickPreview.vue index a15ff84ecb..dc2cb1d0a7 100644 --- a/dashboard/src/components/shared/PersonaQuickPreview.vue +++ b/dashboard/src/components/shared/PersonaQuickPreview.vue @@ -125,7 +125,7 @@ const resolvedTools = computed(() => normalizedTools.value.map((toolName) => { const meta = toolMetaMap.value[toolName] || {} return { - name: toolName, + name: meta.display_name || toolName, // Use display_name for showing origin: meta.origin || '', origin_name: meta.origin_name || '', active: meta.active @@ -144,6 +144,7 @@ async function loadToolsMeta() { continue } nextMap[tool.name] = { + display_name: tool.display_name || tool.name, origin: tool.origin || '', origin_name: tool.origin_name || '', active: tool.active diff --git a/dashboard/src/views/persona/PersonaManager.vue b/dashboard/src/views/persona/PersonaManager.vue index 93bcb69523..d7992f20b3 100644 --- a/dashboard/src/views/persona/PersonaManager.vue +++ b/dashboard/src/views/persona/PersonaManager.vue @@ -164,7 +164,7 @@ class="d-flex flex-wrap ga-1"> - {{ toolName }} + {{ getToolDisplayName(toolName) }}
@@ -265,6 +265,7 @@