anthropics / anthropics/claude-code
MCP tool-call validation rejects omission of any optional/defaulted parameter, regardless of type
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
## Summary
When calling an MCP tool whose JSON Schema declares an optional parameter (one with a `default` value), Claude Code's tool-call argument validator rejects the call if that parameter is omitted — even though the server's schema correctly marks it optional. The error is client-side, before the request reaches the MCP server:
```
Invalid arguments for tool : [{"code":"invalid_type","expected":"nonoptional","path":[""],"message":"Invalid input: expected nonoptional, received undefined"}]
```
## Reproduction
A self-hosted FastMCP-based MCP server (Python, streamable-http) exposes:
```python
def search(query: str, filter_type: str | None = None, page_size: int = 10, start_cursor: str | None = None) -> str: ...
```
FastMCP advertises `page_size` as `{"type": "number", "default": 10}` (plain, non-nullable) and `filter_type`/`start_cursor` as `{"anyOf": [{"type": "string"}, {"type": "null"}], "default": null}`. None appear in the top-level `required` array.
Calling the tool with only `query` set (letting the rest take their declared defaults) fails client-side with the error above for **every** omitted param — `page_size` included.
**`page_size` is the key data point**: it is not nullable and carries no `anyOf` union, so "the client strips `anyOf` unions" cannot explain this. It fails identically to the nullable string params, which points instead at every parameter carrying a `default` being validated as if `.optional()` was never applied to it — a `.nullable()`-without-`.optional()`-shaped gap — regardless of whether the field is actually nullable.
## What I inspected
The tool definition loaded inside my session (surfaced via the tool-search/deferred-tool mechanism) has **no `required` array at all**, e.g. for a different tool on the same server:
```json
{"properties": {"markdown": {"default": null}, "page_id": {"type": "string"}, "text": {"default": null}}, "type": "object"}
```
The upstream server's own advertised schema (verified independently via a local `tools/list` probe against the same server) correctly has `"required": ["page_id"]` and full type info (`anyOf: [string, null]`) on `text`/`markdown`. Something between the server's schema and whatever validates my tool calls drops the `required` array entirely and, for nullable fields, collapses the type info down to bare `{"default": null}`.
## Impact
Any MCP server with an optional/defaulted parameter is affected, not just this one. A caller has no way to omit such a parameter — it must be supplied explicitly, with a real value, on every call, even when the intent is "just use the default." This defeats the purpose of an optional parameter and makes tool schemas misleading: they advertise optional, the runtime enforces required.
## Workaround
None client-side. Server implementers can mitigate for **string** params by treating an empty string `""` as "not provided," but this doesn't work for `bool`/`int` params (no safe empty sentinel without risking a real `0`/`false`), and it's a per-server patch for a client-side defect.
## Environment
- Claude Code (desktop app)
- MCP server: custom FastMCP-based server (Python), streamable-http transport
- Observed 2026-09-17
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by tracing the schema from the MCP tools/list response through the tool-search/deferred-tool mechanism into the tool-call argument validator. Use the reported FastMCP schema and the omitted-parameter reproduction to verify that defaulted optional parameters are accepted while fields listed as required remain rejected when omitted.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100