49b9bb6724
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
json-response
streamable_http_app(json_response=True) — one application/json body per
request instead of an SSE stream. Useful for serverless / edge runtimes that
can't hold a stream open. The 2026-07-28 path is stateless and JSON-only today
regardless of the flag; setting it makes the legacy (2025-era) branch on the
same endpoint behave the same way.
Run it
# HTTP — the client self-hosts the app on a free port, runs the high-level
# Client + raw-envelope probe, then tears it down
uv run python -m stories.json_response.client --http
# same, against the lowlevel-API server variant
uv run python -m stories.json_response.client --http --server server_lowlevel
# against a server you run yourself (real uvicorn on :8000)
uv run python -m stories.json_response.server --port 8000 &
SERVER_PID=$!
uv run python -m stories.json_response.client --http http://127.0.0.1:8000/mcp
# or POST the raw envelope yourself
curl -s http://127.0.0.1:8000/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H 'mcp-protocol-version: 2026-07-28' \
-H 'mcp-method: tools/list' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"curl","version":"0"},"io.modelcontextprotocol/clientCapabilities":{}}}}'
kill "$SERVER_PID"
What to look at
client.pymain—async with Client(target, mode=mode) as client:is an ordinary high-level client; nothing about JSON mode is visible from this side. The samemainalso takes the rawhttpx.AsyncClientso it can prove what the wire looks like underneath.client.pyRAW_ENVELOPE_BODY/MODERN_HEADERS— the exact 2026 wire shape: threeio.modelcontextprotocol/*_metakeys replace the initialize handshake;MCP-Protocol-Version+Mcp-Methodheaders mirror the body so gateways can route without parsing JSON.mainposts it by hand and asserts a singleapplication/jsonresponse with noMcp-Session-Id.server.pygreetcallsctx.report_progress(0.5)— andmainproves the client'sprogress_callbackis never invoked: JSON mode has no back-channel for mid-call notifications (theprogress_seen == []assertion flips to== [0.5]once SSE buffering lands for the modern path).server_lowlevel.py— same ASGI app built fromlowlevel.Server; thejson_response=/transport_security=knobs live onstreamable_http_app, not the server class.
Caveats
- DNS-rebinding protection is on by default; the harness disables it via
NO_DNS_REBINDbecause the in-process httpx client sends noOriginheader. - The
streamable_http_app()call shape here will move when the free-function entry lands (see_hosting.py). Mcp-Nameis omitted fortools/listbecause the SDK only emits it ontools/calltoday.
Spec
Streamable HTTP — 2026-07-28 · SEP-2243 standard headers
See also
stateless_legacy/ (the one-liner stateless_http=True deploy),
legacy_routing/ (route by era at the entry), streaming/ (progress that is
delivered — over stdio/SSE).