Skip to content

Streamable HTTP: Client mode="auto" fails on server/discover against MCPServer + uvicorn (RemoteProtocolError) #3588

Closed as not planned

Description

@Kandeepasundaram

Summary

With mcp 2.2.0, a Client in the default mode="auto" cannot talk to an MCPServer served over Streamable HTTP by uvicorn: the server/discover POST returns 200 OK, but reading the response body fails with httpx2.RemoteProtocolError('peer unexpectedly closed connection'). mode="legacy" against the same server works.

Minimal repro

import asyncio

import uvicorn
from mcp import Client
from mcp.server.mcpserver import MCPServer

server = MCPServer("repro")


@server.tool()
def add(a: int, b: int) -> int:
    return a + b


async def main() -> None:
    uv = uvicorn.Server(uvicorn.Config(server.streamable_http_app(), host="127.0.0.1", port=0, log_level="warning"))
    task = asyncio.create_task(uv.serve())
    while not uv.started:
        await asyncio.sleep(0.01)
    url = f"http://127.0.0.1:{uv.servers[0].sockets[0].getsockname()[1]}/mcp"
    for mode in ("legacy", "auto"):
        try:
            async with Client(url, mode=mode) as client:
                result = await client.call_tool("add", {"a": 1, "b": 2})
                print(mode, "OK", result.content[0].text)
        except Exception as exc:
            print(mode, "FAILED", repr(getattr(exc, "exceptions", [exc])[0]))
    uv.should_exit = True
    await task


asyncio.run(main())

Output:

legacy OK 3
auto FAILED RemoteProtocolError('peer unexpectedly closed connection')

Client debug logging shows the failure on the very first request (server/discover, protocol version 2026-07-28): the server logs POST /mcp 200 OK, then the client errors while reading the body.

Possibly related observation

A hand-written curl POST of the same server/discover request (headers content-type: application/json, accept: application/json, text/event-stream, MCP-Protocol-Version: 2026-07-28) got HTTP/1.1 400 Bad Request with transfer-encoding: chunked, and curl failed with chunk hex-length char not a hex digit: 0x7b — i.e. a raw {-prefixed body under a chunked header. My curl request may itself have been malformed (hence the 400), but the body framing looks wrong regardless of status, and would explain the client-side error.

Environment

  • mcp 2.2.0 (mcp-types 2.2.0), uvicorn 0.53.0, starlette 1.6.0, httpx2 2.13.0
  • Python 3.13.15, Windows 11

Workaround

Client(url, mode="legacy").

Activity

  1. added
    v2Affects the v2 line (2.x on main)
    spec-2026-07-28Concerns the SDK's implementation of the 2026-07-28 MCP spec revision
    on Sep 27, 2026
  2. avinash-git-24 commented on Sep 28, 2026

    @avinash-git-24

    Hey team, on server/discover the Streamable HTTP ASGI handler in src/mcp/server/streamable_http.py sends transfer-encoding: chunked in http.response.start with raw JSON body bytes, causing uvicorn to skip chunk framing. Happy to open a PR fixing this!

  3. maxisbey commented on Oct 1, 2026

    @maxisbey
    Contributor

    I couldn't reproduce this on Windows with the versions you listed (mcp 2.2.0, uvicorn 0.53.0, starlette 1.6.0, httpx2 2.13.0, Python 3.13.15). Your script prints legacy OK 3 / auto OK 3, and a further 1,600 mode="auto" connections all succeed.

    Your curl output is the useful clue. Every mode="auto" reply from the server has a content-length header and a matching body; nothing in the SDK or uvicorn sends transfer-encoding: chunked there. I do get exactly your two results (the RemoteProtocolError and curl's chunk hex-length char not a hex digit: 0x7b) when a proxy sits between the client and the server and relabels those replies as chunked without re-framing them. mode="legacy" survives that because its replies are already chunked streams.

    So I think something on your machine is rewriting the responses. An HTTP_PROXY / ALL_PROXY environment variable or web-filtering software would both do it, and the client sends requests for 127.0.0.1 through a configured proxy too. Setting NO_PROXY=127.0.0.1,localhost should confirm it.

    I'm closing this as not reproducible. If it still fails with NO_PROXY set, comment here with the output of curl -v --raw --noproxy "*" and I'll reopen it.

    AI Disclaimer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    spec-2026-07-28Concerns the SDK's implementation of the 2026-07-28 MCP spec revisionv2Affects the v2 line (2.x on main)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions