"""MCP stdio 子进程生命周期管理。 全局单例,维护 server_id → TransportProtocol 的映射, 前端通过 API 控制 start/stop/restart/status。 """ from __future__ import annotations import logging from typing import Any from app.integrations.mcp.transports.base import TransportProtocol from app.integrations.mcp.transports.http import StreamableHttpTransport logger = logging.getLogger(__name__) # 模块级全局实例(避免依赖 request.app.state) _default_instance: ProcessManager | None = None def get_process_manager() -> ProcessManager | None: """返回默认的全局 ProcessManager 实例。""" return _default_instance def set_process_manager(manager: ProcessManager) -> None: """设置全局 ProcessManager 实例(由 lifespan 调用)。""" global _default_instance _default_instance = manager class ProcessManager: """MCP 进程管理器(全局单例)。""" def __init__(self) -> None: self._transports: dict[str, TransportProtocol] = {} def register_transport(self, server_id: str, transport: TransportProtocol) -> None: """注册传输层实例(不自动 start)。""" self._transports[server_id] = transport def unregister(self, server_id: str) -> None: """注销传输层实例(不自动 stop,调用方需先 stop)。""" self._transports.pop(server_id, None) def get(self, server_id: str) -> TransportProtocol | None: return self._transports.get(server_id) async def start(self, server_id: str) -> None: transport = self._transports.get(server_id) if transport is None: raise ValueError(f"server {server_id} 未注册") if isinstance(transport, StreamableHttpTransport): raise ValueError("streamable-http 无需启动") await transport.start() async def stop(self, server_id: str) -> None: transport = self._transports.get(server_id) if transport is None: raise ValueError(f"server {server_id} 未注册") if isinstance(transport, StreamableHttpTransport): raise ValueError("streamable-http 无需停止") await transport.stop() async def restart(self, server_id: str) -> None: transport = self._transports.get(server_id) if transport is None: raise ValueError(f"server {server_id} 未注册") if isinstance(transport, StreamableHttpTransport): raise ValueError("streamable-http 无需重启") await transport.restart() async def status(self, server_id: str) -> dict[str, Any]: transport = self._transports.get(server_id) if transport is None: return { "server_id": server_id, "name": "unknown", "transport": "unknown", "status": "unregistered", "pid": None, "uptime_seconds": None, } return await transport.get_status() async def shutdown_all(self) -> None: """停止所有 stdio 子进程(lifespan 关闭时调用)。""" for sid, transport in list(self._transports.items()): try: await transport.stop() except Exception: # noqa: BLE001 logger.warning( "停止 MCP 进程失败", extra={"server_id": sid}, ) self._transports.clear()