[BUG] MCP tool schema mangled for locally-configured stdio servers (Optional[str] params)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When a local (stdio) MCP server exposes a tool parameter typed as Optional[str] (e.g. param: str | None = None), the server correctly advertises its JSON Schema as anyOf: [{"type":"string"},{"type":"null"}] (verified directly on the server). But when that tool definition reaches the model through Claude, the parameter's schema arrives as an empty object {}, with no type information at all.
As a result, when I pass a digit-only string for that parameter (e.g. a numeric account ID like "4608023173"), it gets serialized as a JSON number instead of a JSON string in the tools/call request. The server's Pydantic validation then rejects it, and there is no way to pass the value correctly — any non-numeric-looking string (with dashes, spaces, etc.) survives as a string but is then rejected by the downstream API for a different reason (it expects a pure-digit string).
What Should Happen?
The full JSON Schema for Optional/Union-typed parameters, as correctly advertised by the MCP server (e.g. anyOf: [{"type":"string"},{"type":"null"}]), should be preserved when relayed to the model, so arguments can be serialized with the correct JSON type instead of guessing the type from the value's content.
Error Messages/Logs
1 validation error for call[execute_gaql]
login_customer_id
Input should be a valid string [type=string_type, input_value=1234567890, input_type=int]
For further information visit https://errors.pydantic.dev/2.13/v/string_type
---
Regression evidence from ~/Library/Logs/Claude/mcp-server-<name>.log:
The same tool/server had several successful execute_gaql calls (string parameter handled correctly, no
errors) both two days earlier and earlier the same day I hit the bug, from what appears to be a regular
Claude Desktop chat session. A Claude Code session started shortly after, and every execute_gaql call from
that Claude Code session has failed with the type error above since then.
The failing calls don't even appear in the server's own log (no matching tools/call entry) — they never
reach the local server process, meaning the request is rejected earlier, client-side, specifically within
the Claude Code call path.
Steps to Reproduce
- Configure a local stdio MCP server (Python, fastmcp) exposing a tool with a parameter typed
param: str | None = None (Optional[str], no other type in the union).
- Confirm server-side that fastmcp generates the correct schema for it: anyOf: [{"type":"string"},{"type":"null"}].
- From a Claude Code session, ask Claude to call the tool passing a digit-only string for that parameter,
- The call fails with a Pydantic error showing the value was sent as a JSON integer, not a string:
Input should be a valid string [type=string_type, input_value=*number*, input_type=int].
- For comparison, calling the exact same tool with the exact same parameter from a regular Claude Desktop
chat conversation (not Claude Code) works correctly — confirmed on the same server, same day.
This appears specific to the Claude Code execution path, not a general Claude Desktop regression.
Claude Model
Sonnet (default)
Is this a regression?
Yes, this worked in a previous version
Last Working Version
Unknown exact app version — last confirmed working 2026-05-28, per local MCP server logs. Failure onset correlates with a new "Era probe verdict: legacy (exec lane pinned...)" log line first appearing 2026-08-18.
Claude Code Version
2.1.207 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
This affects any locally-configured stdio MCP server with an Optional/Union-typed tool parameter, not specific
to one server implementation — the schema loss happens on the Claude Desktop side when relaying local server
tool definitions to the model.