Skip to content

feat: add type coercion for tool arguments#1739

Open
martingarramon wants to merge 2 commits intomodelcontextprotocol:mainfrom
martingarramon:feat/tool-arg-type-coercion
Open

feat: add type coercion for tool arguments#1739
martingarramon wants to merge 2 commits intomodelcontextprotocol:mainfrom
martingarramon:feat/tool-arg-type-coercion

Conversation

@martingarramon
Copy link

Problem

LLM models frequently send string values for non-string tool parameters — e.g. "42" instead of 42, "true" instead of true. This causes schema validation failures that tool authors have to work around individually (as in #1111 with z.coerce.number() for expires_in).

Solution

Adds a coerceToolArgs() utility in packages/core that applies safe, conservative type coercions based on the tool's JSON Schema before validateStandardSchema() runs. This works regardless of which schema library (Zod, Valibot, ArkType) the tool author chose.

Coercion rules follow the AJV coercion table:

  • string → number/integer: Number() only if Number.isFinite() and non-empty
  • string → boolean: exact "true"/"false" only (no truthy coercion)
  • number/boolean → string: String()
  • Nested objects with properties: recursive coercion
  • Arrays, oneOf, anyOf: skipped (too ambiguous to coerce safely)

Hook point: validateToolInput() in mcp.ts, one line before the existing validateStandardSchema call.

Testing

12 test cases in test/integration/test/issues/test1361.tool-arg-type-coercion.test.ts:

  • String → number, string → integer (with truncation), string → boolean (true/false), number → string, boolean → string
  • Nested object coercion
  • Correctly-typed passthrough (no mutation)
  • Optional params with coercion
  • Negative cases: non-numeric strings, non-boolean strings ("yes"), empty strings all correctly rejected

All existing tests pass. pnpm build:all && pnpm lint:all && pnpm test:all clean (only pre-existing Cloudflare Workers env test skipped).

Fixes #1361

LLM models frequently send string values for non-string tool parameters
(e.g. "42" instead of 42, "true" instead of true). This adds a
coerceToolArgs() utility that applies safe, conservative type coercions
based on the JSON Schema before schema validation runs.

Coercion rules follow the AJV coercion table:
- string → number/integer: Number() only if finite and non-empty
- string → boolean: exact "true"/"false" only
- number/boolean → string: String()
- Nested objects: recursive coercion

Fixes modelcontextprotocol#1361

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@martingarramon martingarramon requested a review from a team as a code owner March 25, 2026 01:27
@changeset-bot
Copy link

changeset-bot bot commented Mar 25, 2026

🦋 Changeset detected

Latest commit: ea60e9b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@modelcontextprotocol/core Patch
@modelcontextprotocol/server Patch
@modelcontextprotocol/node Patch
@modelcontextprotocol/express Patch
@modelcontextprotocol/hono Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider type coercion for tool arguments

1 participant