modelcontextprotocol / modelcontextprotocol/python-sdk

Streamable HTTP clean EOF reconnects can exceed the request retry budget

未關閉 適合新手
#3,307 5 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

bug needs confirmation P2 v1 v2
主要語言
Python
星號
24.3k
分支
4k
平均合併
1 天 1 小時
30 天內合併 PR
31

描述

Description

The streamable HTTP client can exceed its per-request SSE reconnection budget when each reconnect opens successfully, emits only an id-bearing priming event, and then reaches EOF without a JSON-RPC response.

In that case _handle_reconnection() currently recurses with attempt=0 after the clean EOF path. The exception path increments the counter, but the normal EOF-without-response path resets it, so a no-timeout request such as subscriptions/listen can keep reconnecting instead of resolving the waiter with CONNECTION_CLOSED after MAX_RECONNECTION_ATTEMPTS.

Reproduction

Drive StreamableHTTPTransport._handle_reconnection() with a mock HTTP transport that returns these per-request SSE responses:

  1. Reconnect 1: id: evt-1 with empty data, then EOF.
  2. Reconnect 2: id: evt-2 with empty data, then EOF.
  3. Reconnect 3: a JSON-RPC success response.

Starting from Last-Event-ID: evt-0 and retry_interval_ms=0, current main makes the third HTTP request and delivers the success response. I expected the client to stop after the two configured reconnect attempts, emit a JSONRPCError for the original request with CONNECTION_CLOSED, and only send Last-Event-ID: evt-0 and Last-Event-ID: evt-1.

Expected Behavior

Each per-request reconnect that reaches EOF without delivering a JSON-RPC response should consume the reconnect budget. That keeps request-scoped SSE drops consistent whether they end by transport exception or by clean EOF, and prevents no-timeout callers from staying parked forever when a server repeatedly closes resumable streams without producing the response.

Local Verification

I have a local regression test that fails on current main because the third reconnect response is accepted, then passes when the clean EOF path recurses with attempt + 1.

Commands run locally:

  • uv run --frozen pytest tests/client/test_streamable_http.py::test_empty_resumable_sse_reconnects_count_toward_the_request_budget -q
  • uv run --frozen pytest tests/client/test_streamable_http.py -q
  • uv run --frozen ruff check src/mcp/client/streamable_http.py tests/client/test_streamable_http.py
  • uv run --frozen ruff format --check src/mcp/client/streamable_http.py tests/client/test_streamable_http.py
  • uv run --frozen pyright src/mcp/client/streamable_http.py tests/client/test_streamable_http.py
  • UV_FROZEN=1 uv run --frozen strict-no-cover

Disclosure: I used AI assistance to help prepare this report and a local patch; I reviewed the reproduction, root cause, and test results.

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

先從 src/mcp/client/streamable_http.py 中的 StreamableHTTPTransport._handle_reconnection() 開始,然後閱讀 tests/client/test_streamable_http.py 中的 test_empty_resumable_sse_reconnects_count_toward_the_request_budget。首先執行該回歸測試和完整的 streamable HTTP 測試檔案。完成的標準是:沒有 JSON-RPC 回應的乾淨 EOF 會消耗請求重新連線預算,並使用 CONNECTION_CLOSED 解析 waiter。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
python
領域
api, networking
Issue 類型
缺陷
難度
2/5
預估耗時
1-3 小時
活躍度
活躍
描述清晰度
描述清楚
新手友好度
88/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。