h3-mcp logoh3-mcp

Request Headers

Body fields mirrored into headers, validated for divergence.

Modern clients mirror key body fields into HTTP headers so a gateway can route, authorize, or rate-limit without parsing the JSON-RPC body. h3-mcp validates header-vs-body equality on every modern POST and rejects any divergence with 400 + -32020 (HeaderMismatch).

HeaderSourceRequired for
MCP-Protocol-Version_meta protocol versionall requests
Mcp-Methodmethodall requests
Mcp-Nameparams.name or params.uritools/call, resources/read, prompts/get
Mcp-Param-{Name}x-mcp-header annotationannotated tool parameters
curl -X POST http://localhost:3000/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -H 'mcp-protocol-version: 2026-07-28' \
  -H 'mcp-method: tools/call' \
  -H 'mcp-name: hello' \
  -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
        "params": { "name": "hello", "arguments": {},
        "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28",
                   "io.modelcontextprotocol/clientCapabilities": {} } } }'

#Non-ASCII Values

Values that cannot be represented as plain ASCII arrive wrapped in a sentinel — =?base64?{value}?= — and are decoded before comparison. Integer parameters must use the decimal representation (42 or 42.0); hexadecimal, exponent, and zero-padded forms are rejected rather than normalized.

#Notifications Are Checked Too

The headers are validated on every modern POST, requests and notification-shaped bodies alike. A body whose method is a request method but which carries no id is rejected with -32600 and never executed.

This closes an obvious bypass: if header checks were gated on the presence of an id, a client could drop the id and slip past a gateway that routes on Mcp-Method.

#Mirroring Tool Parameters

Annotate a tool parameter with x-mcp-header and clients will mirror it, letting an intermediary route on a value from inside arguments:

const queryTool = defineMcpTool({
  name: "execute_sql",
  inputSchema: {
    type: "object",
    properties: {
      region: { type: "string", "x-mcp-header": "Region" },
      query: { type: "string" },
    },
    required: ["region", "query"],
  },
  handler: async (args) => ({ content: [{ type: "text", text: args.region as string }] }),
});

A client then sends Mcp-Param-Region: eu-west-1 alongside the body, and a mismatch is a -32020.

x-mcp-header is only honored on string / integer / boolean properties reachable from the schema root through a chain of properties keys. Annotations that violate those constraints are ignored rather than enforced.

Important

Never annotate a sensitive value. Mirrored parameters are visible to every intermediary on the path and routinely end up in access logs. Annotate a region or a tenant id, not a token, a query, or personal data.

Note

Header/body divergence is a security control, not a formality. An intermediary may act on the header while your handler acts on the body; if the two are allowed to disagree, every gateway decision becomes advisory. That is why -32020 is never relaxed.

#Accept

When a modern POST sends Accept, it must list both application/json and text/event-stream — the server picks between them per request. Anything else is a 406.

h3-mcp logo

h3-mcp  MCP servers, built on H3.