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 sendsMcp-Param-Regionalongside the body, and the server rejects a call where the two disagree. - A client that hasn't listed the tool has never seen the mark: it sends no header, and the call is rejected. This SDK's
Clientthen lists the tools and resends the call once, so listing first only saves a round trip. - 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.
To check the header, the SDK needs the tool's input schema before it dispatches the call. Without get_tool_input_schema it gets it by running your on_list_tools handler on every call that carries arguments, whether or not any tool is marked.
--8<-- "docs_src/header_parameters/tutorial003.py"- Pass the function to answer from what you already have.
- Return
Nonefor a tool with nothing to check.
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. get_tool_input_schemakeeps the low-levelServerfrom runningon_list_toolson every call.
The rest of the hand-written Server API is The low-level Server.