Skip to content

Return 405 for listen-mode GET on stateless streamable HTTP transport - #3574

Closed
SammyTourani wants to merge 1 commit into
modelcontextprotocol:mainfrom
SammyTourani:fix/issue-3492
Closed

SammyTourani wants to merge 1 commit into
modelcontextprotocol:mainfrom
SammyTourani:fix/issue-3492

Conversation

@SammyTourani

Copy link
Copy Markdown

Fixes #3492

  • StreamableHTTPServerTransport.__init__ gets a new stateless: bool = False parameter, stored as self._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 with mcp_session_id=None specifically to
    exercise the standalone-GET-stream teardown path, so None session 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_request now returns 405 immediately when self._stateless
    is set, before any Accept-header check or stream creation.
  • streamable_http_manager.py::_handle_stateless_request passes
    stateless=True when constructing the transport (the one and only place
    that builds a stateless transport).
  • Test: added stateless_app fixture (via a new stateless=False kwarg on
    the existing running_app helper) and
    test_get_sse_stream_returns_405_when_stateless, which asserts the GET
    returns 405 within a 5s anyio.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.

Stateless mode never sends server-initiated messages, so the standalone
GET stream previously opened a 200 text/event-stream response and held
it open with zero bytes, hanging clients. Add a stateless flag to
StreamableHTTPServerTransport and answer 405 for the listen-mode GET
when set, per the 2025-03-26 spec. Fixes modelcontextprotocol#3492.
@github-actions github-actions Bot added the missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md) label Sep 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR has been closed automatically. This repo only keeps pull requests open when they come from a maintainer, or from a contributor a maintainer has assigned to the linked issue, and you aren't currently assigned to #3492.

If a maintainer assigns you to #3492, this PR reopens on its own and there's nothing more you need to do here. Assignment is a maintainer call based on capacity; comments that only ask to be assigned don't factor in. What does help is engaging on the issue itself by confirming the repro, explaining why it matters for your use case, or describing the approach you'd take.

You're welcome to keep pushing commits here (just avoid force-pushing, since GitHub can't reopen a rewritten branch), but that on its own won't get the PR reviewed or the issue assigned, and realistically most auto-closed PRs stay closed. There's no need to open a new PR either way.

CONTRIBUTING.md has the full reasoning, but in short:

  • We're a small team with very little capacity to review community PRs right now.
  • Many recent PRs are AI-generated with little human review, and reviewing one carefully still costs a maintainer as much time as it ever did. A well-described issue is usually more useful to us than the code.

Maintainers: reopen, remove missing-issue-link, or add bypass-issue-check to override.

@github-actions github-actions Bot closed this Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Streamable HTTP stateless servers hang clients on the listen-mode GET (_handle_get_request opens a hold-open SSE stream)

1 participant