Repository navigation
Conversation
…e handshake eras The Streamable HTTP adapter required an initialize handshake and a server-minted session before forwarding any request, which made sessionless 2026-07-28 traffic impossible. The adapter now auto-detects the protocol era per connection: - Modern era (2026-07-28): a server/discover request or a params._meta io.modelcontextprotocol/protocolVersion field switches the adapter to sessionless forwarding - no initialize, no session headers. Per request it mints the spec-required MCP-Protocol-Version, Mcp-Method, and Mcp-Name headers (base64 sentinel encoding for non-header-safe values). Error statuses carrying JSON-RPC bodies (404 -32601, 400 unsupported version) are relayed to the gateway as ordinary responses, so handshake-era session-expiry recovery (SessionExpiredError, re-registration) can never fire on modern connections. A mcp-session-id on a modern response is ignored per spec. - Handshake era (<= 2025-11-25): unchanged - initialize capture, session headers, 404 retry/re-registration. SSE stays handshake-era only (legacy transport by spec); stdio is era-agnostic passthrough. Test harness: bump the integration extra to fastmcp>=4,<5 (FastMCP 4 / MCP SDK v2 serve both eras per connection, auto-detected). Integration suite now covers handshake 2025-06-18 and 2025-11-25 over stdio/SSE/ streamable-http, plus live sessionless 2026-07-28 round-trips (server/discover, tools/call, tools/list, resources/read, prompts/get) and modern-era 404 error relay. Unit tests cover era detection, header minting/encoding, and era-aware error handling. Signed-off-by: Jonathan Springer <jps@s390x.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related Issue
Closes #12
Summary
Makes the reverse proxy carry both MCP protocol eras over the server-side Streamable HTTP leg. The adapter previously refused any request that arrived before an
initializehandshake produced a session, which made the sessionless 2026-07-28 era impossible to forward.server/discoverrequest or aparams._meta["io.modelcontextprotocol/protocolVersion"]field switches the adapter to sessionless mode;initializekeeps the handshake-era path. An established handshake session can never be flipped, and amcp-session-idminted on a modern-era response is ignored (spec: modern servers must not mint sessions).MCP-Protocol-Version(matching the body_meta),Mcp-Method, andMcp-Namefortools/call/resources/read/prompts/get, using the=?base64?...?=sentinel for non-header-safe values.-32601method not found, 400 unsupported protocol version) are relayed to the gateway as ordinary responses.SessionExpiredErrorand the gateway re-registration flow remain handshake-era only. Bodyless error statuses still raise.fastmcp>=4,<5(FastMCP 4 / MCP SDK v2 serve both eras per connection). Companion server code needed no changes - every API it uses survived v4.Reviewability
triageType of Change
Verification
uv run pytest tests/ -quv run pytest tests/integration -vuv run ruff check src testsuv run mypy src/mcp_reverse_proxyuv buildManual validation: a throwaway smoke script drove
server/discover+tools/call+tools/listthrough the adapter against a live FastMCP 4.1.0 server with no handshake; server returnedsupportedVersions: ["2026-07-28"]and correct call results. (Script removed after use; the flow is pinned bytests/integration/test_modern_era.py.)Note:
black --checkreports 18 files reformat-needed identically at HEAD (pre-existing drift); this change introduces no new violations, so unrelated reformatting was left out of scope.Checklist
pytestpassesruff check .andmypy src/are greengit commit -s, DCO)Notes
Design constraint: the era is detected from the first messages on a connection and fixed until
stop()/reconnect - a gateway switching eras mid-connection is not a real topology. Era support by transport: stdio and Streamable HTTP carry both eras; SSE is handshake-era only by specification.