Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

Streamable HTTP client hangs on a null-id JSON-RPC error in a 200 JSON response

未关闭 适合新手
#3,639 2 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
75/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
python
领域
api

调研方向

从 src/mcp/client/streamable_http.py 中的 _handle_json_response 开始,然后将其处理方式与 _handle_sse_event 和 _handle_post_request 进行比较。在 tests/client/test_notification_response.py 中现有的 non-2xx null-id 测试旁边添加回归测试;完成标准是:id 为 null 的 200 JSON-RPC 错误会引发 MCPError,而不是让请求一直等待。

由索引模型根据 Issue 内容生成。

描述

bug v1 v2
Initial Checks
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 set id: 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)
主要语言
Python
星标
24.5k
派生
4k
平均合并
1 天 4 小时
30 天内合并 PR
33

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

modelcontextprotocol/python-sdk 的其他 Issue

查看 modelcontextprotocol/python-sdk 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。