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
{{ message }}
Repository navigation
Pydantic model parameter not flattened in MCP tool schema — breaks SDK clients that send flat argument dicts #3637
When a FastMCP tool is defined with a Pydantic model as its parameter, FastMCP 4.x generates a JSON schema that wraps the model's fields under a "request" key rather than flattening them to the top level. This breaks MCP clients that send the fields flat — including the official adcp Python SDK, which serialises request objects with model.model_dump() and sends the result as a flat dict.
{ "adcp_version": "3.2", "protocols": null }
FastMCP validates this against the schema and raises:
1 validation error for call[get_adcp_capabilities]
adcp_version
Unexpected keyword argument [type=unexpected_keyword_argument, ...]
What should happen:
Either:
FastMCP should flatten the fields of a Pydantic model parameter into the tool's top-level inputSchema (matching how bare field: type = default kwargs work today), or
FastMCP should accept both the flat form {"adcp_version": "3.2"} and the nested form {"request": {"adcp_version": "3.2"}} when the parameter is a Pydantic model.
The spec (GetAdcpCapabilitiesRequest) already defines the full wire shape including extra: 'allow' for forward compatibility. Using the model type as the parameter should be the idiomatic approach — it avoids re-declaring every field individually.
Workaround: Redeclare every field from the Pydantic model as individual kwargs on the tool function (defeats the purpose of having the model type).
Example Code
frompydanticimportBaseModelfrommcp.server.fastmcpimportFastMCPmcp=FastMCP("example")
classMyRequest(BaseModel):
model_config= {"extra": "allow"}
adcp_version: str|None=Noneprotocols: list[str] |None=None@mcp.tool()asyncdefmy_tool(request: MyRequest=MyRequest()) ->str:
returnf"got: {request.adcp_version}"# Client sends: {"adcp_version": "3.2"}# FastMCP raises: Unexpected keyword argument 'adcp_version'# Client must send: {"request": {"adcp_version": "3.2"}}# — but the SDK and spec generate flat dicts from model_dump()
Confirm scope: reproduced unexpected_keyword_argument in standalone FastMCP 4.0.3; SDK 2.x MCPServer instead ignores flat fields when the model parameter has a default.
Preserve the documented nested contract; clarify model envelopes and the low-level Server route for flat wire schemas. Draft docs: clarify Pydantic tool argument envelopes #3638 contains documentation and regression coverage; 90 relevant tests passed locally.
If flat/dual-shape runtime support is preferred, agree on the published schema and conflict rules first, while preserving existing nested callers.
Investigation and draft prepared with Codex assistance.
The wrapper appears mechanically (the parameter name becomes a property), but the design question is which shape the schema should advertise, and that should be settled by what clients actually send. model_dump() produces the flat dict, your client and the reference SDK both serialize flat, so the schema and the serialization disagree today. That is the bug, and it means the flatten direction is the correct default for a top-level model parameter, with two guardrails:
Flatten only when it is unambiguous. A single model parameter can be treated like a body object and flattened. Two model parameters cannot, because flattened field names would collide and the namespacing is lost. Detect the unambiguous case and keep the wrapper otherwise.
Decide alias semantics deliberately. There is already a validation-mode versus serialization-mode split for aliases and computed fields in this area; whichever mode governs the flattened property names needs to be chosen explicitly, or the aliased-field class reappears inside the new shape.
Accepting both shapes is worth having as a transitional shim, not as the contract: silently normalizing both forms means two wire formats for the same tool, which is exactly the ambiguity that makes cross-SDK interop painful. If you do it, do it loudly (accept both for a deprecation window, emit a server-side warning when the nested form arrives).
A test matrix that would pin this down: model as the only parameter; model plus primitive parameters; optional model with a default; aliased fields under both modes; extra set to allow. The current workaround of redeclaring every field as kwargs is the right escape hatch, but it is precisely the duplication the model type exists to remove.
This is the same rule we follow for connector schemas around corpusiq.io: generate the schema from the same serialization path that produces the payload, never a parallel one.
Thanks for the report. I'm going to close this one, because the error you're seeing comes from the standalone FastMCP package (4.0.3 in your environment) rather than this SDK. Unexpected keyword argument is raised by its own argument validation, so https://github.com/PrefectHQ/fastmcp is the right place for it.
Activity
aritejhg commentedon Oct 3, 2026
Could I take this up with the following approach?
unexpected_keyword_argumentin standalone FastMCP 4.0.3; SDK 2.xMCPServerinstead ignores flat fields when the model parameter has a default.Serverroute for flat wire schemas. Draft docs: clarify Pydantic tool argument envelopes #3638 contains documentation and regression coverage; 90 relevant tests passed locally.Investigation and draft prepared with Codex assistance.
Ben-Home commentedon Oct 5, 2026
The wrapper appears mechanically (the parameter name becomes a property), but the design question is which shape the schema should advertise, and that should be settled by what clients actually send.
model_dump()produces the flat dict, your client and the reference SDK both serialize flat, so the schema and the serialization disagree today. That is the bug, and it means the flatten direction is the correct default for a top-level model parameter, with two guardrails:Accepting both shapes is worth having as a transitional shim, not as the contract: silently normalizing both forms means two wire formats for the same tool, which is exactly the ambiguity that makes cross-SDK interop painful. If you do it, do it loudly (accept both for a deprecation window, emit a server-side warning when the nested form arrives).
A test matrix that would pin this down: model as the only parameter; model plus primitive parameters; optional model with a default; aliased fields under both modes;
extraset to allow. The current workaround of redeclaring every field as kwargs is the right escape hatch, but it is precisely the duplication the model type exists to remove.This is the same rule we follow for connector schemas around corpusiq.io: generate the schema from the same serialization path that produces the payload, never a parallel one.
maxisbey commentedon Oct 6, 2026
Thanks for the report. I'm going to close this one, because the error you're seeing comes from the standalone FastMCP package (4.0.3 in your environment) rather than this SDK.
Unexpected keyword argumentis raised by its own argument validation, so https://github.com/PrefectHQ/fastmcp is the right place for it.AI Disclaimer
Generated by Claude Code