modelcontextprotocol / modelcontextprotocol/python-sdk

1.x: streamable_http client leaves a request unresolved when the SSE response stream ends without a response (fixed on 2.x by #3047 — backport request for v1.x)

Đang mở
#3,441 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

v1
Ngôn ngữ chính
Python
Star
24.3k
Fork
4k
Merge trung bình
1 ngày 1 giờ
Pull request đã merge (30 ngày)
31

Mô tả

Summary

On the 1.x line (v1.29.0, v1.29.1, and the v1.x branch as of 92120b4b), the Streamable HTTP client leaves a pending request unresolved forever when the text/event-stream response for that request ends without a response event. The caller learns of the disconnect only when its own deadline fires, so a dead server is indistinguishable from a slow one — and it is reported as a timeout, not a disconnect.

This is already fixed on 2.x: #3047 (merged 2026-07-07, shipped in v2.0.0) added StreamableHTTPTransport._resolve_abandoned_request(...) and calls it at exactly the two sites below with "SSE stream ended without a response" and "SSE stream ended and reconnection attempts were exhausted". The v1.x branch does not carry it (grep -c _resolve_abandoned_request src/mcp/client/streamable_http.py → 0 at 92120b4b), and neither did the v1.29.1 release (2026-08-24).

Ask: backport the _resolve_abandoned_request resolution from #3047 onto v1.x and cut a 1.29.x/1.30 release. Consumers pinned to mcp<2 (e.g. IBM/mcp-context-forge, mcp>=1.28.1,<2 at v1.0.8/v1.0.9/main) cannot pick the fix up from 2.x.

Mechanism (src/mcp/client/streamable_http.py @ v1.29.0 == v1.29.1, sha256 8eb01c3d…)
  1. _handle_sse_response (:397-435): a completed response returns inside the try. When the server dies mid-stream, EventSource.aiter_sse() raises httpx.RemoteProtocolError("peer closed connection without sending complete message body (incomplete chunked read)"), which except Exception as e: # pragma: no cover (:429-430) logs at DEBUG (SSE stream ended: …). The tail (:432-435) reconnects only if last_event_id is not None; a server that sends no event ids (FastMCP's default, no event store) makes the coroutine return without writing anything to ctx.read_stream_writer. ClientSession.send_request therefore waits forever on its response stream.
  2. _handle_reconnection (:446-448) gives up after MAX_RECONNECTION_ATTEMPTS with a bare return — the same unresolved-request hang for servers that do send ids.

The SDK already has the channel to resolve this: _send_session_terminated_error (:510-522) writes a JSONRPCError for a request id to read_stream_writer, and _receive_loop routes it to the pending request, where send_request raises McpError.

Reproduction (measured 2026-09-04, mcp 1.29.0, httpx 0.28.1, anyio 4.14.2, Python 3.12.13)

Server: a FastMCP 3.x Streamable HTTP server with a tool slow(seconds) that sleeps. Client: streamablehttp_client + ClientSession, initialize(), then

with anyio.fail_after(5):
    await session.call_tool("slow", {"seconds": 4})

and SIGKILL the server ≈1.0 s after the call starts (response headers arrive at ~0.002 s; no event before the kill).

Leaf exception elapsed
v1.29.0 as shipped builtins.TimeoutError (str='') — the caller's own fail_after 5.008 s
v1.29.0 + _resolve_abandoned_request backported in memory (same two sites, same messages, code=CONNECTION_CLOSED) mcp.shared.exceptions.McpError: SSE stream ended without a response (httpx.RemoteProtocolError: peer closed connection without sending complete message body (incomplete chunked read)) 1.06–1.14 s

The SDK's own debug log confirms the swallow: SSE stream ended: peer closed connection without sending complete message body (incomplete chunked read) at +1.11 s, then silence until the caller's deadline.

Related
  • #3047 — the 2.x fix (_resolve_abandoned_request), part of the subscriptions/listen driver PR.
  • #1401 — ClientSession error handling (broader; this is one concrete instance on 1.x).
  • Downstream consumer report: IBM/mcp-context-forge#6592 (a gateway on mcp<2 that renders this hang as Tool invocation timed out after {tool_timeout}s).

We are carrying a build-time backport of the two-site resolution in our ContextForge image until a 1.x release ships it; happy to open the backport PR against v1.x if maintainers want it.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu trong src/mcp/client/streamable_http.py bằng cách đọc _handle_sse_response, _handle_reconnection và _send_session_terminated_error; so sánh cách giải quyết tương ứng trong 2.x từ #3047. Tái hiện việc ngắt kết nối SSE không có phản hồi và xác minh rằng các yêu cầu đang chờ nhận được McpError dự kiến một cách kịp thời, sau đó chạy các bài kiểm thử client liên quan và xác nhận rằng bản backport cho v1.x đã sẵn sàng để phát hành.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
api
Loại issue
Lỗi
Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
72/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.