Return 405 for listen-mode GET on stateless streamable HTTP transport
Closed
SA
23d ago+38+38+01234567890123456789-0-0-0123456789
Fixes #3492
StreamableHTTPServerTransport.__init__gets a newstateless: bool = Falseparameter, stored asself._stateless.- I deliberately did NOT key the 405 off
mcp_session_id is None, even
though that's true for every stateless transport today. An existing test
(test_standalone_stream_teardown_between_dequeues_is_not_an_error)
constructs a transport directly withmcp_session_id=Nonespecifically to
exercise the standalone-GET-stream teardown path, soNonesession id is
not a reliable stand-in for "stateless" at the transport level — it's
already used as "no session tracking, but the standalone stream still
works" in that test. An explicit flag avoids overloading that meaning,
which is also what the issue's first commenter (arpankernel) suggested. _handle_get_requestnow returns 405 immediately whenself._stateless
is set, before any Accept-header check or stream creation.streamable_http_manager.py::_handle_stateless_requestpasses
stateless=Truewhen constructing the transport (the one and only place
that builds a stateless transport).- Test: added
stateless_appfixture (via a newstateless=Falsekwarg on
the existingrunning_apphelper) and
test_get_sse_stream_returns_405_when_stateless, which asserts the GET
returns 405 within a 5sanyio.fail_after(so if the fix ever regresses,
the test fails fast instead of hanging CI).
Verification
Command: `./scripts/test`
Result: `5969 passed, 10 skipped, 1 xfailed` in 57.21s, followed by
`coverage report` → `TOTAL 52233 0 4072 0 100.00%`, and
`strict-no-cover` → `✅ No lines wrongly marked with 'pragma: no cover'`.
Also ran targeted:
- `uv run --frozen pytest tests/shared/test_streamable_http.py::test_get_sse_stream_returns_405_when_stateless -v` → PASSED
- `uv run --frozen pytest tests/shared/test_streamable_http.py tests/server/test_streamable_http_router.py tests/server/test_streamable_http_manager.py -q` → 130 passed
- `uv run --frozen ruff format --check`, `uv run --frozen ruff check`, `uv run --frozen pyright` on the three changed files → all clean
Before the fix, my standalone repro script (curl-equivalent in-process GET)
hung for the full 3s timeout with zero bytes; after the fix it returns
`405` with a JSON-RPC error body immediately.