Skip to content

Commit 4924739

Browse files
committed
refactor: simplify MCP init orchestration and improve log security
- Replace Future-based sync with asyncio.wait + name→task mapping - Explicitly cancel timed-out tasks after 20s timeout - Downgrade sensitive config details (command/args/URL) to debug level - Move urllib.parse import to top-level
1 parent ec9f740 commit 4924739

1 file changed

Lines changed: 33 additions & 44 deletions

File tree

astrbot/core/provider/func_tool_manager.py

Lines changed: 33 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import copy
55
import json
66
import os
7+
import urllib.parse
78
from collections.abc import AsyncGenerator, Awaitable, Callable
89
from typing import Any
910

@@ -212,59 +213,51 @@ async def init_mcp_clients(self) -> None:
212213
open(mcp_json_file, encoding="utf-8"),
213214
)["mcpServers"]
214215

215-
# 收集所有初始化任务的 Future
216-
init_futures: dict[str, asyncio.Future] = {}
216+
tasks: dict[str, asyncio.Task] = {}
217217

218-
for name in mcp_server_json_obj:
219-
cfg = mcp_server_json_obj[name]
218+
for name, cfg in mcp_server_json_obj.items():
220219
if cfg.get("active", True):
221220
event = asyncio.Event()
222-
ready_future = asyncio.Future()
223-
init_futures[name] = ready_future
224-
asyncio.create_task(
225-
self._init_mcp_client_task_wrapper(name, cfg, event, ready_future),
221+
task = asyncio.create_task(
222+
self._init_mcp_client_task_wrapper(name, cfg, event),
226223
)
224+
tasks[name] = task
227225
self.mcp_client_event[name] = event
228226

229-
# 等待所有 MCP 客户端初始化完成(或失败)
230-
if init_futures:
231-
logger.info(f"等待 {len(init_futures)} 个 MCP 服务初始化...")
227+
if tasks:
228+
logger.info(f"等待 {len(tasks)} 个 MCP 服务初始化...")
232229

233-
try:
234-
# 设置总超时时间为 20 秒,避免慢速 MCP 服务器阻塞启动过久
235-
results = await asyncio.wait_for(
236-
asyncio.gather(*init_futures.values(), return_exceptions=True),
237-
timeout=20.0,
238-
)
239-
except asyncio.TimeoutError:
230+
done, pending = await asyncio.wait(tasks.values(), timeout=20.0)
231+
232+
if pending:
240233
logger.warning(
241234
"MCP 服务初始化超时(20秒),部分服务可能未完全加载。"
242235
"建议检查 MCP 服务器配置和网络连接。"
243236
)
244-
# 即使超时也继续,已完成的服务仍然可用
245-
results = []
246-
for name, future in zip(init_futures.keys(), init_futures.values()):
247-
if future.done():
248-
try:
249-
results.append(future.result())
250-
except Exception as e:
251-
results.append(e)
252-
else:
253-
results.append(TimeoutError(f"MCP 服务 {name} 初始化超时"))
237+
for task in pending:
238+
task.cancel()
254239

255240
success_count = 0
256-
failed_services = []
257-
for name, result in zip(init_futures.keys(), results):
258-
if isinstance(result, Exception):
259-
logger.error(f"MCP 服务 {name} 初始化失败: {result}")
260-
# 显示配置信息以便调试
241+
failed_services: list[str] = []
242+
243+
for name, task in tasks.items():
244+
if task in pending:
245+
logger.error(f"MCP 服务 {name} 初始化超时")
246+
failed_services.append(name)
247+
continue
248+
249+
exc = task.exception()
250+
if exc is not None:
251+
logger.error(f"MCP 服务 {name} 初始化失败: {exc}")
252+
# 仅在 debug 级别输出完整配置,避免在生产日志中泄露敏感信息
261253
cfg = mcp_server_json_obj.get(name, {})
262254
if "command" in cfg:
263-
logger.error(f" 命令: {cfg['command']}")
255+
logger.debug(f" 命令: {cfg['command']}")
264256
if "args" in cfg:
265-
logger.error(f" 参数: {cfg['args']}")
257+
logger.debug(f" 参数: {cfg['args']}")
266258
elif "url" in cfg:
267-
logger.error(f" URL: {cfg['url']}")
259+
parsed = urllib.parse.urlparse(cfg["url"])
260+
logger.debug(f" 主机: {parsed.scheme}://{parsed.netloc}")
268261
failed_services.append(name)
269262
else:
270263
success_count += 1
@@ -275,28 +268,24 @@ async def init_mcp_clients(self) -> None:
275268
f"请检查配置文件 mcp_server.json 和服务器可用性。"
276269
)
277270

278-
logger.info(f"MCP 服务初始化完成: {success_count}/{len(init_futures)} 成功")
271+
logger.info(f"MCP 服务初始化完成: {success_count}/{len(tasks)} 成功")
279272

280273
async def _init_mcp_client_task_wrapper(
281274
self,
282275
name: str,
283276
cfg: dict,
284277
event: asyncio.Event,
285-
ready_future: asyncio.Future | None = None,
286278
) -> None:
287279
"""初始化 MCP 客户端的包装函数,用于捕获异常"""
288280
try:
289281
await self._init_mcp_client(name, cfg)
290282
tools = await self.mcp_client_dict[name].list_tools_and_save()
291-
if ready_future and not ready_future.done():
292-
# tell the caller we are ready
293-
ready_future.set_result(tools)
283+
logger.debug(f"MCP 服务 {name} 初始化完成,工具: {tools}")
294284
await event.wait()
295285
logger.info(f"收到 MCP 客户端 {name} 终止信号")
296-
except Exception as e:
286+
except Exception:
297287
logger.error(f"初始化 MCP 客户端 {name} 失败", exc_info=True)
298-
if ready_future and not ready_future.done():
299-
ready_future.set_exception(e)
288+
raise
300289
finally:
301290
# 无论如何都能清理
302291
await self._terminate_mcp_client(name)

0 commit comments

Comments
 (0)