Most servers never need this.
A gateway or load balancer in front of your server can only route on what it can read without parsing the body. Mark a tool argument with x-mcp-header, and clients on the 2026-07-28 protocol version send its value as an HTTP header as well.
The mark is one extra key in the argument's JSON Schema. On MCPServer, Field puts it there:
--8<-- "docs_src/header_parameters/tutorial001.py"- Over Streamable HTTP on
2026-07-28, a client that has listed the tool sendsMcp-Param-Regionalongside the body, and the server rejects a call where the two disagree. - Every other connection ignores the annotation.
Your function doesn't change: region still arrives as an argument.
str, int and bool arguments. Anything else is refused when the tool is registered, with InvalidSignature.
That includes str | None, which has no single type. An optional argument needs its schema spelled out, with pydantic's WithJsonSchema:
region: Annotated[str | None, WithJsonSchema({"type": "string", "x-mcp-header": "Region"})] = NoneThere you write input_schema by hand, so the key goes straight in:
--8<-- "docs_src/header_parameters/tutorial002.py"- Nothing checks the annotation for you: an invalid one is served, and
2026-07-28clients leave the tool out of their listing.
x-mcp-headeron a tool argument makes2026-07-28clients repeat it as anMcp-Param-*HTTP header.- The server rejects a call whose header and body disagree.
- Only
str,intandboolarguments can be marked.MCPServerraisesInvalidSignaturefor anything else. - The low-level
Serverchecks nothing, and clients drop a tool whose annotation is invalid.
The rest of the hand-written Server API is The low-level Server.