
streamable_http: early response.aclose() poisons keepalive connection, causes ~260ms latency on every subsequent tool call
快速结论:此问题发生在使用 MCP Python SDK 的 streamable HTTP 传输模式,客户端在收到首个 SSE 事件后过早调用 response.aclose() 关闭响应流,导致 HTTP/1.1 keepalive 连接被“污染”,后续每次请求额外增加约 260ms 延迟。优先排查是否调用了 aclose() 提前关闭连接。
问题场景
用户在使用 MCP Python SDK (mcp==1.27.1) 的 streamable_http 传输模式时,通过 ClientSession.call_tool() 或 send_ping、list_tools 等调用,在 Windows 11 环境下发现问题。每次串行请求在单一连接上固定增加约 260ms 延迟,而使用裸 httpx.AsyncClient 只消耗约 5ms。
需要注意:该问题在部分环境(如 Linux 下的 uvicorn+sse-starlette)无法复现,可能取决于底层 HTTP 服务器实现。
报错原文
# Symptom: sequential call_tool() requests average ~265ms with early aclose()
# After removing early aclose(), avg drops to ~7ms
# Measured latency from: session.call_tool(TOOL_NAME, TOOL_ARGS)
原因分析
根本原因在于 src/mcp/client/streamable_http.py 中的 _handle_sse_response 方法,它在接收到第一个 JSON-RPC 响应事件后立即调用 await response.aclose(),强制关闭 SSE 流而未能等到 EOF。这导致该 HTTP/1.1 keepalive 连接在放回连接池时处于“未完全排空”状态。后续复用该连接的 POST 请求会在服务器发送状态码之前阻塞约 260ms(可能是服务器端 SSE 空闲/重连窗口所致——sse_starlette.EventSourceResponse 在发送唯一事件后仍会保持写入器任务存活)。
代码中一共存在三个类似的提前关闭站点:
_handle_sse_response(第 364 行附近)_handle_resumption_request(第 251 行附近)_handle_reconnection(第 421 行附近)
环境排查
- 确认 MCP Python SDK 版本(mcp==1.27.1,但问题可能在多个版本存在)
- 确认 Python 版本(用户使用 3.12.8)
- 确认操作系统(Windows 11 上可复现,Linux 上部分环境不可复现)
- 确认服务器端实现(用户使用
mcp.server.streamable_http,其他服务器实现也可能受影响) - 确认使用
httpx.AsyncClient且启用 keepalive
解决步骤
- (可优先尝试)移除
aclose(),改为排空流至 EOF。在_handle_sse_response中,当_handle_sse_event()返回 complete 时,设置本地标记saw_terminal_event = True,继续迭代直到服务器自然关闭流,而不是立即aclose()。 - 仅在取消、显式关闭会话或超时/错误路径时使用
aclose()。保持正常完成路径排空至 EOF,以便连接能正常返回 keepalive 池。 - 对
_handle_resumption_request和_handle_reconnection采用相同策略。PR #2712 覆盖了所有三个提前关闭站点。 - 修复后添加回归测试:通过同一
httpx.AsyncClient执行多次串行call_tool(),验证预热后每次调用保持在个位数毫秒级别,无固定每次调用延迟。 - 添加取消测试:确保修复后不会导致挂起的 SSE 流无法关闭,即取消/超时路径仍正常关闭连接。
验证方法
运行用户提供的复现脚本(参见 Issue 正文),比较修复前后的平均延迟:
- 修复前:avg ≈ 265 ms
- 修复后:avg ≈ 7 ms(37 倍加速)
注意:该验证方法在部分环境(Linux + uvicorn + sse-starlette)可能无法观察到 260ms 差异(已测试仅 ~7ms),但仍应确保代码修改不会引入新的取消/超时相关问题。



