快速结论:当使用 MCP Python SDK v2 的 JSONRPCDispatcher 处理返回 JSON-RPC 错误(如 -32602)的请求时,客户端生成 OpenTelemetry span 虽然请求本身已正确抛出 MCPError,但 span 状态仍停留在 UNSET,缺失 error.type、rpc.response.status_code 和异常事件,导致错误链路在监控中不可见。
适用环境:已在 modelcontextprotocol/python-sdk 的 main 分支提交 11934c90aeff5e1e68aee223edd00c5d1fce1d5c 上复现,涉及 v2 的 JSONRPCDispatcher、内存中运行的 MCP Server(lowlevel.server.Server)与 Client(mode="legacy"),并使用 OpenTelemetry SDK 的 InMemorySpanExporter 收集 span。Issue 中未声明具体操作系统、Python 或 CUDA 版本。
最快修复方案:暂无确认的一步修复方案。Issue 讨论中提出的方向是:在 send_raw_request() 中,于 span 上下文退出前检测 ErrorData,并在 span 上写入 error.type、rpc.response.status_code 以及 StatusCode.ERROR,与 server/_otel.py 的服务端做法保持一致。
注意事项:该方案在 Issue 关闭时仍处于提议阶段,尚无合并的补丁可供直接升级;不要将其视为已验证的官方修复。此外,问题的范围仅限 JSON-RPC/stream-backed dispatcher 调用,不涉及直接的面内 dispatch,也未涉及实际部署的传输层、收集器或生产频率。
问题场景
你在使用 MCP Python SDK v2,并打开了客户端 OpenTelemetry 埋点。当 MCP Server 端对某个资源类请求(如 resources/list)返回 JSON-RPC 错误响应时,客户端的 Client / session.list_resources() 调用会正确抛出 MCPError,但通过 InMemorySpanExporter 或任意 OTel 后端观察到的 CLIENT span 仍然是一个正常的退出状态,无法与成功的控制请求区分。这通常发生在排查可观测性、审计合规或错误告警覆盖时被发现。
报错原文
[v2] JSON-RPC error responses leave client OpenTelemetry spans UNSET
METHOD=resources/list CONTROL=UNSET RED_CODE=-32602 RED_MESSAGE='forced failure' RED_STATUS=UNSET ERROR_TYPE=None RPC_STATUS=None EVENTS=0
复现时每次运行都产生上述输出:成功对照 span 与错误 span 的状态都是 UNSET,错误 span 没有 error.type、没有 rpc.response.status_code,也没有异常事件。
原因分析
根据 Issue 正文以及后续讨论确认的根因:在 send_raw_request()(src/mcp/shared/jsonrpc_dispatcher.py,提交 11934c90 的第 368–434 行)中,客户端 span 在收到 outcome 之后先退出上下文,然后才在第 432–433 行把 ErrorData 转换为 MCPError。span 的上下文管理器因此观察不到任何异常,也就不会把状态设置为 ERROR,也不会附加语义约定属性。
这与服务端中间件 server/_otel.py 的现有做法不一致,后者会记录错误属性和状态;也与 #2381 中“被传播的 MCPError 应由客户端 span 记录”的设计假设相矛盾。
环境排查
- 确认 MCP Python SDK 是否为当前
main分支(Issue 报告基于11934c90aeff5e1e68aee223edd00c5d1fce1d5c),v1 的BaseSession架构不在此问题范围。 - 确认使用的是 v2 的
JSONRPCDispatcher,且为 JSON-RPC/stream-backed dispatcher 调用,而不是直接的面内 dispatch。 - 确认客户端已配置 OpenTelemetry SDK,并使用可导出/可查询的 span exporter(Issue 复现使用
InMemorySpanExporter+SimpleSpanProcessor)。 - 确认 OTel 版本与
SpanKind.CLIENT过滤方式,便于正确抓取对应客户端 span。 - Issue 未声明具体 Python、CUDA、显卡、模型或部署传输环境,排查时无需据此补写版本号。
解决步骤
- 先按 Issue 提供的复现思路,在内存中构造一对成功/失败请求(例如
resources/list),用InMemorySpanExporter分别导出两次的 CLIENT span,确认失败 span 的status为UNSET、无error.type且无异常事件。 - 定位
send_raw_request()内 span 的进入与退出位置,确认ErrorData到MCPError的转换发生在 span 退出之后。 - 可优先尝试的修复:在 span 上下文内收到
outcome后,若判定为ErrorData,就在退出前把error.type、rpc.response.status_code和StatusCode.ERROR写入 span,参照server/_otel.py:35-60的服务端模式;另一种等效做法是让ErrorData到MCPError的转换发生在活跃的客户端 span 内。 - 为修复补上内存回归测试,覆盖成功与 JSON-RPC 错误两种响应,确保失败 span 与成功对照 span 可区分。
- 将改动范围限制在 v2 的
JSONRPCDispatcher,并运行针对性测试、Ruff 和 Pyright。
验证方法
再次运行同一个内存复现:失败请求仍应抛出 MCPError;对应的 CLIENT span 状态应为 ERROR,包含 error.type,并携带 JSON-RPC 错误码(复现中为 -32602)/ rpc.response.status_code,与成功对照 span 明显区分。这可作为该 Issue 的验收标准。
参考来源
modelcontextprotocol/python-sdk #3174
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。
![[v2] Allow MCPServer to disable subscriptions/listen](https://www.chat-gpts.plus/wp-content/uploads/2026/10/3357-b850be43-768x403.jpg)
![[bug]: Still Crashing when i "Invoke" ROCm 7.1](https://www.chat-gpts.plus/wp-content/uploads/2026/10/9374-edd5f33b-768x403.jpg)
![[Bug]: aws_bedrock_project_id sent as wrong header ("anthropic-workspace") to Bedrock Mantle — should be "anthropic-workspace-id"](https://www.chat-gpts.plus/wp-content/uploads/2026/10/31947-aedb8605-768x403.jpg)