From 0de7ae8481d3493cceeed82965e613607e498bcd Mon Sep 17 00:00:00 2001 From: idiotsj Date: Fri, 27 Feb 2026 20:05:13 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20resolve=20MCP=20tools=20race=20condition?= =?UTF-8?q?=20causing=20'completion=20=E6=97=A0=E6=B3=95=E8=A7=A3=E6=9E=90?= =?UTF-8?q?'=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wait for MCP client initialization to complete before accepting requests - Add Future-based synchronization in init_mcp_clients() - Prevent tool_calls from being rejected due to empty func_list - Improve error logging for MCP initialization failures Fixes race condition where AI attempts to call MCP tools before they are registered, resulting in 'API 返回的 completion 无法解析' exceptions. The issue occurred because: 1. MCP clients were initialized asynchronously without waiting 2. System accepted user requests immediately after startup 3. AI received empty tool list and attempted to call non-existent tools 4. Tool matching failed, causing parsing errors This fix ensures all MCP tools are loaded before the system processes any requests that might use them. --- astrbot/core/provider/func_tool_manager.py | 23 +++++++++++++++++++++- astrbot/core/provider/manager.py | 4 ++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/astrbot/core/provider/func_tool_manager.py b/astrbot/core/provider/func_tool_manager.py index 7aad86bdd..db341fde5 100644 --- a/astrbot/core/provider/func_tool_manager.py +++ b/astrbot/core/provider/func_tool_manager.py @@ -212,15 +212,36 @@ class FunctionToolManager: open(mcp_json_file, encoding="utf-8"), )["mcpServers"] + # 收集所有初始化任务的 Future + init_futures: dict[str, asyncio.Future] = {} + for name in mcp_server_json_obj: cfg = mcp_server_json_obj[name] if cfg.get("active", True): event = asyncio.Event() + ready_future = asyncio.Future() + init_futures[name] = ready_future asyncio.create_task( - self._init_mcp_client_task_wrapper(name, cfg, event), + self._init_mcp_client_task_wrapper(name, cfg, event, ready_future), ) self.mcp_client_event[name] = event + # 等待所有 MCP 客户端初始化完成(或失败) + if init_futures: + logger.info(f"等待 {len(init_futures)} 个 MCP 服务初始化...") + results = await asyncio.gather( + *init_futures.values(), return_exceptions=True + ) + + success_count = 0 + for name, result in zip(init_futures.keys(), results): + if isinstance(result, Exception): + logger.error(f"MCP 服务 {name} 初始化失败: {result}") + else: + success_count += 1 + + logger.info(f"MCP 服务初始化完成: {success_count}/{len(init_futures)} 成功") + async def _init_mcp_client_task_wrapper( self, name: str, diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index 7ec8c36ff..e31da851d 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -274,8 +274,8 @@ class ProviderManager: if not self.curr_tts_provider_inst and self.tts_provider_insts: self.curr_tts_provider_inst = self.tts_provider_insts[0] - # 初始化 MCP Client 连接 - asyncio.create_task(self.llm_tools.init_mcp_clients(), name="init_mcp_clients") + # 初始化 MCP Client 连接(等待完成以确保工具可用) + await self.llm_tools.init_mcp_clients() def dynamic_import_provider(self, type: str): """动态导入提供商适配器模块