MCP Tool Arguments Arrive as Strings? Fix Empty JSON Schemas

MCP Tool Arguments Arrive as Strings? Fix Empty JSON Schemas

Aident AI

A teal structured object passes intact through a schema gateway while an amber copy is trapped inside quotation marks.

MCP Tool Arguments Arrive as Strings? Fix Empty JSON Schemas

If an MCP tool receives "8" instead of 8, or "{\"database_id\":\"abc123\"}" instead of an object, inspect the tool's inputSchema. In a current Claude Code report, the failing property used an empty JSON Schema, {}. Replacing it with an explicit type, anyOf, oneOf, or typed $ref preserved the intended JSON type.

The shortest safe fix is:

  1. Capture the server's current tools/list response.

  2. Find parameter schemas that are exactly {} or otherwise omit every type-bearing constraint.

  3. Replace each one with the narrowest honest schema.

  4. Rebuild the server and reconnect the client so it discovers the new schema.

  5. Call the tool with a number, object, array, boolean, string, and null where each is allowed.

  6. Verify the raw tools/call JSON-RPC frame before changing server deserialization.

Expected result: structured values cross the wire as structured JSON. Do not permanently hide the problem by calling JSON.parse on every string.

Match the Exact Failure

This guide applies when all three conditions are true:

  • the MCP server advertises a parameter whose property schema is empty or untyped;

  • the client sends an object, array, number, boolean, or null as quoted text; and

  • the value is already stringified in the raw tools/call request.

The July 30, 2026 Claude Code report reproduced the behavior on version 2.1.212 with @modelcontextprotocol/sdk 1.30.0. Its controlled probe varied only the property schema. Typed integers, inline unions, typed $ref values, objects, and object unions passed. Only {} arrived as a string.

Treat that as a bounded client behavior, not a permanent rule of MCP. A different client or future Claude Code release may behave differently. Reproduce the wire-level symptom before modifying a working server.

Use a different diagnosis when:

  • tools/list itself hangs or fails;

  • an explicitly typed array or object is still stringified;

  • the server receives valid JSON but its framework coerces it later;

  • a property is missing rather than present with the wrong type; or

  • the model emitted malformed JSON or legacy XML inside a tool call.

For discovery failures, start with the MCP tools/list HTTP/2 guide. For a broader protocol boundary, see MCP vs API.

Why an Empty Schema Is a Special Case

JSON Schema defines {} as a schema that constrains nothing, allows anything, and describes nothing. That is valid JSON Schema. It is useful for validators because every JSON value passes.

An AI client needs more than validator semantics. The schema also guides the model that authors a tool call. An empty property schema gives it no signal about whether the intended value is an object, array, number, boolean, string, or null.

MCP requires each tool to expose an inputSchema, and the top-level tool arguments remain an object. The ambiguity is usually inside one property:

{
  "type": "object",
  "properties": {
    "value": {}
  },
  "required": ["value"],
  "additionalProperties": false
}

In the reported failure, the server saw this:

{
  "value": "{\"database_id\": \"abc123\"}"
}

It should have seen this:

{
  "value": {
    "database_id": "abc123"
  }
}

The report inspected the raw stdio frame before the MCP SDK parsed it. That ruled out server-side JSON decoding as the first cause in that reproduction.

Prerequisites

Before changing a tool contract, collect:

  • the client name and version;

  • the MCP SDK and server version;

  • the exact tools/list definition for the failing tool;

  • one redacted raw tools/call request;

  • the intended JSON types for the parameter; and

  • a harmless test tool or non-production target.

Do not log secrets, authorization headers, personal data, or production payloads. A type probe should use inert values such as 8 and {"database_id":"abc123"}.

Step 1: Inspect the Schema the Client Actually Discovers

Inspect the tools/list response, not only the TypeScript, Python, Go, Java, or C# source that generated it. Code-first schema libraries can produce a different contract than the source type suggests.

The official MCP Inspector shows tool schemas and can call tools with custom inputs. For a local Node server, the documented CLI shape is:

npx -y @modelcontextprotocol/inspector --cli +  node build/index.js +  --method tools/list

Pin the Inspector version in repeatable CI. For an interactive check, launch the Inspector against the same command or remote transport used by the client, open the Tools tab, and inspect the failing property.

Search the returned schema for a property shaped exactly like this:

"value": {}

Also look for schema generators that dropped a type and left only annotations that do not constrain the instance.

Expected result: you can point to the exact discovered property schema. If the wire schema is already explicit, stop and diagnose the typed-parameter failure instead.

Step 2: Replace {} With the Narrowest Honest Type

