Skip to content

Commit 423626a

Browse files
authored
Merge pull request #1504 from xinnan-tech/fix-mcp-name
Fix mcp name
2 parents 2a212ae + 182acc0 commit 423626a

File tree

3 files changed

+46
-20
lines changed

3 files changed

+46
-20
lines changed

main/xiaozhi-server/core/handle/mcpHandle.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
import asyncio
33
from concurrent.futures import Future
4-
from core.utils.util import get_vision_url
4+
from core.utils.util import get_vision_url, sanitize_tool_name
55
from core.utils.auth import AuthToken
66

77
TAG = __name__
@@ -11,7 +11,8 @@ class MCPClient:
1111
"""MCPClient,用于管理MCP状态和工具"""
1212

1313
def __init__(self):
14-
self.tools = {} # Dictionary for O(1) lookup
14+
self.tools = {} # sanitized_name -> tool_data
15+
self.name_mapping = {}
1516
self.ready = False
1617
self.call_results = {} # To store Futures for tool call responses
1718
self.next_id = 1
@@ -30,7 +31,7 @@ def get_available_tools(self) -> list:
3031
result = []
3132
for tool_name, tool_data in self.tools.items():
3233
function_def = {
33-
"name": tool_data["name"],
34+
"name": tool_name,
3435
"description": tool_data["description"],
3536
"parameters": {
3637
"type": tool_data["inputSchema"].get("type", "object"),
@@ -53,7 +54,9 @@ async def set_ready(self, status: bool):
5354

5455
async def add_tool(self, tool_data: dict):
5556
async with self.lock:
56-
self.tools[tool_data["name"]] = tool_data
57+
sanitized_name = sanitize_tool_name(tool_data["name"])
58+
self.tools[sanitized_name] = tool_data
59+
self.name_mapping[sanitized_name] = tool_data["name"]
5760
self._cached_available_tools = (
5861
None # Invalidate the cache when a tool is added
5962
)
@@ -133,9 +136,6 @@ async def handle_mcp_message(conn, mcp_client: MCPClient, payload: dict):
133136
conn.logger.bind(tag=TAG).info(
134137
f"客户端MCP服务器信息: name={name}, version={version}"
135138
)
136-
await send_mcp_tools_list_request(
137-
conn
138-
) # After initialization, request tool list
139139
return
140140

141141
elif msg_id == 2: # mcpToolsListID
@@ -174,6 +174,20 @@ async def handle_mcp_message(conn, mcp_client: MCPClient, payload: dict):
174174
await mcp_client.add_tool(new_tool)
175175
conn.logger.bind(tag=TAG).debug(f"客户端工具 #{i+1}: {name}")
176176

177+
# 替换所有工具描述中的工具名称
178+
for tool_data in mcp_client.tools.values():
179+
if "description" in tool_data:
180+
description = tool_data["description"]
181+
# 遍历所有工具名称进行替换
182+
for (
183+
sanitized_name,
184+
original_name,
185+
) in mcp_client.name_mapping.items():
186+
description = description.replace(
187+
original_name, sanitized_name
188+
)
189+
tool_data["description"] = description
190+
177191
next_cursor = result.get("nextCursor", "")
178192
if next_cursor:
179193
conn.logger.bind(tag=TAG).info(
@@ -219,8 +233,6 @@ async def send_mcp_initialize_message(conn):
219233
"token": token,
220234
}
221235

222-
conn.logger.bind(tag=TAG).info(f"视觉服务信息: {vision}")
223-
224236
payload = {
225237
"jsonrpc": "2.0",
226238
"id": 1, # mcpInitializeID
@@ -333,23 +345,24 @@ async def call_mcp_tool(
333345
raise ValueError(f"参数处理失败: {str(e)}")
334346
raise e
335347

348+
actual_name = mcp_client.name_mapping.get(tool_name, tool_name)
336349
payload = {
337350
"jsonrpc": "2.0",
338351
"id": tool_call_id,
339352
"method": "tools/call",
340-
"params": {"name": tool_name, "arguments": arguments},
353+
"params": {"name": actual_name, "arguments": arguments},
341354
}
342355

343356
conn.logger.bind(tag=TAG).info(
344-
f"发送客户端mcp工具调用请求: {tool_name},参数: {args}"
357+
f"发送客户端mcp工具调用请求: {actual_name},参数: {args}"
345358
)
346359
await send_mcp_message(conn, payload)
347360

348361
try:
349362
# Wait for response or timeout
350363
raw_result = await asyncio.wait_for(result_future, timeout=timeout)
351364
conn.logger.bind(tag=TAG).info(
352-
f"客户端mcp工具调用 {tool_name} 成功,原始结果: {raw_result}"
365+
f"客户端mcp工具调用 {actual_name} 成功,原始结果: {raw_result}"
353366
)
354367

355368
if isinstance(raw_result, dict):

main/xiaozhi-server/core/mcp/MCPClient.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from mcp.client.stdio import stdio_client
1010
from mcp.client.sse import sse_client
1111
from config.logger import setup_logging
12+
from core.utils.util import sanitize_tool_name
1213

1314
TAG = __name__
1415

@@ -23,7 +24,9 @@ def __init__(self, config: Dict[str, Any]):
2324
self._shutdown_evt = asyncio.Event()
2425

2526
self.session: Optional[ClientSession] = None
26-
self.tools: List = []
27+
self.tools: List = [] # original tool objects
28+
self.tools_dict: Dict[str, Any] = {}
29+
self.name_mapping: Dict[str, str] = {}
2730

2831
async def initialize(self):
2932
if self._worker_task:
@@ -32,7 +35,7 @@ async def initialize(self):
3235
await self._ready_evt.wait()
3336

3437
self.logger.bind(tag=TAG).info(
35-
f"Connected, tools = {[t.name for t in self.tools]}"
38+
f"Connected, tools = {[name for name in self.name_mapping.values()]}"
3639
)
3740

3841
async def cleanup(self):
@@ -48,27 +51,28 @@ async def cleanup(self):
4851
self._worker_task = None
4952

5053
def has_tool(self, name: str) -> bool:
51-
return any(t.name == name for t in self.tools)
54+
return name in self.tools_dict
5255

5356
def get_available_tools(self):
5457
return [
5558
{
5659
"type": "function",
5760
"function": {
58-
"name": t.name,
59-
"description": t.description,
60-
"parameters": t.inputSchema,
61+
"name": name,
62+
"description": tool.description,
63+
"parameters": tool.inputSchema,
6164
},
6265
}
63-
for t in self.tools
66+
for name, tool in self.tools_dict.items()
6467
]
6568

6669
async def call_tool(self, name: str, args: dict):
6770
if not self.session:
6871
raise RuntimeError("MCPClient not initialized")
6972

73+
real_name = self.name_mapping.get(name, name)
7074
loop = self._worker_task.get_loop()
71-
coro = self.session.call_tool(name, args)
75+
coro = self.session.call_tool(real_name, args)
7276

7377
if loop is asyncio.get_running_loop():
7478
return await coro
@@ -123,6 +127,10 @@ async def _worker(self):
123127

124128
# 获取工具
125129
self.tools = (await self.session.list_tools()).tools
130+
for t in self.tools:
131+
sanitized = sanitize_tool_name(t.name)
132+
self.tools_dict[sanitized] = t
133+
self.name_mapping[sanitized] = t.name
126134

127135
self._ready_evt.set()
128136

main/xiaozhi-server/core/utils/util.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,3 +976,8 @@ def is_valid_image_file(file_data: bytes) -> bool:
976976
return True
977977

978978
return False
979+
980+
981+
def sanitize_tool_name(name: str) -> str:
982+
"""Sanitize tool names for OpenAI compatibility."""
983+
return re.sub(r"[^a-zA-Z0-9_-]", "_", name)

0 commit comments

Comments
 (0)