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)
Ninguém assumiu esta issue ainda.
- Linguagem predominante
- Python
- Estrelas
- 24.3k
- Forks
- 4k
- Merge médio
- 1d 1h
- PRs com merge (30d)
- 31
Descrição
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…)
_handle_sse_response(:397-435): a completed response returns inside thetry. When the server dies mid-stream,EventSource.aiter_sse()raiseshttpx.RemoteProtocolError("peer closed connection without sending complete message body (incomplete chunked read)"), whichexcept Exception as e: # pragma: no cover(:429-430) logs at DEBUG (SSE stream ended: …). The tail (:432-435) reconnects onlyif 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 toctx.read_stream_writer.ClientSession.send_requesttherefore waits forever on its response stream._handle_reconnection(:446-448) gives up afterMAX_RECONNECTION_ATTEMPTSwith a barereturn— 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<2that renders this hang asTool 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.
Guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Direção de pesquisa
Comece em src/mcp/client/streamable_http.py lendo _handle_sse_response, _handle_reconnection e _send_session_terminated_error; compare a resolução correspondente em 2.x de #3047. Reproduza a desconexão SSE sem resposta e verifique se as solicitações pendentes recebem prontamente o McpError esperado; em seguida, execute os testes relevantes do cliente e confirme que o backport para v1.x está pronto para o lançamento.
Escrita pelo modelo de indexação a partir do texto da issue.
Avaliação
- Stack de tecnologia
- python
- Domínio
- api
- Tipo de issue
- Bug
- Dificuldade
- 3/5
- Tempo estimado
- 1-2 dias
- Status de atividade
- Ativa
- Clareza
- Claramente especificada
- Facilidade para iniciantes
- 72/100