Routing without reading the body: required HTTP headers
Every POST mirrors key body fields into headers, so load balancers, gateways and firewalls can route and rate-limit without parsing JSON. If a header disagrees with the body, the request is rejected.
A bank runs one MCP endpoint for analysts worldwide. European customer data must never leave the EU.
Its execute_sql tool marks region with x-mcp-header, so every call arrives with Mcp-Param-Region: eu-west1. The edge gateway routes on that header alone and never parses the JSON body. A firewall rule blocks Mcp-Name: drop_* for contractor tokens.
Then an attacker crafts a request whose header says get_balance but whose body says transfer_funds, hoping the gateway approves one while the server runs the other. The server compares the two, finds a mismatch, and returns 400 with -32020. That one rule is why the routing is safe to trust.
MCP-Protocol-Version (around since 2025-06-18) MUST now equal the _meta protocolVersion.
Mcp-Method on every request. Mcp-Name (params.name or params.uri) on tools/call, resources/read and prompts/get.
A tool can mark a parameter with x-mcp-header. Clients MUST then mirror its value as Mcp-Param-{Name}. Values that aren't plain ASCII use =?base64?…?=.
Missing, mismatched or malformed headers get 400 and error -32020HeaderMismatch. An unknown method gets 404 with -32601.
The danger here is a load balancer routing on the header while the server executes the body. If the two could disagree, an attacker could route a "harmless" call past a policy and run a dangerous one. Strict header-body equality closes that gap.
{"name":"execute_sql","inputSchema": {"type":"object","properties": {"region": {"type":"string","description":"The region to execute the query in","x-mcp-header":"Region" },"query": { "type":"string", "description":"The SQL query to execute" } },"required": ["region", "query"] }}
Only primitive types (string, integer, boolean, never number), reachable from the schema root through properties alone. A client must drop a tool whose annotation breaks these rules.
First time here? Set up the test helper (once per terminal)
ShellSetup
# 1. In a SECOND terminal, start the reference server (Node 18+, no dependencies)curl -sO https://www.diegozuluaga.dev/mcpa/reference-server.mjsnode reference-server.mjs # http://localhost:3000/mcp, logs appear here# 2. In THIS terminal, define the helper every test uses (bash or zsh)export MCP=http://localhost:3000/mcpMETA='"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}'mcp() { # usage: mcp <method> '<json body>' [extra curl args...] curl -sS -N "$MCP" \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -H 'MCP-Protocol-Version: 2026-07-28' \ -H "Authorization: Bearer ${MCP_USER:-alice}" \ -H "Mcp-Method: $1" "${@:3}" -d "$2" \ -w '\nHTTP %{http_code}\n'}# Demo auth: the reference server treats the bearer token as the user's name.# Prefix a command with MCP_USER=bob to act as someone else.
ShellTests
# 1. Header says one tool, body says another: expect HTTP 400 and -32020mcp tools/call '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"delete_all","arguments":{},'"$META"'}}' -H 'Mcp-Name: get_weather'# 2. Missing Mcp-Name on tools/call: expect 400 and -32020mcp tools/call '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_weather","arguments":{"location":"Seattle"},'"$META"'}}'# 3. x-mcp-header: execute_sql mirrors region into Mcp-Param-Regionmcp tools/call '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"execute_sql","arguments":{"region":"eu-west1","query":"SELECT 1"},'"$META"'}}' -H 'Mcp-Name: execute_sql' -H 'Mcp-Param-Region: eu-west1'# expect: 200. Now change the header to us-west1 (or drop it): expect 400 and -32020# 4. Unknown method: expect 404 and -32601mcp nope/nope '{"jsonrpc":"2.0","id":4,"method":"nope/nope","params":{'"$META"'}}'