Skip to content

Commit 3379587

Browse files
authored
feat(mcp): enhance logging and initialize MCP clients in background (#5993)
* feat(mcp): enhance logging and initialize MCP clients in background fixes: #5777 * rf * fix(mcp): simplify MCP client initialization in background * fix(mcp): update error message for MCP background initialization failure
1 parent e25a1a4 commit 3379587

3 files changed

Lines changed: 60 additions & 41 deletions

File tree

astrbot/core/agent/mcp_client.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,14 @@ async def connect_to_server(self, mcp_server_config: dict, name: str) -> None:
144144

145145
cfg = _prepare_config(mcp_server_config.copy())
146146

147-
def logging_callback(msg: str) -> None:
147+
def logging_callback(
148+
msg: str | mcp.types.LoggingMessageNotificationParams,
149+
) -> None:
148150
# Handle MCP service error logs
149-
print(f"MCP Server {name} Error: {msg}")
150-
self.server_errlogs.append(msg)
151+
if isinstance(msg, mcp.types.LoggingMessageNotificationParams):
152+
if msg.level in ("warning", "error", "critical", "alert", "emergency"):
153+
log_msg = f"[{msg.level.upper()}] {str(msg.data)}"
154+
self.server_errlogs.append(log_msg)
151155

152156
if "url" in cfg:
153157
success, error_msg = await _quick_test_mcp_connection(cfg)
@@ -214,15 +218,24 @@ def logging_callback(msg: str) -> None:
214218
**cfg,
215219
)
216220

217-
def callback(msg: str) -> None:
221+
def callback(msg: str | mcp.types.LoggingMessageNotificationParams) -> None:
218222
# Handle MCP service error logs
219-
self.server_errlogs.append(msg)
223+
if isinstance(msg, mcp.types.LoggingMessageNotificationParams):
224+
if msg.level in (
225+
"warning",
226+
"error",
227+
"critical",
228+
"alert",
229+
"emergency",
230+
):
231+
log_msg = f"[{msg.level.upper()}] {str(msg.data)}"
232+
self.server_errlogs.append(log_msg)
220233

