feat: SEP-2243 custom half — Mcp-Param header codec, client mirroring, server validation
SEP-2243 Mcp-Param-* headers — codec, client-side mirroring, and server-side validation (protocol revision 2026-07-28).
Motivation and Context
SEP-2243 lets a server designate selected tool parameters (via an x-mcp-header annotation in the tool's inputSchema) to be mirrored into Mcp-Param-{Name} HTTP request headers, so HTTP intermediaries can route, filter, or audit on them without parsing the JSON-RPC body. The spec defines a value encoding (with a =?base64?…?= sentinel for values that are not safe plain-ASCII field values), a 5-step client algorithm, a constraint table for x-mcp-header declarations, and a server-behavior table that requires 400 Bad Request / JSON-RPC -32001 (HeaderMismatch) when the headers and body disagree.
This PR implements all three halves on the modern (2026-07-28) Streamable HTTP path:
packages/core— the codec module: scan aninputSchemaforx-mcp-headerdeclarations, encode/decode the sentinel, build the per-request header set, and validate a request'sMcp-Param-*headers against its bodyarguments.packages/client—Client.callTool()mirrors designated arguments intoMcp-Param-{Name}headers (from the most recenttools/listresult, or a caller-suppliedtoolDefinition); on a-32001rejection it refreshes the definition cache once and retries.Client.listTools()excludes tool definitions whose declarations violate the spec's constraints, logging a warning. The Streamable HTTP transport setsMcp-Name(sentinel-encoded) alongsideMcp-Method, surfaces an HTTP 400 carrying a JSON-RPC error response in-band as aProtocolError, and skips the reserved standard/auth header names in the per-requestTransportSendOptions.headerscarrier.packages/server—createMcpHandlervalidatesMcp-Param-*headers against the named tool's declarations before dispatch and rejects with400/-32001on a missing/disagreeing/malformed header.McpServer.registerToolwarns at registration time on an invalid declaration.
The legacy-era serving paths and the client's legacy-era callTool/listTools are unchanged.
How Has This Been Tested?
- Codec: 39-row fixture corpus mirroring the spec's encoding-examples, constraint, and server-behavior tables (including the two manifest checks the published conformance suite leaves globally untested).
- Client: mirroring from cached definitions and the
toolDefinitionescape hatch, paginatedtools/listaccumulation,_resetConnectionStatecache-clear, era-parity (legacycallTool/listToolsbyte-untouched), the one-refresh-on--32001retry, sentinel-encodedMcp-Name, the reserved-header guard, and 400 in-band delivery. - Server: over-HTTP rejection corpus through
createMcpHandler. - Conformance (published referee):
http-custom-headers18/0,http-invalid-tool-headers11/0,http-custom-header-server-validation9/0. - Full workspace test suite green, e2e matrix unchanged at 2544 + 205 xfail.
Breaking Changes
None on the 2025-era serving paths. On a 2026-07-28 Streamable HTTP connection: Client.listTools() excludes tool definitions whose x-mcp-header declarations violate the spec's constraints (logged); createMcpHandler rejects a tools/call whose Mcp-Param-* headers are missing for a present body value, malformed, or disagree with the body. Browser clients skip mirroring (dynamically named headers cannot be statically allow-listed for credentialed CORS), so calling an x-mcp-header tool with a non-null designated argument from a browser against a server that enforces SEP-2243 validation is a known limitation.
Types of changes
- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing functionality to change)
- Documentation update
Checklist
- I have read the MCP Documentation
- My code follows the repository's style guidelines
- New and existing tests pass locally
- I have added appropriate error handling
- I have added or updated documentation as needed
Additional context
Additive options: CallToolRequestOptions.toolDefinition (escape hatch so mirroring runs without a prior tools/list) and TransportSendOptions.headers (per-request HTTP headers; the Streamable HTTP transport skips authorization/mcp-protocol-version/mcp-method/mcp-name/mcp-session-id/content-type; transports that share a single channel — stdio, in-memory — ignore it).
Consumes the same -32001 (HeaderMismatch) shape the inbound classifier already emits for the standard-header cross-checks; the new param-header-validation rung sits at order 8 in the inbound validation ladder, evaluated pre-dispatch against the resolved tool's schema.
Spec text: a type: "number" x-mcp-header is accepted alongside integer/string/boolean because the published conformance referee ships fixtures that require it; one constant (PERMITTED_X_MCP_HEADER_TYPES) flips this strict if upstream aligns.