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

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).

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;
  • both servlet server transports validate present Mcp-Method / Mcp-Name
    headers against the deserialized request body, and validate the
    MCP-Protocol-Version header against the supported version set.

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 a message naming both values.
  • 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 is rejected.

This replaces the opt-in rejectMissingHeaders(false) switch discussed for the
consolidation: gating absence would wrongly gate mismatches too, and splitting
the two concerns needs two flags where current semantics need none.

Implementation notes

  • New constants HttpHeaders.MCP_METHOD and HttpHeaders.MCP_NAME.
  • 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,
    so validation compares header against the parsed body, not raw text.
    Responses carry no method and are skipped.

Testing

  • Sep2243ClientRequestHeaderTests asserts emitted headers through a real
    JDK HTTP server round-trip.
  • Sep2243ServerHeaderValidationTests covers the rejection matrix (bad
    protocol version, method mismatch, name mismatch) and the tolerance rule
    (absent headers produce only unrelated errors such as a missing session),
    for both the streamable servlet provider and the stateless transport.

Conformance Tests (@modelcontextprotocol/conformance@0.2.0-alpha.11)

Suite Scope / Scenarios Result
Server Active Suite 31 scenarios (Lifecycle, Tools, Resources, Prompts, SSE, Security) 73/73 checks passed (100%)
Server SEP-1613 json-schema-2020-12 schema preservation 5/5 checks passed
Client Suite client-jdk-http-client (initialize, tools_call, defaults, sse-retry) 3/4 scenarios passed (expected sse-retry failure in baseline)
Auth Suite client-spring-http-client (14 OAuth2 scenarios) 193/193 checks passed (100%)

All tests match the expected baseline 100% across all released spec versions (2025-03-26, 2025-06-18, and 2025-11-25).

Compatibility

Non-breaking for conformant legacy traffic: emission is additive on outbound
requests, and server behavior changes only for clients that already send these
headers with wrong values, which is what the SEP requires servers to catch.

Out of scope, deliberately

The Spring/WebClient/WebMvc stack has no separate streamable-HTTP transport in
this codebase, so there is nothing further to wire. Name extraction is
duplicated as private helpers in the two servlet classes today; pulling it
into a shared utility can follow once a third caller exists.

…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
Previous run failed on pre-existing flaky race in AbstractMcpClientServerIntegrationTests.testRootsNotificationWithEmptyRootsList (Stream unavailable for session); unrelated transport changes all green.
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.
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