MCP 空服务报 KeyError
MCP 空服务通过 ClientSessionGroup 连接报 KeyError,或单独断开未关闭 transport。区分外部 session 与组内连接,核对空目录记账和当前版本边界。
MCP 服务没有注册工具、资源或 prompt,单独连接没有问题,交给 ClientSessionGroup.connect_with_session() 却报原始 KeyError。如果堆栈指向 _aggregate_components 中删除 _session_exit_stacks[session] 的位置,应检查空组件分支的连接记账。
这类服务不一定无效:它可以只使用 sampling 或 roots。问题已有 SDK 2.1.0、1.29.1 和后续开发版本反馈;已核对的正式版 2.3.0 仍保留相关删除分支,不能把重复问题被关闭理解成故障已经修复。
两个连接入口表现不同
聚合逻辑取得的 prompts、resources 和 tools 都为空时,会直接删除这个 session 的 exit stack 记录。
connect_with_session() 接收的是调用方已创建的 session。组内未必为它登记过 exit stack,直接删除就会抛 KeyError。处理时不能仅捕获异常后继续宣称连接成功,因为聚合过程并没有按预期完成。
connect_to_server() 则先创建并登记自己的 exit stack。同一删除操作不会立即报错,却会忘掉记账项,而且没有关闭栈。后续代码仍把 session 登记为已连接,单独调用 disconnect_from_server() 时又找不到要关闭的栈,transport 因此留到整个 group 退出才清理。
所以,调用不报错并不代表这条路径安全;两种入口需要分别核对。
空目录与列举失败不要混为一谈
这一分支既可能来自合法的空目录,也可能来自三个列举请求都失败。既有代码会捕获相应 MCPError 并留下空字典,应先查看真实的列举结果和警告。
若失败原因是远端未声明某个可选能力,按能力声明与 Method not found 的检查处理。若是认证、权限或网络失败,也不能为了避免 KeyError 把它们当成“服务只是没有工具”。
同样,给服务器添加无意义的 dummy 工具,只是让空分支暂时不触发,还会改变对客户端公开的能力,不宜作为修复。
不需要聚合时,保留原连接拥有者
如果应用只需要单独使用这个服务,不需要把目录合并进 group,可以让原有的 Client 或 session 上下文继续管理完整连接生命周期,直接使用需要的能力,避开不必要的空目录聚合。
这是应用组织方式的绕行,不能称作 SDK 已发布的修复。原先的认证、回调和关闭责任仍要保留;外部传入的 session 也不能因为被 group 登记,就擅自认定关闭权已经转移。
确实需要聚合时,应对实际使用版本审查补丁:合法空服务保留正常记账,或者明确关闭并移除整个 session。不能只删除记录,也不能用忽略 KeyError 来留下半完成状态。当前已核对的 2.3.0 没有提供可直接指定的正式修复版本。
这个故障也不是跨 Task 退出 cancel scope 的问题。即使进入和退出都由同一个 Task 完成,空目录删除分支仍可能失败;若异常是 cancel scope 的任务归属,则按连接拥有者与关闭顺序另查。
分别验收登记与关闭
在获准的测试环境中,确认合法空服务能被正确处理,有组件的服务仍能完成聚合。外部 session 保留调用方的关闭责任;group 自建的 session 则应在单独断开时真正关闭对应 transport,不能只检查它从组件列表中消失。
最后检查整个 group 退出和列举失败路径。这个缺陷使单独断开失去关闭入口,不代表所有连接都会永久泄漏;原有整体退出栈仍可能负责最终清理,判断时应保留这层区别。


