Repository navigation
Description
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 inputdateRangeEnd→ JSON textarea
- Type
2026-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}$"
}
}
}($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"(optionalpattern/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.
Activity
Ben-Home commentedon Sep 12, 2026
cliffhall commentedon Sep 16, 2026
Triage: Priority Medium (total 6)
$ref'd primitive renders as a JSON editor and its value is dropped; quoting the value is a partial workaroundbuglabelBoard: #28, Status
Incoming(awaiting maintainer review — no milestone yet).Merge pull request #2391 from modelcontextprotocol/v2/fix/2321-resolv…
cliffhall commentedon Sep 16, 2026
Fixed by #2391 (merged to v2/main).
fix: inline same-document $refs before choosing form widgets (modelco…