Skip to content

feat(transport): add SEP-2243 Mcp-Method / Mcp-Name header mirroring with MCP-Protocol-Version validation - #1112

Open
slachiewicz wants to merge 4 commits into
modelcontextprotocol:mainfrom
slachiewicz:feat/sep-2243-headers
Open

feat(transport): add SEP-2243 Mcp-Method / Mcp-Name header mirroring with MCP-Protocol-Version validation#1112
slachiewicz wants to merge 4 commits into
modelcontextprotocol:mainfrom
slachiewicz:feat/sep-2243-headers

Conversation

@slachiewicz

@slachiewicz slachiewicz commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Closes #990. Supersedes #994, #1026, #1092 (with credit to @cooleditphoto, @nikita-kibitkin, and @ez-lbz — the consolidation proposed in #990; @nikita-kibitkin agreed to fold in #1026).

Summary

Implements SEP-2243 HTTP header standardization end-to-end:

  • The JDK Streamable HTTP client emits an Mcp-Method header on every POST and an Mcp-Name header whenever the request targets a named artifact (tools/call, prompts/get, resources/read, resources/subscribe, resources/unsubscribe).
  • Non-ASCII, control character, or sentinel-matching Mcp-Name values are safely encoded using the SEP-2243 Base64 sentinel format (=?base64?...?=), protecting against JDK HttpClient IllegalArgumentException on non-Latin-1 characters.
  • Both servlet server transports validate present Mcp-Method / Mcp-Name headers against the deserialized request body (decoding Base64 sentinels where present), returning HEADER_MISMATCH (-32020) with HTTP 400 on mismatches.
  • Servlet server transports validate the MCP-Protocol-Version header against the supported version set (exempting initialize requests, which negotiate version via payload).
  • Includes the deflake for HttpClientStreamableHttpVersionNegotiationIntegrationTests.

Semantics (the decision this PR stands on)

SEP-2243 requires servers to reject header/body mismatches, and discussion on #994 established that rejecting absent headers must not break legacy clients:

  • A present-but-mismatched Mcp-Method or Mcp-Name is rejected with 400 Bad Request and HEADER_MISMATCH (-32020).
  • Absent or blank headers never trigger validation errors, so clients that do not send the new headers keep working unchanged.
  • An absent MCP-Protocol-Version falls back to negotiated behavior, while a present-but-unsupported version on post-init requests is rejected.

Implementation notes

  • New constants HttpHeaders.MCP_METHOD, HttpHeaders.MCP_NAME, HttpHeaders.BASE64_SENTINEL_PREFIX, HttpHeaders.BASE64_SENTINEL_SUFFIX, and helper methods HttpHeaders.encodeHeaderValue / HttpHeaders.decodeHeaderValue.
  • New error code McpSchema.ErrorCodes.HEADER_MISMATCH = -32020.
  • Client emission happens in HttpClientStreamableHttpTransport.sendMessage for both requests and notifications. The name/URI comes from typed binding of the params (tools/call, prompts/get use .name(); the resource operations use .uri()), so no regex-on-body parsing is involved. Extraction failures log at debug and omit the header rather than fail the request.
  • Server checks run after JSON-RPC deserialization in the servlet transports, comparing header against the parsed body. Responses carry no method and are skipped.

Testing

  • Sep2243ClientRequestHeaderTests asserts emitted headers through a real JDK HTTP server round-trip, including Base64 sentinel encoding for non-ASCII tool names.
  • Sep2243ServerHeaderValidationTests covers the rejection matrix (bad protocol version, method mismatch, name mismatch with error code -32020), Base64 decoding acceptance, and the tolerance rule (absent headers do not trigger SEP-2243 errors), for both the streamable servlet provider and the stateless transport.
  • HttpClientStreamableHttpVersionNegotiationIntegrationTests verifies protocol version propagation and async stream readiness.

Co-authored-by: Nikita Kibitkin nikita.n.kibitkin@gmail.com

slachiewicz and others added 4 commits September 2, 2026 22:04
…with MCP-Protocol-Version validation

Implement SEP-2243 HTTP header standardization across client and server
servlet transports.

* Client: Emit 'Mcp-Method' on outbound Streamable HTTP requests and
  notifications, and 'Mcp-Name' when targeting named tools, prompts, or
  resources.
* Server: Validate 'Mcp-Method' and 'Mcp-Name' headers against deserialized
  JSON-RPC payloads in HttpServletStreamableServerTransportProvider and
  HttpServletStatelessServerTransport. Reject mismatches with HTTP 400 while
  tolerating absent headers for backward compatibility.
* Versioning: Validate 'MCP-Protocol-Version' against supported protocol
  versions on incoming servlet requests.
* Tests: Add Sep2243ClientRequestHeaderTests and Sep2243ServerHeaderValidationTests
  verifying emission, mismatch rejections, and absent-header tolerance.
…checks

Per the Streamable HTTP spec the MCP-Protocol-Version header is required only after initialization completes; version selection for initialize happens through body-level negotiation, not header validation.

* Client: stop sending MCP-Protocol-Version on initialize requests

* Servlet servers: skip strict header validation for initialize so clients advertising an unsupported version negotiate instead of getting 400

* Tests: pin client omission and server tolerance for initialize; make version-negotiation test contextExtractor null-safe for absent headers
The GET /mcp stream is opened asynchronously once initialize creates the session, so asserting recorded calls immediately races under load (seen as Jackson 2 Integration Tests failing usesLatestVersion with Expected size: 3 but was: 2). Await the recorded GET before asserting header propagation.
… and return -32020 HeaderMismatch

* Encode non-ASCII and sentinel-matching Mcp-Name values in Base64 sentinel
  format (=?base64?...?=) in HttpClientStreamableHttpTransport to prevent
  JDK HttpClient IllegalArgumentException on non-Latin-1 characters.
* Decode Base64 sentinel Mcp-Name values on HttpServletStreamableServerTransportProvider
  and HttpServletStatelessServerTransport prior to body validation.
* Add HEADER_MISMATCH (-32020) error code to McpSchema.ErrorCodes per SEP-2243
  and return it on header/body mismatches.
* Add unit and integration tests for Base64 sentinel encoding/decoding and
  -32020 mismatch error codes.

Co-authored-by: Nikita Kibitkin <nikita.n.kibitkin@gmail.com>
@slachiewicz
slachiewicz force-pushed the feat/sep-2243-headers branch from edcac59 to 63b1c04 Compare September 2, 2026 20:08
@slachiewicz
slachiewicz marked this pull request as ready for review September 2, 2026 20:08
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.

SEP-2243: HTTP Header Standardization for Streamable HTTP Transport

1 participant