Coerce string→number/boolean on MCP tool params (P2)#169
Merged
Conversation
Several MCP clients (and a lot of LLM tool-call layers) serialize every
argument as a string before transport. A tool with a numeric param
generated from OpenAPI 'type: integer' was rejecting valid calls like
{ top_k: '5' }
→ 'Input validation error: expected number, received string'
Fix: in jsonSchemaToZod, use z.coerce.number(), z.coerce.number().int(),
z.coerce.boolean(), and z.coerce.date() (for string fields with
format: date / date-time). Coercion still rejects non-numeric strings,
so the validation signal where it matters is preserved.
Also defensive: openapi-3.1-normalizer now strips info.summary, a 3.1-only
field that 3.0 schema validation rejects. We currently use dereference()
on 3.1 docs (no validation), so this is preventive — if a future code
path calls validate() the relabeled 3.0.3 document stays accepted.
Tests: 9 new specs for the coerce paths (number, integer, float,
boolean, date-time, enum strict, optional, plain string, negative-case
'abc' rejection). 2 new specs for the info.summary strip. Full suite
643 passing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Many MCP clients (and most LLM tool-call layers) serialize every argument as a string before transport. A tool generated from an OpenAPI `type: integer` parameter was rejecting valid calls like:
```
{ "top_k": "5" }
→ "Input validation error: expected number, received string"
```
This blocked the `koch-filesystem-bridge` integration on real client traffic — the Claude desktop MCP path was always passing `top_k` as a string.
Fix
In `McpServerService.jsonSchemaToZod`, switch the numeric / boolean / date paths to use `z.coerce.*`:
Coercion still rejects non-coercible strings (e.g. `"abc"` for integer, `"1.5"` for integer), so we keep the validation signal where it matters. Enum strings stay strict.
Defensive side-fix
`openapi-3.1-normalizer` now also strips `info.summary` — a 3.1-only field that the 3.0 schema validator rejects. We currently use `dereference()` on 3.1 docs (no validation), so this is a preventive cleanup: if any future code path calls `validate()` the relabeled 3.0.3 document stays accepted.
Test plan