Skip to content

Build1 publisher3 min readPublished

x-mcp-header is a wire contract: one bad annotation invalidates the whole MCP tool

The finalized Streamable HTTP transport mirrors tool arguments into Mcp-Param-* headers. A bad suffix, an excluded type, or an unreachable annotation kills the tool definition, so validate before tools/list.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

What happened

  • On the 2026-07-28 Streamable HTTP transport, x-mcp-header is a wire contract: the client copies selected tool arguments into Mcp-Param-* headers, intermediaries can act on those headers, and the server checks them against the JSON-RPC body.
  • A bad suffix, an unsupported type, or an unreachable annotation makes the whole tool definition invalid; silently accepting it only moves the failure to a harder place to diagnose.
  • The final Streamable HTTP specification mirrors request metadata into HTTP headers so a load balancer, gateway, or WAF does not need to parse JSON-RPC.
  • A server can add x-mcp-header to a tool property, for example a string property 'region' annotated with 'Region'; a call with "region": "us-west1" then carries the header Mcp-Param-Region: us-west1.
  • The official C# SDK can generate that schema from a parameter attribute, for example [McpHeader("Region")] string region on an [McpServerTool] method.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

The finalized Streamable HTTP transport, dated 2026-07-28, makes the `x-mcp-header` annotation a wire contract rather than decorative JSON Schema metadata: the client copies selected tool arguments into `Mcp-Param-*` headers, intermediaries can act on those headers, and the server checks them against the JSON-RPC body [1]. That matters because the failure mode is coarse. A bad suffix, an unsupported type, or an unreachable annotation makes the entire tool definition invalid [2].

The point of the mirroring is to keep JSON-RPC parsing out of the network path, so a load balancer, gateway, or WAF can act on request metadata without opening the body [3]. A server annotates a property, and a call carrying `"region": "us-west1"` then also carries the header `Mcp-Param-Region: us-west1` [4]. The official C# SDK can generate that schema from a parameter attribute [5], and its v2 tool documentation covers both schema generation and automatic header projection on the stable line, so pinning a preview or release candidate is unnecessary [6].

The validity rules are narrow and worth reading as a checklist. The annotation value must be a non-empty HTTP field-name token and must be unique case-insensitively, so `Region` and `region` collide; control characters, spaces, and separators such as a colon are not valid [7]. Only `string`, `integer`, and `boolean` properties can be mirrored, `number` is excluded, and integers must stay within -(2^53 - 1) to 2^53 - 1 so every implementation represents them exactly [8], which is a range of plus or minus 9,007,199,254,740,991 [9]. Reachability is the rule most likely to bite a refactor: an annotated property may be nested, but the path from the schema root must pass only through `properties`, and an annotation below `items`, `$ref`, `oneOf`, `allOf`, `if`, or another composition or conditional keyword is invalid [10]. A Streamable HTTP client must exclude an invalid tool from the `tools/list` result and should log why [11]. A stdio client may ignore the annotations entirely, having no headers to project [12].

Encoding has its own rules. Plain visible ASCII travels as-is, while non-ASCII text, control characters, leading or trailing whitespace, and strings that already resemble the `=?base64?...?=` sentinel must be UTF-8/Base64 encoded inside that sentinel [13]. Booleans become lowercase `true` or `false`, and integral forms such as `42.0` normalize to `42` [14]. An absent or explicitly `null` optional argument means the client omits the header [15].

The dev.to write-up turns the requirements into a dependency-free .NET 10 executable that scans the relevant subschema locations, ignores annotation-shaped literal data under keywords such as `default`, records valid property paths, and fails malformed schemas before any network request [16]. Its deterministic verifier covers twelve cases, including nested primitives, absent and null arguments, sentinel and non-ASCII encoding, case-insensitive duplicates, the forbidden `number` type, annotations below `items` and `oneOf`, literal example data, invalid HTTP tokens, exponent notation, and both safe-integer boundaries [17]. Two regressions it catches compile cleanly: a server moving an annotation behind a `$ref`, and a client that stops encoding a padded or Unicode value [17].

At runtime the server decodes recognized `Mcp-Param-*` values and compares them with the body; missing, malformed, or divergent values return HTTP 400 with JSON-RPC error -32020, `HeaderMismatch` [18]. Where the mismatch suggests a stale schema, the client should refresh `tools/list` before retrying against the new definition [19]. The headers help infrastructure route, meter, and observe requests; per the same source, they do not prove that a caller is entitled to the region, tenant, or resource named in the value [20].

Watch two things in your own stack: whether your client library actually drops invalid tools and logs the reason instead of passing them through [11], and whether -32020 responses start appearing after a schema change, which is the signal that clients are holding an outdated tool list [18][19].

Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories