cd /news/developer-tools/mcp-x-mcp-header-validation-keep-bad… · home topics developer-tools article
[ARTICLE · art-103344] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

MCP x-mcp-header Validation: Keep Bad Tool Schemas Out of tools/list

A developer has published a validation guide for the MCP x-mcp-header annotation, which mirrors tool arguments into HTTP headers on the Streamable HTTP transport. The guide details strict rules for header names, supported types, and reachability, and includes a dependency-free .NET 10 verifier to catch invalid tool schemas before they reach tools/list. The developer emphasizes that silently accepting bad schemas only moves failures to harder-to-diagnose places.

read4 min views3 publishedAug 19, 2026

MCP x-mcp-header

validation is easy to miss because the annotation looks like ordinary JSON Schema metadata. On the 2026-07-28 Streamable HTTP transport, it 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.

I treat that contract as something to test before a tool reaches tools/list

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

{
  "type": "object",
  "properties": {
    "region": {
      "type": "string",
      "x-mcp-header": "Region"
    }
  }
}

A call with "region": "us-west1"

then carries:

Mcp-Param-Region: us-west1

The official C# SDK can generate that schema from a parameter attribute:

[McpServerTool]
public static string ExecuteSql(
    [McpHeader("Region")] string region,
    string query) => $"Queued for {region}";

Current C# SDK v2 tool documentation describes both schema generation and automatic header projection. The feature is on the stable v2 line; it is not necessary to pin an earlier preview or release candidate.

The final tool definition rules are deliberately narrow.

The annotation value must be a non-empty HTTP field-name token and must be unique without regard to case. Region

and region

therefore collide. Control characters, spaces, and separators such as a colon are not valid suffix characters.

Only string

, integer

, and boolean

properties can be mirrored. JSON Schema number

is excluded, and integer values must stay between -(2^53 - 1)

and 2^53 - 1

so every conforming implementation can represent the value exactly.

Reachability is the rule most likely to surprise me. An annotated property can be nested, but the path from the schema root must pass only through properties

. An annotation below items

, $ref

, oneOf

, allOf

, if

, or another composition or conditional keyword is invalid. A Streamable HTTP client must exclude an invalid tool from the returned tools/list

result and should log the reason. A stdio client may ignore these annotations because it has no HTTP headers to project.

Values have their own encoding rules. Plain visible ASCII can travel as-is. Non-ASCII text, control characters, leading or trailing whitespace, and strings that already look like the =?base64?...?=

sentinel must be UTF-8/Base64 encoded inside that sentinel. Boolean values become lowercase true

or false

; mathematically integral JSON forms such as 42.0

normalize to decimal 42

. If an optional argument is absent or explicitly null

, the client omits its header.

The sample draft PR turns those requirements into a dependency-free .NET 10 executable. It scans the relevant JSON Schema subschema locations, ignores annotation-shaped literal data under keywords such as default

, records valid property paths, and fails malformed schemas before any network request.

using JsonDocument schema = JsonDocument.Parse(schemaJson);
using JsonDocument arguments = JsonDocument.Parse(argumentJson);

var headers = McpHeaderProjector.Project(
    schema.RootElement,
    arguments.RootElement);

The deterministic verifier covers twelve cases, including nested primitive properties, absent and null

arguments, non-ASCII and sentinel encoding, case-insensitive duplicates, the forbidden number

type, annotations below items

and oneOf

, literal example data, invalid HTTP tokens, integral exponent notation, and both safe-integer boundaries.

I like this style of test because it catches two different regressions. A server refactor can accidentally move an annotation behind a $ref

; a client refactor can stop encoding a padded or Unicode value. Both changes compile, but both break the transport contract.

At runtime, the server has another job. It must decode recognized Mcp-Param-*

values and compare them with the body. A missing, malformed, or different value is HTTP 400 with JSON-RPC error -32020

(HeaderMismatch

). When that mismatch suggests a stale schema, the client should refresh tools/list

before retrying with the new definition.

These headers help infrastructure route, meter, and observe requests. They do not prove that a caller may use the region, tenant, or resource named in the value. An attacker who can choose the body can usually choose the matching header too, so the application still needs normal authentication and authorization checks. A gateway enforcing policy on mirrored headers should reject an absent or older protocol version, where header/body validation is not guaranteed.

I would never mark a password, API key, access token, or personally identifiable value with x-mcp-header

. Base64 is only an encoding, and headers are visible to intermediaries and often copied into logs.

The sample is also a focused conformance fixture, not a full JSON Schema 2020-12 engine or a replacement for the official SDK. Its value is keeping the sharp transport rules visible in tests. For production, use a current SDK, validate header/body equality on the server, and keep authorization tied to the authenticated principal.

Which malformed schema or encoding edge case would you add to this regression set?

Happy coding!

── more in #developer-tools 4 stories · sorted by recency
── more on @model context protocol 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/mcp-x-mcp-header-val…] indexed:0 read:4min 2026-08-19 ·