modelcontextprotocol / modelcontextprotocol/python-sdk

Streamable HTTP clean EOF reconnects can exceed the request retry budget

Ouverte Adaptée aux débutants
#3,307 5 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

bug needs confirmation P2 v1 v2
Langage dominant
Python
Étoiles
24.3k
Forks
4k
Merge moyen
1 j 1 h
PR mergées (30 j)
31

Description

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.

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par StreamableHTTPTransport._handle_reconnection() dans src/mcp/client/streamable_http.py, puis lisez test_empty_resumable_sse_reconnects_count_toward_the_request_budget dans tests/client/test_streamable_http.py. Exécutez d’abord ce test de régression et le fichier de tests streamable HTTP complet. C’est terminé lorsque des EOF propres sans réponse JSON-RPC consomment le budget de reconnexion de la requête et résolvent le waiter avec CONNECTION_CLOSED.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
api, networking
Type d'issue
Bug
Difficulté
2/5
Temps estimé
1-3 heures
Activité
Active
Clarté
Clairement spécifiée
Accessibilité débutants
88/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.