Summary
When two (or more) tool parameters are built from the same Zod schema instance (e.g. z.string().regex(...) reused for dateRangeBegin and dateRangeEnd), Inspector shows the first field as a string input and later fields as a JSON editor. Typing a normal date string such as 2026-09-27 then fails with:
Not valid JSON – this field will be omitted
The value is omitted from the tool call even though it is a valid string for the server schema.
Environment
- MCP Inspector: v2.6.0 (
@modelcontextprotocol/inspector@2.6.0)
- Client: Web UI
- Server: custom MCP server using Zod 3 (JSON Schema produced via the usual Zod → JSON Schema path used by MCP SDKs)
- Transport: Streamable HTTP
Reproduction
- Register a tool whose input schema is produced from Zod like this:
const dateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);
server.tool("example", "demo", {
dateRangeBegin: dateSchema.optional().describe("start date, yyyy-MM-dd"),
dateRangeEnd: dateSchema.optional().describe("end date, yyyy-MM-dd"),
}, handler);
- Open the tool in Inspector v2.6.0 Web UI.
- Observe:
dateRangeBegin → text input
dateRangeEnd → JSON textarea
- Type
2026-09-27 into dateRangeEnd (no quotes).
A minimal public-style schema that reproduces the same Inspector widgets (after Zod/JSON Schema conversion) looks like:
{
"type": "object",
"properties": {
"dateRangeBegin": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "start date, yyyy-MM-dd"
},
"dateRangeEnd": {
"$ref": "#/$defs/DateString"
}
},
"$defs": {
"DateString": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
}
}
}
($ref target may be #/definitions/... depending on the converter.)
Expected
Both fields are optional strings. Both should use the string control. 2026-09-27 should be sent as a string.
Actual
dateRangeEnd is treated as JSON. Unquoted 2026-09-27 is invalid JSON, so Inspector shows the error and drops the field. Wrapping it as "2026-09-27" works around the UI, but the control is still wrong.
Likely cause
Zod → JSON Schema converters deduplicate identical Zod nodes with $ref. Inspector appears to pick the widget from the top-level type of each property. A bare $ref has no type: "string", so it falls back to the JSON editor.
This is easy to hit: a shared dateSchema / timeSchema is a normal way to keep two fields consistent.
Suggested fix
When choosing a widget, resolve $ref / $defs / definitions (and anyOf / allOf wrappers from .optional()) before checking type:
- resolved
type === "string" (optional pattern / format) → string input
- resolved
type === "number" / integer → number input
- use the JSON editor only when the resolved schema is object / array / unknown
Alternatively, inline $ref for primitive schemas in the UI layer so optional strings keep type: "string".
Workaround (server-side)
Do not reuse one Zod instance. Build two separate schemas:
dateRangeBegin: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("..."),
dateRangeEnd: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("..."),
Then both properties are inlined as { "type": "string", "pattern": "..." } and Inspector shows two text inputs.
Additional context
This does not mean the MCP server defined different types. Both parameters are the same optional yyyy-MM-dd string. Reproduced on Inspector v2.6.0 Web UI against a Streamable HTTP MCP server.
Summary
When two (or more) tool parameters are built from the same Zod schema instance (e.g.
z.string().regex(...)reused fordateRangeBeginanddateRangeEnd), Inspector shows the first field as a string input and later fields as a JSON editor. Typing a normal date string such as2026-09-27then fails with:The value is omitted from the tool call even though it is a valid string for the server schema.
Environment
@modelcontextprotocol/inspector@2.6.0)Reproduction
dateRangeBegin→ text inputdateRangeEnd→ JSON textarea2026-09-27intodateRangeEnd(no quotes).A minimal public-style schema that reproduces the same Inspector widgets (after Zod/JSON Schema conversion) looks like:
{ "type": "object", "properties": { "dateRangeBegin": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$", "description": "start date, yyyy-MM-dd" }, "dateRangeEnd": { "$ref": "#/$defs/DateString" } }, "$defs": { "DateString": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" } } }(
$reftarget may be#/definitions/...depending on the converter.)Expected
Both fields are optional strings. Both should use the string control.
2026-09-27should be sent as a string.Actual
dateRangeEndis treated as JSON. Unquoted2026-09-27is invalid JSON, so Inspector shows the error and drops the field. Wrapping it as"2026-09-27"works around the UI, but the control is still wrong.Likely cause
Zod → JSON Schema converters deduplicate identical Zod nodes with
$ref. Inspector appears to pick the widget from the top-leveltypeof each property. A bare$refhas notype: "string", so it falls back to the JSON editor.This is easy to hit: a shared
dateSchema/timeSchemais a normal way to keep two fields consistent.Suggested fix
When choosing a widget, resolve
$ref/$defs/definitions(andanyOf/allOfwrappers from.optional()) before checkingtype:type === "string"(optionalpattern/format) → string inputtype === "number"/integer→ number inputAlternatively, inline
$reffor primitive schemas in the UI layer so optional strings keeptype: "string".Workaround (server-side)
Do not reuse one Zod instance. Build two separate schemas:
Then both properties are inlined as
{ "type": "string", "pattern": "..." }and Inspector shows two text inputs.Additional context
This does not mean the MCP server defined different types. Both parameters are the same optional
yyyy-MM-ddstring. Reproduced on Inspector v2.6.0 Web UI against a Streamable HTTP MCP server.