On this page
Tool dropped for an invalid x-mcp-header
[mcp-sdk] excluding tool '<name>' from tools/list: invalid x-mcp-header declaration One parameter in that tool's inputSchema carries an x-mcp-header annotation that breaks a 2026-07-28 constraint, so a conforming Streamable HTTP client drops the whole tool from tools/list and logs a warning instead of raising an error.
- MCP specification
- 2026-07-28
Is this you?
Compare what the server returns against what the client passes on. The server's tools/list response still contains the tool, so seeing it in curl and not in your client is the confirmation — and it rules out a tool that failed to register or a capability filter in front of the route.
- This error
- Step 1 lists the tool and your client does not. Step 2 then prints a path with another keyword in it —
broken: properties.regions.items.x-mcp-headeris the common one, an annotation left on an array'sitems— oroneOf,anyOf,allOf,not,if,then,else,$ref, oradditionalPropertiesin the same position. Two annotations whose values differ only in case, and an annotation on a property typednumber,objectorarray, fail the same way. If step 1 does not list the tool either, this is not your problem: the tool never registered, or something in front of the route is filtering capabilities. - Then the fix is the next section.
- Working
- Step 1 lists the tool and your client offers it too, which means no exclusion is happening. Every path step 2 prints alternates
propertiesand a property name all the way down, as inexecute_sql: properties.region.x-mcp-header, and each annotated property is typedstring,integerorboolean. - Then this isn't your problem — try the error reference.
5 other strings clients print for this
- [mcp-sdk] excluding tool '<name>' from tools/list: invalid x-mcp-header declaration — <reason>
- [mcp-sdk] tool '<name>' carries an invalid x-mcp-header declaration and will be excluded by conforming Streamable HTTP clients: <reason>
- dropping tool '<name>': invalid x-mcp-header (<reason>)
- <path>: x-mcp-header is only permitted on primitive-typed properties (string, integer, boolean); got <type>
- <path>: x-mcp-header is only permitted on properties statically reachable via a chain of 'properties' keys (not under items, additionalProperties, oneOf/anyOf/allOf/not, if/then/else, or $ref)
Dotted spans such as <url> are placeholders — your client prints your own value there.
Fix it
Find the annotation and fix the schema; the warning already names the tool and the reason. An x-mcp-header value must be a non-empty RFC 9110 token, case-insensitively unique across the schema, on a property typed string, integer or boolean — number is not permitted — and reachable from the schema root through a chain of properties keys only, never through items, oneOf/anyOf/allOf/not, if/then/else, or $ref. If the tool is not yours, read the raw tools/list response with curl: the server still returns the tool, and the client removes it after the fact.
Have your agent fix this
Copy a prompt that sends your coding agent to this page to find and apply the right fix for your project.
The cause depends on your platform. Pick yours.
Which side of the tool list can you change?
MCP TypeScript SDK
If your server or client is the MCP TypeScript SDK 2.x
- 1 Read the warning from whichever side logged it, because both sides have one and they are worded differently. The client logs
[mcp-sdk] excluding tool '<name>' from tools/list: invalid x-mcp-header declaration — <reason>when it drops the tool.McpServer.registerToolwarns at registration time instead:[mcp-sdk] tool '<name>' carries an invalid x-mcp-header declaration and will be excluded by conforming Streamable HTTP clients: <reason>. That server-side warning fires whether or not any client has connected, so it is the faster place to look. - 2 Take the reason literally — the SDK names the failing property path and the constraint. Its messages cover the five cases separately: not
statically reachable via a chain of 'properties' keys, nota non-empty string,not a valid RFC 9110 token, not on aprimitive-typedproperty, andnot case-insensitively unique. - 3 Expect the exclusion on the client to be skipped over stdio. The client applies it only on a modern-era connection that is not stdio, so the same malformed tool stays visible to a stdio client and vanishes over Streamable HTTP. The server-side registration warning does not make that distinction and is the one to trust.
- 4 Clear the client's cached listing after you fix the schema. The exclusion is applied to the cached aggregated
tools/listresult as well as to each returned page, so a still-fresh cache entry keeps the tool missing;cacheMode: 'refresh'on the call forces a fetch. - 5 Do not reach for
x-mcp-headerin a browser-resident client. The SDK skips mirroring there, because dynamically named headers cannot be statically allowlisted for credentialed CORS, and a conforming server then rejects thetools/callfor the missing header. The SDK documents this as a known limitation rather than a bug.
MCP Python SDK
If the client dropping the tool is the MCP Python SDK 2.x
- 1 Search your logs for
dropping toolrather than for the tool name. The client session logsdropping tool '<name>': invalid x-mcp-header (<reason>)at warning level as it filters the listing, and it also evicts any argument-to-header map it had cached for that tool from an earlier, valid listing. - 2 Expect the Python client to drop the tool on stdio as well. It gates the filter on the negotiated protocol version alone, with no transport carve-out, so a tool that a TypeScript client still shows over stdio disappears here. That difference is a real one between the two SDKs, not a misconfiguration in your server.
- 3 Read the reason string for the property path. The validator reports the five cases distinctly: it says the annotation was "found at a schema position not reachable via a pure properties chain" for the unreachable case, and
is only permitted on integer/string/boolean propertiesfor a wrong type. - 4 Check the annotation survived schema generation when you declare it through Pydantic. It travels as
json_schema_extra={"x-mcp-header": "Region"}on the field, and a wrapper that rebuilds the schema — an optional field widened totype: ["string", "null"], for instance — can move it somewhere the validator will not accept.
A server you don't control
If the tool belongs to someone else's server and you only consume it
- 1 Confirm the annotation is the cause before you file anything. A tool present in the raw
tools/listand absent from your client is the signature of this exclusion; a tool absent from both is a different problem entirely. - 2 Send the maintainer the property path and the reason string. The SDK warnings name both, which is most of a bug report, and a server author who only ever tested over stdio may never have seen the warning — the TypeScript client skips the exclusion on stdio, so the tool works there.
- 3 Reach the tool over stdio in the meantime, if the server offers that transport and your client is the TypeScript SDK. The exclusion is scoped to non-stdio modern connections there. The Python client applies it on every transport, so this is not a general workaround.
- 4 Pin the connection to an earlier protocol revision as the other stopgap. The rule arrived in 2026-07-28, so a client that negotiates 2025-11-25 against a server that still serves it returns the tool unfiltered. Treat that as buying days, not as a resting place.
- 5 Do not expect a capability allowlist to bring it back.
mcp-capability-filter-inboundand its equivalents decide which capabilities a route exposes; nothing in front of the client can restore a tool the client removed after reading a successful response.
Something else
Whatever the SDK, the shape is the same: the schema is the fault and the client is the enforcement point, so nothing you change at the transport or the authorization layer will bring the tool back. Read the raw tools/list response to confirm the server still publishes the tool, then find every x-mcp-header in that tool's inputSchema and check each one against the five constraints — non-empty, RFC 9110 token, case-insensitively unique, on a string, integer or boolean property, and reachable through properties keys only. Fix the schema where you can, and expect a cached listing to keep the tool hidden until it is refreshed. Where the server is not yours, send the maintainer the property path from that jq output and treat stdio or an earlier negotiated revision as the stopgap. One thing to rule out before you start: a valid annotation still adds a header to every matching tools/call, so a signing or header-allowlisting layer that did not expect Mcp-Param-* fails separately and later.
Why it happens
The 2026-07-28 revision lets a server mirror a tool argument into an Mcp-Param-{Name} HTTP header by annotating the parameter with x-mcp-header, so an intermediary can route on a value without parsing the body.
An invalid annotation is fatal to the tool rather than to the request: "Clients using the Streamable HTTP transport MUST reject tool definitions where any x-mcp-header value violates these constraints. Rejection means the client MUST exclude the invalid tool from the result of tools/list." That "ensures that a single malformed tool definition does not prevent other valid tools from being used."
Logging is only a SHOULD and nothing raises an error, so both sides behaved correctly and the sole trace is a warning on whichever stream the client logs to.
The rule is narrower than it looks. It is transport-scoped, because clients on other transports "MAY ignore x-mcp-header annotations entirely", and era-scoped, so the same server and client keep working together until they negotiate 2026-07-28.
The exchange
- Model sees the tool list
- MCP client Streamable HTTP, 2026-07-28
- MCP server
-
Both sides are conformant here. The server is entitled to publish the annotation and returns every tool it has, so a capture of this exchange shows nothing wrong.
MCP client to MCP server
POST /mcp Mcp-Method: tools/list -
MCP server to MCP client
200 OK 4 tools, one with x-mcp-header -
The exclusion happens inside the client, after a successful response. Nothing is sent back to the server, so server-side logs record a normal tools/list and the tool count the model was offered appears nowhere.
Inside MCP client
scan x-mcp-header declarations annotation under items, not properties -
MCP client to Model
3 tools (this step fails) warning logged, no error raisedFlow stops here
The headers that annotation was trying to populate exist so whatever sits in the request path can act on them, which is where the next questions land.
-
We route on a mirrored header. Can a client downgrade its way past that?
-
mcp_request_rejectedOnly if nothing checks the version. Rejections are recorded per request with a named reason, so a request arriving without the header validation the version implies is visible rather than silently trusted.
-
Which tools does this route publish at all, whatever a server lists?
-
mcp-capability-filter-inboundExactly the ones you put on it: capabilities outside the route's allowlist are refused with
MethodNotFound, so the exposed surface is a route property rather than an upstream one — andaccessControlnarrows it further per caller. -
Can we meter the tool that header was naming the tenant for?
-
complex-rate-limit-inboundYes — per tenant: named counters, each with its own limit and window, keyed on the authenticated caller, so one tenant's volume is bounded without a shared global bucket. An Enterprise policy.
-
A tool call failed on a header. Which caller, and on which tool?
-
capability_invocationBoth, from the event stream: every call carries the caller, the capability and the outcome, so a header-validation failure resolves to a client and a tool rather than to a
400in an access log. Arguments are not in there — request bodies are never logged.
All of these attach to one MCP route's policies.inbound — the same policy engine on the way in and on the way out. Your MCP server keeps
the code and the authentication it has today.
Stop debugging auth on every MCP server
A gateway in front of your MCP servers handles discovery, audience binding, and token validation once — for every server and every client.