@@ -65,6 +65,22 @@ class _MCPServerRuntime:
6565 lifecycle_task : asyncio .Task [None ]
6666
6767
68+ class _MCPClientDictView (Mapping [str , MCPClient ]):
69+ """Read-only view of MCP clients derived from runtime state."""
70+
71+ def __init__ (self , runtime : dict [str , _MCPServerRuntime ]) -> None :
72+ self ._runtime = runtime
73+
74+ def __getitem__ (self , key : str ) -> MCPClient :
75+ return self ._runtime [key ].client
76+
77+ def __iter__ (self ):
78+ return iter (self ._runtime )
79+
80+ def __len__ (self ) -> int :
81+ return len (self ._runtime )
82+
83+
6884def _resolve_timeout (
6985 timeout : float | int | str | None = None ,
7086 * ,
@@ -194,6 +210,7 @@ def __init__(self) -> None:
194210 self ._mcp_server_runtime : dict [str , _MCPServerRuntime ] = {}
195211 """MCP 服务运行时状态(唯一事实来源)"""
196212 self ._mcp_server_runtime_view = MappingProxyType (self ._mcp_server_runtime )
213+ self ._mcp_client_dict_view = _MCPClientDictView (self ._mcp_server_runtime )
197214 self ._timeout_mismatch_warned = False
198215 self ._timeout_warn_lock = threading .Lock ()
199216 self ._runtime_lock = asyncio .Lock ()
@@ -219,9 +236,7 @@ def mcp_client_dict(self) -> Mapping[str, MCPClient]:
219236
220237 Note: Mutating this mapping is unsupported and will raise TypeError.
221238 """
222- return MappingProxyType (
223- {name : runtime .client for name , runtime in self ._mcp_server_runtime .items ()}
224- )
239+ return self ._mcp_client_dict_view
225240
226241 @property
227242 def mcp_server_runtime_view (self ) -> Mapping [str , _MCPServerRuntime ]:
@@ -596,22 +611,18 @@ async def _terminate_mcp_client(self, name: str) -> None:
596611 runtime = self ._mcp_server_runtime .get (name )
597612 if runtime :
598613 client = runtime .client
599- try :
600- # 关闭MCP连接
601- await client .cleanup ()
602- except Exception as e :
603- logger .error (f"清空 MCP 客户端资源 { name } : { e } 。" )
604- finally :
605- # 移除关联的FuncTool
606- self .func_list = [
607- f
608- for f in self .func_list
609- if not (isinstance (f , MCPTool ) and f .mcp_server_name == name )
610- ]
611- async with self ._runtime_lock :
612- self ._mcp_server_runtime .pop (name , None )
613- self ._mcp_starting .discard (name )
614- logger .info (f"已关闭 MCP 服务 { name } " )
614+ # 关闭MCP连接
615+ await self ._cleanup_mcp_client_safely (client , name )
616+ # 移除关联的FuncTool
617+ self .func_list = [
618+ f
619+ for f in self .func_list
620+ if not (isinstance (f , MCPTool ) and f .mcp_server_name == name )
621+ ]
622+ async with self ._runtime_lock :
623+ self ._mcp_server_runtime .pop (name , None )
624+ self ._mcp_starting .discard (name )
625+ logger .info (f"已关闭 MCP 服务 { name } " )
615626 return
616627
617628 # Runtime missing but stale tools may still exist after failed flows.
0 commit comments