快速结论:当 MCP Python SDK 的 Streamable HTTP 客户端在每次重连时只收到一条带 id 的 priming 事件、随后干净地 EOF 而没有 JSON-RPC 响应时,会触发 Streamable HTTP clean EOF reconnects can exceed the request retry budget,表现为无超时的请求(如 subscriptions/listen)一直重连、永远不返回。优先排查 _handle_reconnection() 的 clean EOF 分支是否把重连计数重置为 0。
适用环境:Issue 中确认的环境为 MCP Python SDK(src/mcp/client/streamable_http.py)、使用 uv/pytest 进行本地验证;Issue 未提供操作系统、Python、CUDA、显卡或具体依赖版本信息。
最快修复方案:暂无确认的一步修复方案。Issue 已被标记为与 #2393 重复并关闭,维护者尚未在此 Issue 中确认最终修复;可优先尝试的验证方向是把 clean EOF 分支的重连计数从重置改为递增(或仅在本次重连真正收到 data 非空事件时才重置预算)。
注意事项:最直接的“clean EOF 一律递增”写法可能破坏需要跨重连重置预算的正常场景(例如一次长生命周期工具调用中有 3 次有意的流关闭);若采用,需要保留“有真实进展才重置预算”的逻辑。以上修复思路来自 Issue 讨论中的本地补丁与 PR 提案,未在 Issue 中标记为已合并。
问题场景
在使用 MCP Python SDK 的 Streamable HTTP 客户端时,服务端反复以可恢复 SSE 流的形式重开连接:每次只发送一条仅带 id 的 priming 事件、data 为空,然后干净地 EOF,始终不发送 JSON-RPC 响应。此时一个没有超时的请求(例如 subscriptions/listen)会不断重连,而不是在达到 MAX_RECONNECTION_ATTEMPTS 后以 CONNECTION_CLOSED 结束等待。
报错原文
Streamable HTTP clean EOF reconnects can exceed the request retry budget
原因分析
问题定位在 src/mcp/client/streamable_http.py 的 _handle_reconnection():异常路径会以 attempt + 1 递增重连计数,但“干净 EOF 且没有收到响应”的路径却以 attempt=0 递归,等于每次重连都把请求级的重连预算清零。因此只要服务端持续重开可恢复流、只发一条仅带 id 的 priming 事件就 EOF,客户端就会无限重连,永远无法用 CONNECTION_CLOSED 结束等待。Issue 中的复现从 Last-Event-ID: evt-0 与 retry_interval_ms=0 开始,预期在两次配置的重连尝试后停止,只发送 Last-Event-ID: evt-0 和 Last-Event-ID: evt-1。
环境排查
- 确认使用的是 MCP Python SDK 的 Streamable HTTP 客户端,且调用的是
src/mcp/client/streamable_http.py中的重连逻辑。 - 确认服务端行为:每次重连是否返回带 id 但
data为空的 priming 事件后立即 EOF,且不返回 JSON-RPC 响应。 - 确认请求是否为无超时请求(如
subscriptions/listen),此类调用更容易长期挂起而不报错。 - Issue 未提供操作系统、Python、PyTorch、CUDA、显卡及依赖版本等环境细节,排查时请结合自身环境记录。
解决步骤
- 按 Issue 复现方式,用 mock HTTP transport 驱动
StreamableHTTPTransport._handle_reconnection():第 1 次重连返回id: evt-1加空data后 EOF,第 2 次返回id: evt-2加空data后 EOF,第 3 次返回 JSON-RPC 成功响应;从Last-Event-ID: evt-0、retry_interval_ms=0开始。 - 观察当前代码是否在第 3 次请求中接受了成功响应;如果是,说明重连预算被错误重置。
- 检查
_handle_reconnection()的 clean EOF 分支:当前实现为await self._handle_reconnection(ctx, reconnect_last_event_id, reconnect_retry_ms, 0),与异常分支的attempt + 1不一致。 - 可优先尝试的修复方向:让 clean EOF 且未交付 JSON-RPC 响应的重连消耗重连预算,即在 clean EOF 分支使用
attempt + 1;注意该写法可能影响依赖跨重连重置预算的测试。 - 若上述写法导致
tests/shared/test_streamable_http.py::test_streamable_http_multiple_reconnections失败,可改为“进度感知”:只有本次重连真正收到data非空的事件(通知、响应或错误)才重置预算,否则递增计数,与异常路径保持一致。 - 修改后运行相关测试:
uv run pytest tests/client/test_streamable_http.py、uv run pytest tests/shared/test_streamable_http.py::test_streamable_http_multiple_reconnections,并跑完整测试套件确认无回归。
验证方法
用上述 mock transport 场景验证:修复后客户端应在恰好 MAX_RECONNECTION_ATTEMPTS 次重连后放弃,并以 CONNECTION_CLOSED 结束原始请求的等待,不再发送第 3 次请求。Issue 中报告的本地回归测试在修复前失败(挂起/超时)、修复后通过;同时 tests/client/test_streamable_http.py 全部通过,test_streamable_http_multiple_reconnections 不回归。
参考来源
modelcontextprotocol/python-sdk #3307
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


