{"slug": "mcp-x-mcp-header-validation-keep-bad-tool-schemas-out-of-tools-list", "title": "MCP x-mcp-header Validation: Keep Bad Tool Schemas Out of tools/list", "summary": "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.", "body_md": "MCP `x-mcp-header`\n\nvalidation 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-*`\n\nheaders, intermediaries can act on those headers, and the server checks them against the JSON-RPC body.\n\nI treat that contract as something to test before a tool reaches `tools/list`\n\n. 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.\n\nThe final [Streamable HTTP specification](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http) 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`\n\nto a tool property:\n\n```\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"region\": {\n      \"type\": \"string\",\n      \"x-mcp-header\": \"Region\"\n    }\n  }\n}\n```\n\nA call with `\"region\": \"us-west1\"`\n\nthen carries:\n\n```\nMcp-Param-Region: us-west1\n```\n\nThe official C# SDK can generate that schema from a parameter attribute:\n\n```\n[McpServerTool]\npublic static string ExecuteSql(\n    [McpHeader(\"Region\")] string region,\n    string query) => $\"Queued for {region}\";\n```\n\nCurrent [C# SDK v2 tool documentation](https://csharp.sdk.modelcontextprotocol.io/v2/concepts/tools/tools.html) 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.\n\nThe [final tool definition rules](https://modelcontextprotocol.io/specification/2026-07-28/server/tools#x-mcp-header) are deliberately narrow.\n\nThe annotation value must be a non-empty HTTP field-name token and must be unique without regard to case. `Region`\n\nand `region`\n\ntherefore collide. Control characters, spaces, and separators such as a colon are not valid suffix characters.\n\nOnly `string`\n\n, `integer`\n\n, and `boolean`\n\nproperties can be mirrored. JSON Schema `number`\n\nis excluded, and integer values must stay between `-(2^53 - 1)`\n\nand `2^53 - 1`\n\nso every conforming implementation can represent the value exactly.\n\nReachability 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`\n\n. An annotation below `items`\n\n, `$ref`\n\n, `oneOf`\n\n, `allOf`\n\n, `if`\n\n, or another composition or conditional keyword is invalid. A Streamable HTTP client must exclude an invalid tool from the returned `tools/list`\n\nresult and should log the reason. A stdio client may ignore these annotations because it has no HTTP headers to project.\n\nValues 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?...?=`\n\nsentinel must be UTF-8/Base64 encoded inside that sentinel. Boolean values become lowercase `true`\n\nor `false`\n\n; mathematically integral JSON forms such as `42.0`\n\nnormalize to decimal `42`\n\n. If an optional argument is absent or explicitly `null`\n\n, the client omits its header.\n\nThe [sample draft PR](https://github.com/ssukhpinder/dev-to-code-samples/pull/16) 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`\n\n, records valid property paths, and fails malformed schemas before any network request.\n\n```\nusing JsonDocument schema = JsonDocument.Parse(schemaJson);\nusing JsonDocument arguments = JsonDocument.Parse(argumentJson);\n\nvar headers = McpHeaderProjector.Project(\n    schema.RootElement,\n    arguments.RootElement);\n```\n\nThe deterministic verifier covers twelve cases, including nested primitive properties, absent and `null`\n\narguments, non-ASCII and sentinel encoding, case-insensitive duplicates, the forbidden `number`\n\ntype, annotations below `items`\n\nand `oneOf`\n\n, literal example data, invalid HTTP tokens, integral exponent notation, and both safe-integer boundaries.\n\nI like this style of test because it catches two different regressions. A server refactor can accidentally move an annotation behind a `$ref`\n\n; a client refactor can stop encoding a padded or Unicode value. Both changes compile, but both break the transport contract.\n\nAt runtime, the server has another job. It must decode recognized `Mcp-Param-*`\n\nvalues and compare them with the body. A missing, malformed, or different value is HTTP 400 with JSON-RPC error `-32020`\n\n(`HeaderMismatch`\n\n). When that mismatch suggests a stale schema, the client should refresh `tools/list`\n\nbefore retrying with the new definition.\n\nThese 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.\n\nI would never mark a password, API key, access token, or personally identifiable value with `x-mcp-header`\n\n. Base64 is only an encoding, and headers are visible to intermediaries and often copied into logs.\n\nThe 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.\n\nWhich malformed schema or encoding edge case would you add to this regression set?\n\nHappy coding!", "url": "https://wpnews.pro/news/mcp-x-mcp-header-validation-keep-bad-tool-schemas-out-of-tools-list", "canonical_source": "https://dev.to/ssukhpinder/mcp-x-mcp-header-validation-keep-bad-tool-schemas-out-of-toolslist-3j3d", "published_at": "2026-08-19 18:33:55+00:00", "updated_at": "2026-08-19 18:57:19.131523+00:00", "lang": "en", "topics": ["developer-tools", "ai-infrastructure"], "entities": ["Model Context Protocol", "C# SDK", ".NET 10", "Streamable HTTP"], "alternates": {"html": "https://wpnews.pro/news/mcp-x-mcp-header-validation-keep-bad-tool-schemas-out-of-tools-list", "markdown": "https://wpnews.pro/news/mcp-x-mcp-header-validation-keep-bad-tool-schemas-out-of-tools-list.md", "text": "https://wpnews.pro/news/mcp-x-mcp-header-validation-keep-bad-tool-schemas-out-of-tools-list.txt", "jsonld": "https://wpnews.pro/news/mcp-x-mcp-header-validation-keep-bad-tool-schemas-out-of-tools-list.jsonld"}}