Initialize call hangs forever if MCP server does not return a `Content-Type: text/plain`

当 MCP 服务器对 initialize 请求返回的不是 text/event-stream (例如 text/plain )时,MCP Python SDK 客户端会永久卡在 ClientSession.initialize() 上,报错 Initialize call hangs foreve

快速结论:当 MCP 服务器对 initialize 请求返回的不是 text/event-stream(例如 text/plain)时,MCP Python SDK 客户端会永久卡在 ClientSession.initialize() 上,报错 Initialize call hangs forever if MCP server does not return a `Content-Type: text/plain`。优先排查服务器 initialize 响应的 Content-Type,以及所使用的 SDK 版本是否已包含修复。

适用环境:Issue 已确认:Python 3.12.12,MCP Python SDK 1.27.0(即 v1.x 分支问题场景),客户端使用 mcp.client.streamable_http.streamable_http_client;已确认 v1.x 受影响,main 分支在 commit 7c7c13b(PR #2005)修复。未提及具体操作系统、CUDA、显卡等信息,故不补充。

最快修复方案:暂无确认的一步修复方案。Issue 中明确给出的 workaround 是在调用外层加 asyncio.timeout 或 httpx 超时,以避免无限挂起;根因修复已在 main 分支 commit 7c7c13b 合入,可关注包含该提交的发布版本。

注意事项:超时只能让调用“失败退出”,不能解决服务器返回非法 Content-Type 的根本问题,仍需服务器端修正响应。修复版本号在 Issue 中未明确给出,请以实际发布的版本说明为准;v1.x 与 main 行为不同(v1.x 挂起,main 抛出 TaskGroup 异常),不要混用结论。

问题场景

用户通过 MCP Python SDK 的 streamable_http_client 连接 MCP 服务器,并在 ClientSession 中执行 await session.initialize()。当服务器被错误配置、对 initialize 请求返回的 Content-Type 不是 text/event-stream(Issue 中示例为 text/plain,返回体也不是合法 JSON-RPC)时,客户端不会立即报错,而是一直等待该请求 ID 对应的响应。

报错原文

Initialize call hangs forever if MCP server does not return a `Content-Type: text/plain`
Unexpected content type: text/plain

原因分析

最可能的原因是客户端在遇到非预期 Content-Type 时的错误传递路径有缺陷。Issue 评论指出了根因:v1.x 中 _handle_unexpected_content_type 会向读流发送一个裸的 ValueError,而不是携带对应请求 ID 的 JSONRPCError;而 ClientSession.initialize() 只等待与其请求 ID 匹配的响应,裸异常无法被匹配,因此调用一直阻塞。

main 分支在 commit 7c7c13b(PR #2005)中的修复做法是:构造带请求 ID 的 JSONRPCError(ErrorData(code=INVALID_REQUEST, message=f"Unexpected content type: {content_type}"))并发送到读流,使会话能解析并结束等待。

环境排查

  • 确认 Python 版本(Issue 为 3.12.12)。
  • 确认 MCP Python SDK 版本(Issue 为 1.27.0),并确认是否包含 commit 7c7c13b / PR #2005 的修复。
  • 确认客户端使用的是 mcp.client.streamable_http.streamable_http_client。
  • 确认 MCP 服务器端 initialize 响应返回的 Content-Type 是否为 text/event-stream,响应体是否为合法 JSON-RPC。
  • 确认调用是否位于 asyncio.timeout 或 httpx 超时保护之内(用于区分“真的挂起”还是“超时退出”)。

解决步骤

  1. 先定位触发条件:在客户端调用 initialize 处加日志或抓包,确认服务器返回的 Content-Type 与响应体内容。
  2. 若服务器返回 text/plain 等非 text/event-stream 类型,先修正服务器端 initialize 响应,使其符合 MCP 要求。
  3. 若暂时无法修正服务器,按 Issue 建议的 workaround,在调用外层设置 asyncio.timeout 或 httpx 超时,使挂起变为可观测的超时失败。
  4. 升级到包含 commit 7c7c13b(PR #2005)修复的 SDK 版本,使客户端在遇到非预期 Content-Type 时立即抛出可理解错误,而非无限挂起。若无法确认具体发布版本号,请以发布说明或仓库分支状态为准。
  5. 如果上述步骤都排除后仍表现异常,可参照 Issue 中的 repro 脚本与 code path,核对当前安装的 streamable_http.py 是否仍使用“裸异常”分支。

验证方法

使用 Issue 中的复现方式:让服务器对 initialize 返回 Content-Type: text/plain。修复前后确认行为差异——修复后客户端应在 5 秒超时内直接报错(main 表现为 TaskGroup 异常而非挂起),不再卡在 initialize 无限等待。若日志中不再出现无限等待、而是能收到与请求 ID 对应的错误响应,即可确认问题已解决。

参考来源

modelcontextprotocol/python-sdk #2432

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 29194

发表回复

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