If the value must be an object, say so:

{
  "type": "object",
  "properties": {
    "database_id": {
      "type": "string"
    }
  },
  "required": ["database_id"],
  "additionalProperties": false
}

If the keys are dynamic but the value must still be an object:

{
  "type": "object",
  "additionalProperties": true
}

If the property genuinely accepts several JSON types, enumerate them:

{
  "anyOf": [
    { "type": "string" },
    { "type": "number" },
    { "type": "boolean" },
    { "type": "object", "additionalProperties": true },
    { "type": "array", "items": {} },
    { "type": "null" }
  ]
}

Use a smaller union whenever possible. If a settings field accepts only strings, numbers, and booleans, do not advertise objects or arrays. If the object has known fields, define them instead of leaving additionalProperties open.

The controlled report also found that typed $ref, anyOf, and oneOf schemas preserved types. A reference is not inherently the problem. The missing type signal is.

Expected result: every accepted shape is machine-readable, while invalid shapes fail validation before a side effect.

Step 3: Rebuild and Force Schema Rediscovery

Rebuild the MCP server using its normal package command. Then terminate the old server process and reconnect the client. A long-running client may retain an earlier tool definition for the session.

In Claude Code, first confirm the configured server:

claude mcp list

Restart the affected Claude Code session after rebuilding the server. Re-run tools/list through MCP Inspector and compare the property schema again.

Expected result: the discovered tool contract contains the explicit schema. If the old {} remains, fix the build artifact, server command, cache, or connection target before testing calls.

Step 4: Run a Type Matrix

Test the exact types your tool supports. A simple matrix catches a workaround that fixes objects but breaks numbers or literal strings.

Test value

Expected JSON type

Expected wire value

8

number

"value": 8

{"database_id":"abc123"}

object

"value": {"database_id":"abc123"}

["alpha","beta"]

array

"value": ["alpha","beta"]

true

boolean

"value": true

"8"

string

"value": "8"

null when explicitly allowed

null

"value": null

Inspect both:

  1. the raw JSON-RPC request received by the server; and

  2. the value after the MCP SDK and application validator process it.

Expected result: the raw and parsed types agree. A passing application assertion alone is insufficient if a compatibility parser silently changed the value.

Step 5: Validate Before Side Effects

Even a good schema does not make model-authored input trusted. Validate the received arguments before a file write, database mutation, shell command, or provider request.

Keep protocol validation and business validation separate:

  • JSON Schema decides whether the value has an allowed shape.

  • Application validation decides whether the value is safe and meaningful for this operation.

  • Authorization decides whether this caller may perform the operation.

  • Confirmation or approval covers the intended side effect.

This boundary matters most for polymorphic fields. An object that passes a broad schema can still contain an unsafe key, path, URL, query, or command.

Expected result: malformed or unauthorized input fails before execution, and the error identifies the field without echoing secrets.

Common Failure Modes

The schema changed, but Claude Code still sends strings

Restart both the server and the client session. Then verify tools/list again. Editing source does not prove that the running process or client cache sees the new schema.

A typed parameter is still stringified

Do not stretch this workaround to a different bug. Capture the client version, exact schema, raw request, and minimal reproduction. Current reports include separate typed-array, typed-number, and malformed-tool-call failures with different causes.

JSON.parse appears to fix everything

Parsing every string is ambiguous. A legitimate string such as "8" becomes indistinguishable from a stringified number unless the schema supplies the intended type. It can also create double-parsing bugs and bypass the validator that should own the contract.

If compatibility requires temporary coercion, scope it to one documented client/version and one parameter. Emit a deprecation signal, keep the typed schema, test both legacy and corrected calls, and remove the coercion after the client rollout is complete.

The generated schema still contains {}

Fix the source type or schema adapter that owns the contract. Avoid a second hand-written schema that can drift from runtime validation. One definition should generate discovery metadata and validate execution inputs.

The tool has no arguments

Do not use a blank top-level schema as shorthand. The MCP specification recommends:

{
  "type": "object",
  "additionalProperties": false
}

That explicitly accepts only an empty argument object.

Why the Fix Works

The fix adds type information at the boundary where the model chooses tool-call values. In the controlled Claude Code reproduction, explicit types and typed unions preserved JSON structure. An empty schema did not.

This is also why the useful debugging order is:

source type -> tools/list schema -> model-authored tool_use -> tools/call frame -> SDK parsing -> application validation

Checking the stages in order prevents a server-side workaround from hiding a discovery or generation defect.

Use the Same Discipline With Managed Actions

