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
Would opt-in rejection of unknown tool arguments be useful in 2.x? #3646
Related: #3067 and the closed PR #3068. I would like to ask whether maintainers want an opt-in, per-tool policy for this behavior, while preserving the existing 2.x default. If this is better discussed in #3067, I am happy to consolidate it there.
On current main (91941ed4d3985d59def99e090baa3f880c626cc8), I reproduced a misspelled argument being silently ignored through an in-memory Client call:
importanyiofrommcpimportClientfrommcp.serverimportMCPServermcp=MCPServer("repro")
@mcp.tool()deflist_records(limit: int=10) ->int:
"""Return at most limit records."""returnlimitasyncdefmain() ->None:
asyncwithClient(mcp) asclient:
result=awaitclient.call_tool("list_records", {"limti": 1})
print(result.is_error, result.structured_content)
anyio.run(main)
Observed: False {'result': 10}. The tool runs with its default, so the caller receives no indication that the supplied parameter was not applied.
Would a configuration along these lines be useful?
@mcp.tool(extra_arguments="forbid") # proposed API, not implementeddeflist_records(limit: int=10) ->int:
returnlimit
Default remains extra_arguments="ignore" for compatibility.
Opted-in tools publish root additionalProperties: false and reject unknown top-level argument names before executing the handler.
Rejection uses the existing is_error=True tool-result path, allowing the model to correct its call.
Existing type coercion and nested model/dictionary behavior remain unchanged.
Programmatic add_tool registration would expose the same option.
I am not proposing a global default change or automatic spelling correction. Would maintainers consider this worth supporting in 2.x, and is a per-tool option the right scope? No implementation has been started.
Prepared with AI assistance; the reported behavior was reproduced locally.
Yes, this is worth supporting in 2.x, and per-tool is the right default scope. A few refinements from the client side of this behavior:
On scope: a per-tool decorator plus a server-level default policy covers both cases (a strict server, or a strict tool inside an otherwise lax server). Three states read better than a boolean: inherit / strict / lax, with inherit falling back to the server setting. Keeping ignore as the 2.x default is the right compatibility call.
On rejection semantics: is_error=True is the correct path, but the error content should name both the unknown arguments and the accepted ones, for example "unknown argument limti; accepted: limit". The model needs the accepted set to repair the call, and a generic "invalid arguments" message usually causes a second bad attempt. Distinguishing "unknown argument" from "invalid value" in the text also matters, since the repair strategies differ.
On schema shape: additionalProperties: false only guards the top level. Nested object arguments (Pydantic submodels, dict-typed parameters) need the same treatment through the generated schema, and depending on how tool schemas are assembled, $defs / $ref chains can leave the check partial. A misspelled nested field is worth an explicit test.
One boundary to draw: protocol-level metadata (progress tokens, future spec additions) does not arrive as declared tool arguments, so strictness should apply only to the declared inputSchema surface and never reject protocol frames.
The in-memory Client reproduction is a good one because it isolates server-side validation from transport behavior. If maintainers prefer to consolidate with #3067, the questions above (scope state, error content, nested coverage) are the ones a design note would need to settle first. If a warn-then-reject mode is added as a migration step, logging the offending key names in warn mode would let teams flip it to strict later with evidence.
Thanks for asking before building anything, that's exactly how we'd like new API to start.
I'm going to close this as not planned, along with #3067. A per-tool option would be new public API on tool() and add_tool(), and so far nobody has told us they need it. None of the other SDKs I checked have a dedicated setting for this either. Where you can be strict, it comes from the schema or type you already write. You can also do the check yourself today from the raw arguments, which I've described on #3067.
The thing that would help most is a real use case. If you run a server where a dropped argument has caused trouble, tell us what happened and why it mattered, here or on #3067. That's what we use to decide what to prioritise (CONTRIBUTING.md has more on this), and we're happy to reopen with that in hand.
Activity
Ben-Home commentedon Oct 6, 2026
Yes, this is worth supporting in 2.x, and per-tool is the right default scope. A few refinements from the client side of this behavior:
On scope: a per-tool decorator plus a server-level default policy covers both cases (a strict server, or a strict tool inside an otherwise lax server). Three states read better than a boolean: inherit / strict / lax, with inherit falling back to the server setting. Keeping ignore as the 2.x default is the right compatibility call.
On rejection semantics: is_error=True is the correct path, but the error content should name both the unknown arguments and the accepted ones, for example "unknown argument limti; accepted: limit". The model needs the accepted set to repair the call, and a generic "invalid arguments" message usually causes a second bad attempt. Distinguishing "unknown argument" from "invalid value" in the text also matters, since the repair strategies differ.
On schema shape: additionalProperties: false only guards the top level. Nested object arguments (Pydantic submodels, dict-typed parameters) need the same treatment through the generated schema, and depending on how tool schemas are assembled, $defs / $ref chains can leave the check partial. A misspelled nested field is worth an explicit test.
One boundary to draw: protocol-level metadata (progress tokens, future spec additions) does not arrive as declared tool arguments, so strictness should apply only to the declared inputSchema surface and never reject protocol frames.
The in-memory Client reproduction is a good one because it isolates server-side validation from transport behavior. If maintainers prefer to consolidate with #3067, the questions above (scope state, error content, nested coverage) are the ones a design note would need to settle first. If a warn-then-reject mode is added as a migration step, logging the offending key names in warn mode would let teams flip it to strict later with evidence.
maxisbey commentedon Oct 6, 2026
Thanks for asking before building anything, that's exactly how we'd like new API to start.
I'm going to close this as not planned, along with #3067. A per-tool option would be new public API on
tool()andadd_tool(), and so far nobody has told us they need it. None of the other SDKs I checked have a dedicated setting for this either. Where you can be strict, it comes from the schema or type you already write. You can also do the check yourself today from the raw arguments, which I've described on #3067.The thing that would help most is a real use case. If you run a server where a dropped argument has caused trouble, tell us what happened and why it mattered, here or on #3067. That's what we use to decide what to prioritise (CONTRIBUTING.md has more on this), and we're happy to reopen with that in hand.
AI Disclaimer
Generated by Claude Code