RequestResponder.cancel sends JSON-RPC response, violating cancellation spec

该报错发生在 MCP Python SDK(1.x 版本)中,当客户端发送 notifications/cancelled 取消一个正在处理的请求(如 tools/call )时,服务端的 RequestResponder.cancel 会额外向对方发送一条 Request cancelled 错误

快速结论:该报错发生在 MCP Python SDK(1.x 版本)中,当客户端发送 notifications/cancelled 取消一个正在处理的请求(如 tools/call)时,服务端的 RequestResponder.cancel 会额外向对方发送一条 Request cancelled 错误响应,违反 MCP 取消规范( SHOULD NOT send a response for the cancelled request)。优先排查 SDK 版本是否为 1.x,并考虑升级到 2.x 或应用 monkey-patch 临时修复。

适用环境:mcp 1.27.0、Python 3.11、stdio transport。

最快修复方案:将 MCP Python SDK 升级到 v2.0.0 或更高版本(该版本已通过 #3188 修复,取消的请求不再返回任何响应)。如果无法升级,可在下游服务端 monkey-patch RequestResponder.cancel 屏蔽 _send_response 调用。

注意事项:1.x 分支目前仅接收关键修复,官方建议在 1.x 场景下使用 Issue 正文提供的 monkey-patch 作为临时方案;2.x 已修复该问题,但需验证你的项目与 2.x 的兼容性。

问题场景

用户通过 stdio transport 运行 MCP Python SDK 服务端,当客户端发起一个长时间运行的 tools/call(例如 await asyncio.sleep、自定义轮询工具、长数据库查询),随后发送 notifications/cancelled 取消该请求时,服务端会在取消后主动向客户端发送一条带有 Request cancelled 的 JSON-RPC 错误响应。严格的客户端(如 Claude Code)会将此响应识别为未知消息 ID,关闭传输并重新连接。

报错原文

{"jsonrpc": "2.0", "id": 99, "error": {"code": 0, "message": "Request cancelled"}}

Received a response for an unknown message ID

原因分析

RequestResponder.cancel 方法(mcp/shared/session.py 中的 cancel)在收到 notifications/cancelled 后,除取消 _cancel_scope 和设置 _completed = True 外,还会调用 _session._send_response 向请求方发送一条 ErrorData(code=0, message="Request cancelled") 响应。这违反了 MCP 规范中“接收方不应为已取消的请求发送响应”的明确要求(详见 cancellation.mdx 规范第 33-36 行)。

环境排查

  • 确认 MCP Python SDK 版本:在 1.x 分支(如 1.27.0)存在该行为;2.0.0 及更高版本已修复。
  • 确认 Python 版本(Issue 中验证为 3.11,其他版本可能同样受影响)。
  • 确认传输方式为 stdio transport(其他传输方式可能也会触发,但 Issue 未验证)。
  • 确认客户端是否为严格实现(如 Claude Code),会因未知消息 ID 断开传输。

解决步骤

  1. 首选方案:将 MCP Python SDK 升级到 v2.0.0 或更高版本——官方确认 #3188 已修复该问题,取消的请求不会再收到合成响应。
  2. 临时方案(1.x 环境):在下游服务端入口处应用 monkey-patch,覆盖 RequestResponder.cancel,移除 _send_response 调用:
from mcp.shared.session import RequestResponder

async def _cancel_without_response(self) -> None:
    if not self._entered:
        raise RuntimeError("RequestResponder must be used as a context manager")
    if not self._cancel_scope:
        raise RuntimeError("No active cancel scope")
    self._cancel_scope.cancel()
    self._completed = True

RequestResponder.cancel = _cancel_without_response
  1. 升级后仍需验证:确保发送 notifications/cancelled 后,服务端不再输出 Request cancelled 响应。

验证方法

在 stdio 传输下,按 Issue 复现步骤操作:发送 initialize + notifications/initialized,再发送一个等待中的 tools/call,然后发送 notifications/cancelled 并根据请求 ID 观察服务端 stdout——不再出现 {"jsonrpc": "2.0", "id": 99, "error": {"code": 0, "message": "Request cancelled"}} 即表示修复生效。若使用 Claude Code 作为客户端,应不再出现 Received a response for an unknown message ID 且不会触发传输重连。

参考来源

modelcontextprotocol/python-sdk #2480

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 18484

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注