stateless-legacy
The one-liner HTTP deploy. MCPServer.streamable_http_app(stateless_http=True)
returns a complete ASGI app that serves both protocol eras on /mcp: 2025
clients get the initialize handshake answered statelessly (no Mcp-Session-Id,
fresh transport per request, horizontally scalable), 2026 clients get the
per-request envelope path. Hand it straight to uvicorn — no session-manager
wiring, no era flag. The client connects once per era and asserts the same
greet tool answers identically either way.
Run it
# HTTP — the client self-hosts the app on a free port, connects once as a
# modern client and once as a legacy client, then tears it down
uv run python -m stories.stateless_legacy.client --http
# same, against the lowlevel-API server variant
uv run python -m stories.stateless_legacy.client --http --server server_lowlevel
# against a server you run yourself (real uvicorn on :8000)
uv run python -m stories.stateless_legacy.server --port 8000 &
SERVER_PID=$!
uv run python -m stories.stateless_legacy.client --http http://127.0.0.1:8000/mcp
kill "$SERVER_PID"
What to look at
client.py— two visibleClient(targets(), mode=...)constructions against the same URL. The first connects at the caller'smode(the real-user"auto"default routes to the 2026 envelope path); the second pinsmode="legacy"and runs theinitializehandshake.client.protocol_versionis the era-neutral accessor: two negotiated versions, identical tool result.server.py—stateless_http=Trueis the only knob; era routing is automatic insideStreamableHTTPSessionManager.handle_request. The returnedStarlettealready wireslifespan=session_manager.run(), souvicorn.run(app, ...)works with no parent-lifespan ceremony.server_lowlevel.py—lowlevel.Server.streamable_http_app()is the same call;MCPServerdelegates to it.
Caveats
transport_security=NO_DNS_REBIND— DNS-rebinding protection is on by default for localhost binds; the harness disables it because the in-process httpx client sends noOriginheader. Drop the kwarg for a real deployment.streamable_http_app()reshapes in a later release; the call is isolated inbuild_app()so the change touches one line per server file.
Spec
Streamable HTTP transport · Versioning — backward compatibility
See also
dual_era/ (era branching inside a tool handler) · legacy_routing/
(classify_inbound_request() for sessionful-2025 + modern on one mount) ·
starlette_mount/ (mounting under FastAPI/Starlette with parent lifespan) ·
json_response/ (json_response=True and what it drops).