claude-code - 💡(How to fix) Fix [Bug] MCP tool-call arguments coerced to JSON-string on the wire regardless of schema type

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…

The Claude Code MCP client appears to coerce non-string scalar inputs (numbers, booleans, arrays, objects) to their JSON-string representation before sending tool-call payloads to the MCP server, regardless of the tool's declared input schema type. This makes schema-side type declarations cosmetic for serialization purposes — they may appear in the catalog view via ToolSearch but don't affect wire behavior.

Error Message

  • A. Validate-and-reject: Tool A receives value: 42 (number) → client rejects the call as schema-violating (42 is not a string), surfacing the schema mismatch to the caller LLM as a clear error.
  • B. Pass-through: Tool A receives value: 42 (number) → client forwards 42 as a JSON-number; server's untyped/lenient handlers see actual numbers, typed handlers can return a clear error from their own validation.

Root Cause

I spent 5 rounds of release cycles on a downstream plugin (ws404-thegem-mcp v0.4.6 → v0.4.9, ~3 weeks of audit work) trying to leverage schema-side type declarations to enforce input refusal on a server-side check. The fixes always shipped looking correct (catalog view via ToolSearch showed the type field) but had no behavioral effect. Empirical verification only came from a two-tool discriminator test (described below) that should have been the first step.

RAW_BUFFERClick to expand / collapse

Summary

The Claude Code MCP client appears to coerce non-string scalar inputs (numbers, booleans, arrays, objects) to their JSON-string representation before sending tool-call payloads to the MCP server, regardless of the tool's declared input schema type. This makes schema-side type declarations cosmetic for serialization purposes — they may appear in the catalog view via ToolSearch but don't affect wire behavior.

Reproduction

Tested against AI Engine Pro 3.4.8 (a WordPress MCP server) on 2026-05-10. Two tools registered on the same MCP server:

  • Tool A: set_context_settings with value: { type: "string" }
  • Tool B: set_theme_options with value: { /* no type declared */ }

Test inputs:

  1. Send value: 42 (number literal) to both tools.
  2. Send value: ["a", "b"] (array literal) to both tools.

Observed

Both tools' server-side handlers received string values in every case:

  • 42 → arrived as "42" in PHP, regardless of schema.
  • ["a","b"] → arrived as "[\"a\",\"b\"]" (JSON-encoded string).

The schema-declared type: "string" on Tool A had no effect — same coercion happened on the untyped Tool B.

Expected

Either:

  • A. Validate-and-reject: Tool A receives value: 42 (number) → client rejects the call as schema-violating (42 is not a string), surfacing the schema mismatch to the caller LLM as a clear error.
  • B. Pass-through: Tool A receives value: 42 (number) → client forwards 42 as a JSON-number; server's untyped/lenient handlers see actual numbers, typed handlers can return a clear error from their own validation.

The current silent coercion to string-of-JSON is the worst of both worlds: schema appears to enforce a type but doesn't, and the server-side handler can't distinguish "caller passed a number" from "caller passed a string-shaped number."

Why this matters

I spent 5 rounds of release cycles on a downstream plugin (ws404-thegem-mcp v0.4.6 → v0.4.9, ~3 weeks of audit work) trying to leverage schema-side type declarations to enforce input refusal on a server-side check. The fixes always shipped looking correct (catalog view via ToolSearch showed the type field) but had no behavioral effect. Empirical verification only came from a two-tool discriminator test (described below) that should have been the first step.

Discriminator (proposed regression test for the harness)

If Claude Code's MCP client adopts validate-and-reject behavior in a future version, downstream consumers should run this test to detect the change:

  1. Pick a tool with value: { type: "string" } and a control tool with no type declaration on the comparable param.
  2. Send value: 42 to both.
  3. If both arrive as "42" (string) → current silent-coercion behavior.
  4. If typed tool errors and control accepts 42 → validate-reject behavior.

Related upstream issue

There's an adjacent issue on the AI Engine (server) side: normalize_input_schema appears to silently strip ALL union-array type declarations (e.g., type: ["string","number","boolean"]), not just the documented ["string","object"] / ["string","array"] patterns that "break ChatGPT." Already filed with Meow Apps; this report is specifically about the client-side coercion behavior.

Environment

  • Claude Code (current as of 2026-05-20)
  • AI Engine Pro 3.4.8 (target MCP server)
  • WordPress 6.x on PHP 8.1
  • Single-admin connection over the MCP transport

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