Streamable HTTP client hangs on a null-id JSON-RPC error in a 200 JSON response
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 75/100
Línea de trabajo
Comienza en src/mcp/client/streamable_http.py, en _handle_json_response, y compara después su gestión con _handle_sse_event y _handle_post_request. Añade la prueba de regresión junto a la prueba existente de null-id no-2xx en tests/client/test_notification_response.py; se considera terminado cuando un error JSON-RPC 200 con id null lanza MCPError en lugar de dejar la solicitud esperando.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Initial Checks
- I confirm that I'm using the newest release of my line (the latest 2.x, or the latest 1.x if I'm still on v1)
- I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Release line
2.x (current stable)
Description
On current main (2118f14f), when a server answers a request POST with a 200 application/json body holding a JSON-RPC error with "id": null, the streamable HTTP client never resolves the call. session.list_tools() (or any request) waits forever, or until a read timeout if one is set.
JSON-RPC 2.0 allows id: null on an error when the server could not determine the request id. Gateways and non-SDK servers can send one with status 200.
_handle_json_response in src/mcp/client/streamable_http.py forwards the parsed message unchanged. The dispatcher then drops it as a response to an unknown id (jsonrpc_dispatcher.py, "unknown/late request id").
The other two response paths in the same transport already handle this:
- SSE (
_handle_sse_event): replaces a response's id with the original request id. - Non-2xx JSON body (
_handle_post_request): rebuilds the error under the request's id. The comment there says: "The server may have setid: null(request rejected before its id was parsed); use this request's id so correlation works."
So the same error body resolves the call over SSE or with a 4xx status, but hangs with a 200.
Expected: the call raises MCPError with the server's error, the same as the SSE and non-2xx paths.
Proposed fix: in _handle_json_response, when the parsed message is a JSONRPCError, set its id to the POST's request id. A 2xx JSON body answers exactly that POST, so nothing else can be waiting on it. That is a 4-line change plus a regression test next to the existing non-2xx null-id test in tests/client/test_notification_response.py.
I'd like to fix this and have a branch ready: https://github.com/hyeonsang010716/python-sdk/tree/fix/streamable-http-json-null-id
I found and prepared this with AI assistance. I've reviewed the change, checked the repro below fails on main and passes with the fix, and can explain it.
Example Code
import json
import anyio
import httpx2
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.routing import Route
from mcp import ClientSession, MCPError
from mcp.client.streamable_http import streamable_http_client
async def handle_mcp(request: Request) -> Response:
data = json.loads(await request.body())
if data.get("method") == "initialize":
result = {"protocolVersion": "2025-06-18", "capabilities": {}, "serverInfo": {"name": "s", "version": "1"}}
return JSONResponse({"jsonrpc": "2.0", "id": data["id"], "result": result})
if "id" not in data:
return Response(status_code=202)
# e.g. a gateway that fails before it has parsed the request id
return JSONResponse({"jsonrpc": "2.0", "id": None, "error": {"code": -32603, "message": "upstream failed"}})
async def main() -> None:
app = Starlette(routes=[Route("/mcp", handle_mcp, methods=["POST"])])
async with httpx2.AsyncClient(transport=httpx2.ASGITransport(app=app)) as http:
async with streamable_http_client("http://localhost/mcp", http_client=http) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
try:
with anyio.fail_after(3):
await session.list_tools()
except MCPError as exc:
print("raised MCPError:", exc.error.message)
except TimeoutError:
print("list_tools() never returned")
anyio.run(main)
Output on main:
list_tools() never returned
Output with the fix:
raised MCPError: upstream failed
Python & MCP Python SDK
Python 3.14.7
mcp main @ 2118f14f (also present in v2.3.0)
- Lenguaje dominante
- Python
- Estrellas
- 24.5k
- Forks
- 4k
- Merge medio
- 1 d 14 h
- PR fusionados (30 d)
- 34
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de modelcontextprotocol/python-sdk
-
bug v1 v2
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
modelcontextprotocol/python-sdk#3656 ·
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
modelcontextprotocol/python-sdk#3655 ·
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 1/5 Menos de una hora Aptitud para principiantes 86/100
modelcontextprotocol/python-sdk#3654 ·
Los mantenedores suelen responder en 1 día
-
bug v1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
modelcontextprotocol/python-sdk#3652 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
bug spec-2026-07-28 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
modelcontextprotocol/python-sdk#3649 ·
Los mantenedores suelen responder en 1 día
Todos los issues de modelcontextprotocol/python-sdk
Issues similares
-
docs(types): update the collection binding note now that typed collections shipped in pycubrid 1.9.0Abiertodocumentation priority: low size: S
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
cubrid-lab/sqlalchemy-cubrid#768 ·
Los mantenedores suelen responder en 1 día
-
--csv-bom was never wired up: PR #850 added an unused helper parameter, so #846 is not fixedAbiertobug help wanted
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
Los mantenedores suelen responder en 1 día
-
Broken link in index.rstAbiertodocumentation
Dificultad 1/5 Menos de una hora Aptitud para principiantes 65/100
ansys/pydpf-core#3547 ·
Los mantenedores suelen responder en 1 día
-
core
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
vectorize-io/hindsight#5457 ·
Los mantenedores suelen responder en 1 día
-
[Bug]: LangChain drops OpenAI Responses text blocks from session recordingPosiblemente ocupada @ktz03 la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
volcengine/OpenViking#5806 ·
Los mantenedores suelen responder en 1 día