Large capability catalogs make schema inspection more important, not less. Aident Loadout lets an agent search for an Action, inspect its current input schema, preflight the exact inputs, and then execute through a managed integration boundary.

Read https://aident.ai/SETUP.md and install Aident Loadout in this agent. Then ask:

Find one read-only Action for a connected service.
Show me the input schema and preflight the exact request.
Execute one harmless call only after the schema is valid.
Do not expose credentials or perform a write

Measure success as one validated read-only result, no provider credential copied into the prompt, and no local repository change. For reducing a large MCP catalog before inspection, use the separate MCP token-usage guide.

Sources

Refresh this guide when Claude Code changes the behavior tracked in issue 82652, MCP changes its tool schema contract, or JSON Schema handling changes in a supported client or SDK.

Home

Home

Home

Integrations

Integrations

Integrations

Vault

Vault

Vault

Audit

Audit

Audit

Arana Grande

Arana Grande

Arana Grande

Free

Free

Free

30-day audit summary

30-day audit summary

30-day audit summary

Daily action-call volume and the latest receipts from the Loadout audit trail.

Daily action-call volume and the latest receipts from the Loadout audit trail.

Daily action-call volume and the latest receipts from the Loadout audit trail.

View Audit

View Audit

View Audit

Loadout usage

Loadout usage

Loadout usage

617 action calls in the last 30 days

617 action calls in the last 30 days

617 action calls in the last 30 days

May 19 - Jun 17

May 19 - Jun 17

May 19 - Jun 17

10 active days

10 active days

10 active days

Less

Less

Less

More

More

More

Recent activity

Recent activity

Recent activity

Latest action-call receipts from connected agents

Latest action-call receipts from connected agents

Latest action-call receipts from connected agents

Apr 23, 09:23 AM

Apr 23, 09:23 AM

Apr 23, 09:23 AM

Shopify

Shopify

Shopify

Creates Or Updates An Asset For A Theme

Creates Or Updates An Asset For A Theme

Creates Or Updates An Asset For A Theme

Success

Success

Success

Apr 23, 09:21 AM

Apr 23, 09:21 AM

Apr 23, 09:21 AM

Shopify

Shopify

Shopify

Update Products Param Product Id

Update Products Param Product Id

Update Products Param Product Id

Success

Success

Success

Apr 23, 08:53 AM

Apr 23, 08:53 AM

Apr 23, 08:53 AM

Shopify

Shopify

Shopify

Update Products Param Product Id

Update Products Param Product Id

Update Products Param Product Id

Failed

Failed

Failed

Apr 22, 22:13 PM

Apr 22, 22:13 PM

Apr 22, 22:13 PM

Shopify

Shopify

Shopify

Create Product Image

Create Product Image

Create Product Image

Success

Success

Success

Apr 22, 22:12 PM

Apr 22, 22:12 PM

Apr 22, 22:12 PM

Shopify

Shopify

Shopify

Create Product Image

Create Product Image

Create Product Image

Success

Success

Success

Connected integration coverage

Connected integration coverage

Connected integration coverage

162

162

162

of 753 accessible connected

of 753 accessible connected

of 753 accessible connected

Callable actions

Callable actions

Callable actions

1,126

1,126

1,126

Vault credentials

Vault credentials

Vault credentials

8

8

8

Explore what's possible

Explore what's possible

Explore what's possible

See all Integrations

See all Integrations

See all Integrations

Google Ads

Google Ads

Google Ads

All available Goolge Ads tools via...

All available Goolge Ads tools via...

All available Goolge Ads tools via...

X (twitter)

X (twitter)

X (twitter)

All available X tools via...

All available X tools via...

All available X tools via...

Github

Github

Github

All available Github tools via...

All available Github tools via...

All available Github tools via...

Notion

Notion

Notion

All available Notion tools via...

All available Notion tools via...

All available Notion tools via...

Slack

Slack

Slack

All available Slack tools via...

All available Slack tools via...

All available Slack tools via...

Firecrawl

Firecrawl

Firecrawl

All available Firecrawl tools via...

All available Firecrawl tools via...

All available Firecrawl tools via...

753 integrations are available for loadouts.

753 integrations are available for loadouts.

753 integrations are available for loadouts.

Plug your entire stack into your AI agents.

Plug your entire stack into your AI agents.

Plug your entire stack into your AI agents.

Skip the integration headache. Plug 750+ tools into Claude Code, Codex, and OpenClaw in one go, and let your agents execute today.

Skip the integration headache. Plug 750+ tools into Claude Code, Codex, and OpenClaw in one go, and let your agents execute today.