You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Harden Mcp-Param validation per review: rendering as one fact, duplicate routing headers, parse-limit 500s
- A value's header rendering (or its absence) is now a single shared fact
(_render_header_scalar returns None for non-primitives and integers
beyond the int-to-str digit limit): the client omits such headers, the
validator treats a header claiming such a value as a mismatch, and a
huge-integer body can no longer raise out of the public validator.
- A non-canonical-decimal header for an integer declaration falls back to
the rendered comparison instead of rejecting outright, so the client's
own scientific-notation rendering of large integral floats (str(1e16)
== '1e+16') round-trips against this server.
- Duplicated MCP-Protocol-Version / Mcp-Method / Mcp-Name raw header
lines are rejected before classification (find_duplicated_routing_header)
- the same first-wins/last-wins divergence the Mcp-Param duplicate
rejection closes, where it matters most.
- The synthetic listing's fail-open boundary now covers the result scan
(a middleware short-circuiting tools/list with a mis-shaped result is a
logged skip, not a 500), and a mis-shaped envelope failing the listing's
surface validation logs at debug as client fault instead of an
error-level traceback blaming the tools/list handler.
- json.loads failures are caught as ValueError (an integer literal beyond
the digit limit raises the bare parent, not JSONDecodeError), keeping
unparseable bodies at 400 + PARSE_ERROR.
- migration.md: handler-raise skip is logged as an error, not a warning;
document the omitted-unrenderable-value and duplicate-line rules.
Copy file name to clipboardExpand all lines: docs/migration.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -425,13 +425,13 @@ On `ClientSession`, `call_tool` / `get_prompt` / `read_resource` still return th
425
425
426
426
### `call_tool` mirrors `x-mcp-header` arguments into `Mcp-Param-*` headers ([SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243))
427
427
428
-
For protocol 2026-07-28 over Streamable HTTP, a tool's input-schema property may carry an `x-mcp-header` annotation. When a tool the client has listed is called, each annotated argument is mirrored into an `Mcp-Param-<name>` request header (string verbatim, integer as decimal, boolean as `true`/`false`, base64-sentinel-wrapped when not header-safe; `null`/absent arguments are omitted). The argument is also left in the request body. `list_tools` caches a tool's annotations, so list a tool before calling it to enable mirroring; a tool the client never listed emits no `Mcp-Param-*` headers. Other transports ignore the annotation.
428
+
For protocol 2026-07-28 over Streamable HTTP, a tool's input-schema property may carry an `x-mcp-header` annotation. When a tool the client has listed is called, each annotated argument is mirrored into an `Mcp-Param-<name>` request header (string verbatim, integer as decimal, boolean as `true`/`false`, base64-sentinel-wrapped when not header-safe; `null`/absent arguments — and values with no scalar rendering, such as objects or arrays — are omitted). The argument is also left in the request body. `list_tools` caches a tool's annotations, so list a tool before calling it to enable mirroring; a tool the client never listed emits no `Mcp-Param-*` headers. Other transports ignore the annotation.
429
429
430
430
### Servers validate `Mcp-Param-*` headers against the request body ([SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243))
431
431
432
432
The server half of the same contract: on the 2026-07-28 Streamable HTTP path, a `tools/call` whose tool declares `x-mcp-header` annotations is validated before dispatch — each annotated argument and its mirroring `Mcp-Param-*` header must be present together and agree (after base64-sentinel decoding; integers compare numerically), or absent together. A violation is rejected with HTTP 400 and JSON-RPC error `-32020` (`HeaderMismatch`), as the spec requires. A client that sends an annotated argument *without* its header — for example one that never listed the tool — is therefore rejected instead of silently served; the spec's recovery is to re-list and retry.
433
433
434
-
There is nothing to configure. The server resolves the called tool's schema through its own registered `tools/list` handler (for `MCPServer`, the built-in one), so the validated catalog is exactly what that caller would be shown. Two consequences worth knowing: the listing runs internally on validated calls, so middleware and an expensive or paginated `tools/list` handler see extra invocations; and validation is skipped — never failing the call — when no `tools/list` handler is registered, the tool isn't in the listing, the handler raises (logged as a warning), or the call has no arguments and no `Mcp-Param-*` headers. Headers with no matching annotation are ignored; a recognized header supplied more than once is rejected. The codec and validator are public in `mcp.shared.inbound` (`decode_header_value`, `validate_mcp_param_headers`) for low-level servers hosting their own HTTP entry.
434
+
There is nothing to configure. The server resolves the called tool's schema through its own registered `tools/list` handler (for `MCPServer`, the built-in one), so the validated catalog is exactly what that caller would be shown. Two consequences worth knowing: the listing runs internally on validated calls, so middleware and an expensive or paginated `tools/list` handler see extra invocations; and validation is skipped — never failing the call — when no `tools/list` handler is registered, the tool isn't in the listing, the handler raises (logged as an error), or the call has no arguments and no `Mcp-Param-*` headers. Headers with no matching annotation are ignored; a recognized header supplied more than once is rejected, as is a duplicated `MCP-Protocol-Version`, `Mcp-Method`, or `Mcp-Name` line. The codec and validator are public in `mcp.shared.inbound` (`decode_header_value`, `validate_mcp_param_headers`) for low-level servers hosting their own HTTP entry.
435
435
436
436
Base64-sentinel decoding is strict everywhere it applies, including the `Mcp-Name` header: a `=?base64?...?=` value whose payload is not canonical base64 (wrong padding, stray characters, non-zero trailing bits) or not valid UTF-8 is rejected as malformed rather than leniently decoded.
0 commit comments