Fail fast on server-to-client requests in JSON-response mode instead of hanging (#3195)

This commit is contained in:
Max 2026-07-28 11:04:51 +01:00 • committed by GitHub
commit 528e366558
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 259 additions and 93 deletions

View file

@ -65,7 +65,7 @@ Each transport has its own keyword arguments, all on `run()`:
* `host` / `port`: where to listen. Defaults `127.0.0.1` and `8000`.
* `streamable_http_path`: where the MCP endpoint lives. Default `/mcp`.
* `json_response=True`: answer with plain JSON instead of an SSE stream.
* `json_response=True`: answer each POST with a single JSON body instead of an SSE stream. That body has room for the response and nothing else, so a tool that calls back into the client mid-request (`ctx.elicit()`, sampling) raises `NoBackChannelError` on this leg, and notifications tied to the in-flight call (progress from `ctx.report_progress()`, per-call log messages) are dropped; the standalone `GET` stream still carries unrelated ones.
* `stateless_http=True`: a fresh transport per request, no session tracking.
* `max_request_body_size`: largest accepted POST body in bytes. Defaults to 4 MiB; larger requests
receive HTTP 413 before parsing or session creation. Raise it only when legitimate MCP messages

View file

@ -72,6 +72,13 @@ Two things about it matter more than what it does.
**It costs both server-to-client channels on that leg.** A session that lives for one `POST` has no stream for the server to push a request down and no standalone stream for it to push notifications down. Every server-initiated request raises `NoBackChannelError`: `ctx.elicit()`, the retired sampling and roots calls (**[Deprecated features](../deprecated.md)**), and, yes, `Resolve` asking a *legacy* client its question. Notifications don't even get an error; they are silently dropped.
!!! note
`json_response=True` is not that knob, but it takes half the same cost on *every* legacy
session: a `POST` answered with one JSON body has no stream for the request-scoped channel,
so a mid-request `ctx.elicit()` raises the same `NoBackChannelError` and notifications tied to
the request are dropped. The session's standalone stream is untouched: unrelated notifications
still arrive.
!!! check
Do the wrong thing. `reserve` is the exact tool that just served both clients. Deploy it with
`stateless_http=True`, connect the same two clients over HTTP, and call it from each.