221234
stdio_transport = await self.exit_stack.enter_async_context(
222235
mcp.stdio_client(
223236
server_params,
224237
errlog=LogPipe(
225-
level=logging.ERROR,
238+
level=logging.INFO,
226239
logger=logger,
227240
identifier=f"MCPServer-{name}",
228241
callback=callback,

astrbot/core/provider/func_tool_manager.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121

2222
DEFAULT_MCP_CONFIG = {"mcpServers": {}}
2323

24-
DEFAULT_MCP_INIT_TIMEOUT_SECONDS = 20.0
25-
DEFAULT_ENABLE_MCP_TIMEOUT_SECONDS = 30.0
24+
DEFAULT_MCP_INIT_TIMEOUT_SECONDS = 180.0
25+
DEFAULT_ENABLE_MCP_TIMEOUT_SECONDS = 180.0
2626
MCP_INIT_TIMEOUT_ENV = "ASTRBOT_MCP_INIT_TIMEOUT"
2727
ENABLE_MCP_TIMEOUT_ENV = "ASTRBOT_MCP_ENABLE_TIMEOUT"
2828
MAX_MCP_TIMEOUT_SECONDS = 300.0
@@ -417,9 +417,11 @@ async def init_mcp_clients(
417417
for (name, cfg, _), result in zip(active_configs, results, strict=False):
418418
if isinstance(result, Exception):
419419
if isinstance(result, MCPInitTimeoutError):
420-
logger.error(f"MCP 服务 {name} 初始化超时({timeout_display}秒)")
420+
logger.error(
421+
f"Connected to MCP server {name} timeout ({timeout_display} seconds)"
422+
)
421423
else:
422-
logger.error(f"MCP 服务 {name} 初始化失败: {result}")
424+
logger.error(f"Failed to initialize MCP server {name}: {result}")
423425
self._log_safe_mcp_debug_config(cfg)
424426
failed_services.append(name)
425427
async with self._runtime_lock:
@@ -430,16 +432,18 @@ async def init_mcp_clients(
430432

431433
if failed_services:
432434
logger.warning(
433-
f"以下 MCP 服务初始化失败: {', '.join(failed_services)}"
434-
f"请检查配置文件 mcp_server.json 和服务器可用性。"
435+
f"The following MCP services failed to initialize: {', '.join(failed_services)}. "
436+
f"Please check the mcp_server.json file and server availability."
435437
)
436438

437439
summary = MCPInitSummary(
438440
total=len(active_configs), success=success_count, failed=failed_services
439441
)
440-
logger.info(f"MCP 服务初始化完成: {summary.success}/{summary.total} 成功")
442+
logger.info(
443+
f"MCP services initialization completed: {summary.success}/{summary.total} successful, {len(summary.failed)} failed."
444+
)
441445
if summary.total > 0 and summary.success == 0:
442-
msg = "全部 MCP 服务初始化失败,请检查 mcp_server.json 配置和服务器可用性。"
446+
msg = "All MCP services failed to initialize, please check the mcp_server.json and server availability."
443447
if raise_on_all_failed:
444448
raise MCPAllServicesFailedError(msg)
445449
logger.error(msg)
@@ -461,7 +465,7 @@ async def _start_mcp_server(
461465
async with self._runtime_lock:
462466
if name in self._mcp_server_runtime or name in self._mcp_starting:
463467
logger.warning(
464-
f"MCP 服务 {name} 已在运行,忽略本次启用请求(timeout={timeout:g})。"
468+
f"Connected to MCP server {name}, ignoring this startup request (timeout={timeout:g})."
465469
)
466470
self._log_safe_mcp_debug_config(cfg)
467471
return
@@ -478,10 +482,10 @@ async def _start_mcp_server(
478482
)
479483
except asyncio.TimeoutError as exc:
480484
raise MCPInitTimeoutError(
481-
f"MCP 服务 {name} 初始化超时({timeout:g} 秒)"
485+
f"Connected to MCP server {name} timeout ({timeout:g} seconds)"
482486
) from exc
483487
except Exception:
484-
logger.error(f"初始化 MCP 客户端 {name} 失败", exc_info=True)
488+
logger.error(f"Failed to initialize MCP client {name}", exc_info=True)
485489
raise
486490
finally:
487491
if mcp_client is None:
@@ -491,9 +495,9 @@ async def _start_mcp_server(
491495
async def lifecycle() -> None:
492496
try:
493497
await shutdown_event.wait()
494-
logger.info(f"收到 MCP 客户端 {name} 终止信号")
498+
logger.info(f"Received shutdown signal for MCP client {name}")
495499
except asyncio.CancelledError:
496-
logger.debug(f"MCP 客户端 {name} 任务被取消")
500+
logger.debug(f"MCP client {name} task was cancelled")
497501
raise
498502
finally:
499503
await self._terminate_mcp_client(name)
@@ -545,7 +549,7 @@ async def _shutdown_runtimes(
545549
if strict:
546550
raise MCPShutdownTimeoutError(pending_names, timeout)
547551
logger.warning(
548-
"MCP 服务关闭超时(%s 秒),以下服务未完全关闭:%s",
552+
"MCP server shutdown timeout (%s seconds), the following servers were not fully closed: %s",
549553
f"{timeout:g}",
550554
", ".join(pending_names),
551555
)
@@ -568,7 +572,9 @@ async def _cleanup_mcp_client_safely(
568572
try:
569573
await mcp_client.cleanup()
570574
except Exception as cleanup_exc: # noqa: BLE001 - only log here
571-
logger.error(f"清理 MCP 客户端资源 {name} 失败: {cleanup_exc}")
575+
logger.error(
576+
f"Failed to cleanup MCP client resources {name}: {cleanup_exc}"
577+
)
572578

573579
async def _init_mcp_client(self, name: str, config: dict) -> MCPClient:
574580
"""初始化单个MCP客户端"""
@@ -602,7 +608,7 @@ async def _init_mcp_client(self, name: str, config: dict) -> MCPClient:
602608
)
603609
self.func_list.append(func_tool)
604610

605-
logger.info(f"已连接 MCP 服务 {name}, Tools: {tool_names}")
611+
logger.info(f"Connected to MCP server {name}, Tools: {tool_names}")
606612
return mcp_client
607613

608614
async def _terminate_mcp_client(self, name: str) -> None:
@@ -622,7 +628,7 @@ async def _terminate_mcp_client(self, name: str) -> None:
622628
async with self._runtime_lock:
623629
self._mcp_server_runtime.pop(name, None)
624630
self._mcp_starting.discard(name)
625-
logger.info(f"已关闭 MCP 服务 {name}")
631+
logger.info(f"Disconnected from MCP server {name}")
626632
return
627633

628634
# Runtime missing but stale tools may still exist after failed flows.

astrbot/core/provider/manager.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def __init__(
7979
self._provider_change_hooks: list[
8080
Callable[[str, ProviderType, str | None], None]
8181
] = []
82+
self._mcp_init_task: asyncio.Task | None = None
8283

8384
def set_provider_change_callback(
8485
self,
@@ -330,24 +331,16 @@ async def initialize(self) -> None:
330331
if not self.curr_tts_provider_inst and self.tts_provider_insts:
331332
self.curr_tts_provider_inst = self.tts_provider_insts[0]
332333

333-
# 初始化 MCP Client 连接(等待完成以确保工具可用)
334-
strict_mcp_init = os.getenv("ASTRBOT_MCP_INIT_STRICT", "").strip().lower() in {
335-
"1",
336-
"true",
337-
"yes",
338-
"on",
339-
}
340-
mcp_init_summary = await self.llm_tools.init_mcp_clients(
341-
raise_on_all_failed=strict_mcp_init
342-
)
343-
if (
344-
mcp_init_summary.total > 0
345-
and mcp_init_summary.success == 0
346-
and not strict_mcp_init
347-
):
348-
logger.warning(
349-
"MCP 服务全部初始化失败,系统将继续启动(可设置 "
350-
"ASTRBOT_MCP_INIT_STRICT=1 以在此场景下中止启动)。"
334+
async def _init_mcp_clients_bg() -> None:
335+
try:
336+
await self.llm_tools.init_mcp_clients()
337+
except Exception:
338+
logger.error("MCP init background task failed", exc_info=True)
339+
340+
if self._mcp_init_task is None or self._mcp_init_task.done():
341+
self._mcp_init_task = asyncio.create_task(
342+
_init_mcp_clients_bg(),
343+
name="provider-manager:mcp-init",
351344
)
352345

353346
def dynamic_import_provider(self, type: str) -> None:
@@ -817,6 +810,13 @@ async def create_provider(self, new_config: dict) -> None:
817810
await self.load_provider(new_config)
818811

819812
async def terminate(self) -> None:
813+
if self._mcp_init_task and not self._mcp_init_task.done():
814+
self._mcp_init_task.cancel()
815+
try:
816+
await self._mcp_init_task
817+
except asyncio.CancelledError:
818+
pass
819+
820820
for provider_inst in self.provider_insts:
821821
if hasattr(provider_inst, "terminate"):
822822
await provider_inst.terminate() # type: ignore

0 commit comments

Comments
 (0)