claude-code - 💡(How to fix) Fix [BUG] MCP client drops a trailing Optional[str] tool argument from tools/call (present in schema, lost on the wire)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

When Claude Code calls an MCP server tool whose signature has an Optional[str] = None parameter positioned after a parameter that has a non-None default, the value supplied for that trailing optional parameter does not reach the server. The tool receives the parameter as its default (None), even though a value was provided in the tool call and the parameter is present in the tool's advertised input schema. Sibling tools whose optional parameters follow only required parameters deliver their values correctly.

Error Message

Tools relying on such a parameter silently receive the default, so any behavior gated on it never runs and no error is surfaced.

Root Cause

When Claude Code calls an MCP server tool whose signature has an Optional[str] = None parameter positioned after a parameter that has a non-None default, the value supplied for that trailing optional parameter does not reach the server. The tool receives the parameter as its default (None), even though a value was provided in the tool call and the parameter is present in the tool's advertised input schema. Sibling tools whose optional parameters follow only required parameters deliver their values correctly.

Code Example

from typing import Optional
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("repro")

@mcp.tool()
def echo_args(summary: str, observation_type: str = "general",
              related_item_ids: Optional[str] = None) -> str:
    return (f"summary={summary!r} "
            f"observation_type={observation_type!r} "
            f"related_item_ids={related_item_ids!r}")

if __name__ == "__main__":
    mcp.run()

---

{ "mcpServers": { "repro": { "command": "python", "args": ["/abs/path/repro_server.py"] } } }
RAW_BUFFERClick to expand / collapse

Summary

When Claude Code calls an MCP server tool whose signature has an Optional[str] = None parameter positioned after a parameter that has a non-None default, the value supplied for that trailing optional parameter does not reach the server. The tool receives the parameter as its default (None), even though a value was provided in the tool call and the parameter is present in the tool's advertised input schema. Sibling tools whose optional parameters follow only required parameters deliver their values correctly.

Environment

  • Claude Code: 2.1.126
  • OS: macOS (Darwin 25.5.0)
  • MCP server: Python, mcp 1.26.0 (FastMCP), stdio transport

Reproduction

Minimal server:

from typing import Optional
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("repro")

@mcp.tool()
def echo_args(summary: str, observation_type: str = "general",
              related_item_ids: Optional[str] = None) -> str:
    return (f"summary={summary!r} "
            f"observation_type={observation_type!r} "
            f"related_item_ids={related_item_ids!r}")

if __name__ == "__main__":
    mcp.run()

.mcp.json:

{ "mcpServers": { "repro": { "command": "python", "args": ["/abs/path/repro_server.py"] } } }

Then have Claude Code call echo_args with all three arguments, e.g. summary=\"x\", observation_type=\"y\", related_item_ids=\"a,b\".

Expected

related_item_ids='a,b'

Actual

related_item_ids=None — the supplied value is absent and the tool runs with its default.

What rules out the server

  • The tool's advertised input schema (generated by mcp 1.26.0) includes related_item_ids.
  • Calling the underlying function directly (bypassing the MCP transport) with the value works as expected.
  • A sibling tool with signature (item_id: str, label: Optional[str] = None, body: Optional[str] = None) receives label and body correctly over the same connection.
  • The failing tool differs only by having a defaulted non-Optional parameter (observation_type: str = \"general\") between the required parameter and the trailing optional one.

Suspected trigger (unconfirmed)

An Optional parameter that follows a parameter with a non-None default may be omitted from the outgoing tools/call arguments object. This is inferred by eliminating the server; I did not capture the raw JSON-RPC tools/call payload. Confirmation via claude --debug (inspecting the outgoing tools/call arguments) would verify whether the field is absent on the wire.

Impact

Tools relying on such a parameter silently receive the default, so any behavior gated on it never runs and no error is surfaced.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING