Skip to content

fix: return 405 for GET/DELETE in stateless streamable-http mode#2262

Open
sys-2077 wants to merge 1 commit intomodelcontextprotocol:mainfrom
sys-2077:fix/stateless-reject-get-delete
Open

fix: return 405 for GET/DELETE in stateless streamable-http mode#2262
sys-2077 wants to merge 1 commit intomodelcontextprotocol:mainfrom
sys-2077:fix/stateless-reject-get-delete

Conversation

@sys-2077
Copy link

Problem

When stateless=True, StreamableHTTPSessionManager._handle_stateless_request() delegates all HTTP methods (GET, POST, DELETE) to a per-request StreamableHTTPServerTransport. GET opens an SSE stream that has no session context and will never receive server-initiated messages. The stream idles until the platform kills it (e.g. Cloud Run 120s timeout), then clients like mcp-remote auto-reconnect, creating an infinite loop of billed CPU time.

The TypeScript SDK correctly returns 405 for GET and DELETE in stateless mode (see src/server/streamableHttp.ts stateless example).

Impact

Any MCP server deployed on serverless platforms (Cloud Run, Lambda, Azure Container Apps) with stateless=True is affected. Each idle SSE reconnection cycle bills ~120s of CPU. A single forgotten client session can consume 180,000+ CPU-seconds/month (exceeding Cloud Run's entire free tier).

Fix

In _handle_stateless_request(), validate that the method is POST before creating the transport. Return 405 with Allow: POST header for GET and DELETE, matching the TypeScript SDK behavior and the MCP spec.

Spec reference

MCP Streamable HTTP transport (2025-03-26):

  • "The client MAY issue an HTTP GET" (optional, not required)
  • "The server MUST return HTTP 405 Method Not Allowed if an SSE stream is not offered at the endpoint"
  • Stateless servers have no session → no notifications → no SSE stream to offer

Tests

Added two tests in tests/server/test_streamable_http_manager.py:

  • test_stateless_get_returns_405
  • test_stateless_delete_returns_405

In stateless mode, GET opens a session-less SSE stream that idles until
the platform kills it (e.g. Cloud Run 120s timeout). Clients like
mcp-remote auto-reconnect, creating an infinite loop of billed CPU.

The TypeScript SDK correctly returns 405 for GET and DELETE in stateless
mode. This aligns the Python SDK with that behavior and the MCP spec
(GET is MAY, not MUST; stateless servers have no session context for
server-initiated notifications).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant