modelcontextprotocol / modelcontextprotocol/python-sdk
MCPServer has no x-mcp-header declaration mechanism and never validates one, so an invalid annotation is served happily and dropped by every client
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- Python
- Sterne
- 24.3k
- Forks
- 4k
- Ø Merge
- 1 T. 1 Std.
- Gemergte PRs (30 T.)
- 31
Beschreibung
Summary
MCPServer offers no way to mark a tool parameter with x-mcp-header, and does not validate the annotation when one is smuggled in through pydantic. A server author who gets it wrong gets no signal at all: the tool is served happily, and every conforming client silently drops it.
SEP-2243's Reference Implementation section names this as a server-SDK requirement:
- Server SDKs: Provide a mechanism (attribute/decorator) for marking parameters with
x-mcp-header- Client SDKs: Implement the client behavior for extracting and encoding header values
- Validation: Both sides must validate header/body consistency
The client half is implemented. The server half is not: x-mcp-header appears in mcp/client/session.py, mcp/shared/inbound.py and mcp_types/_v2026_07_28/, and nowhere under mcp/server/.
1. No declaration mechanism
The only route is pydantic passthrough:
@server.tool()
async def fetch(
owner: Annotated[str, Field(json_schema_extra={"x-mcp-header": "owner"})],
) -> str:
...
This works — the annotation reaches inputSchema, the client mirrors it, mcp/shared/inbound.py validates it — so this is an ergonomics and discoverability gap rather than a functional one. But it means the feature is invisible from the server API, and that a server author must know the extension keyword's exact spelling from the spec.
2. Nothing validates the declaration server-side
This is the part that fails silently. SEP-2243 puts type restrictions on x-mcp-header and assigns their enforcement to the server:
| Test Case | Property Type | x-mcp-header Present | Expected Behavior |
| Array type |"type": "array"| Yes | Server MUST reject tool definition |
| Object type |"type": "object"| Yes | Server MUST reject tool definition |
| Null type |"type": "null"| Yes | Server MUST reject tool definition |
MCPServer rejects none of them.
import anyio
from typing import Annotated
from pydantic import Field
from mcp.client import Client
from mcp.client._memory import InMemoryTransport
from mcp.server.mcpserver import MCPServer
server = MCPServer("repro")
@server.tool()
async def bad(
tags: Annotated[list[str], Field(json_schema_extra={"x-mcp-header": "Tags"})],
) -> str:
"""An array parameter annotated x-mcp-header -- the spec says reject."""
return "ok"
async def main() -> None:
async with Client(InMemoryTransport(server), mode="auto") as client:
print("negotiated:", client.protocol_version)
result = await client.list_tools()
print("tools the client kept:", [t.name for t in result.tools])
anyio.run(main)
Output on mcp 2.1.1:
WARNING dropping tool 'bad': invalid x-mcp-header (property 'tags':
x-mcp-header is only permitted on integer/string/boolean
properties (got 'array'))
negotiated: 2026-07-28
tools the client kept: []
Registration succeeded, startup succeeded, tools/list served it. The client — correctly, per the client-side MUST — drops it. So the failure mode is a tool that exists on the server and is invisible to every client, with the only diagnostic emitted in the client's process, which in a real deployment belongs to someone else.
The validator that would catch this already exists and is already imported by the server package's transport: find_invalid_x_mcp_header in mcp/shared/inbound.py. It is simply never run against a tool the server itself is registering.
Suggested fixes
- Run
find_invalid_x_mcp_headerat tool-registration time and raise. This is the one that matters: it turns a silent cross-process failure into an error at the line that caused it, and it reuses code that is already there. - A first-class declaration API, so the extension keyword does not have to be spelled by hand — whatever shape fits the SDK's conventions, e.g.
Annotated[str, McpHeader("Region")].
Happy to open a PR for (1) if the direction is agreeable.
Environment
mcp2.1.1,mcp-types2.1.1, Python 3.12.9- Both reproductions negotiate
2026-07-28
Beitragsleitfaden
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Rechercherichtung
Beginne mit find_invalid_x_mcp_header in mcp/shared/inbound.py und verfolge, wie Tools unter mcp/server/ registriert werden. Füge Abdeckung für ungültige Array-, Objekt- und Null-Deklarationen hinzu und überprüfe anschließend, dass die Registrierung sie ablehnt, anstatt Tools bereitzustellen, die Clients stillschweigend verwerfen.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- python
- Bereich
- api, backend
- Issue-Typ
- Bug
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Aktivitätsstatus
- Aktiv
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 55/100