Symptom
Calling a generated tool from Cursor's MCP client fails before the result reaches the model when the route's resultSchema contains a zod tuple:
MCP error -32602: Structured content does not match the tool's output schema:
data/metrics/cargo_run_ms/buckets/0/0 boolean schema is false,
data/metrics/cargo_run_ms/buckets/0/1 boolean schema is false, …
data/metrics/wait_ms_summary/quantiles/0/0 boolean schema is false, …
Seen on cargo-hauler's hauler_status (src/lib/protocol-schemas.ts:119,159: z.array(z.tuple([z.number().nullable(), z.number().int().nonnegative()]))). The server's structuredContent is correct; the client's validator rejects the schema, so the tool — and the MCP App attached to it (the dashboard) — is unusable from Cursor. Codex and Claude Code do not validate outputSchema this way and are unaffected.
Cause
src/mcp-server-runtime.ts:299,342 advertises outputSchema through Standard Schema with target: 'draft-2020-12'. For a tuple, zod v4 emits
{"type":"array","prefixItems":[{"type":"number"},{"type":"number"}],"items":false,"minItems":2,"maxItems":2}
which is correct 2020-12. Cursor validates with draft-07 semantics: prefixItems is an unknown keyword and items: false is applied to every element, hence "boolean schema is false" for each tuple member. (The MCP TS SDK's own validator and the Workbench accept it.)
Proposal
The framework controls the projection, so it can emit the one form both drafts read the same way:
- When a projected schema has
prefixItems, drop items: false (keep minItems/maxItems). A 2020-12 validator still checks the positions; a draft-07 validator sees a plain array of length 2 and passes. The only loss is "no extra items", already enforced by maxItems.
- Alternatively emit draft-07 (
items: [...], additionalItems: false) for tools' outputSchema — but that breaks strict 2020-12 validators, so the first option is the interoperable one.
- Add a route-contract diagnostic (or at least a
docs/diagnostics.md note) when a resultSchema produces a construct known not to validate in a shipping host, and a test in tests/mcp-server-runtime (or the packed contract matrix) that runs the emitted outputSchema through a draft-07 Ajv against a real result.
Until then, plugins must avoid tuples in resultSchema (e.g. z.array(z.number()).length(2)), which the docs should say.
Host reference: capability tables under packages/agent-bundle/src/adapters/capabilities/cursor-*.json should record that Cursor validates structuredContent against outputSchema with draft-07 semantics (observed on Cursor 3.18.25).
Symptom
Calling a generated tool from Cursor's MCP client fails before the result reaches the model when the route's
resultSchemacontains a zod tuple:Seen on cargo-hauler's
hauler_status(src/lib/protocol-schemas.ts:119,159:z.array(z.tuple([z.number().nullable(), z.number().int().nonnegative()]))). The server'sstructuredContentis correct; the client's validator rejects the schema, so the tool — and the MCP App attached to it (the dashboard) — is unusable from Cursor. Codex and Claude Code do not validateoutputSchemathis way and are unaffected.Cause
src/mcp-server-runtime.ts:299,342advertisesoutputSchemathrough Standard Schema withtarget: 'draft-2020-12'. For a tuple, zod v4 emits{"type":"array","prefixItems":[{"type":"number"},{"type":"number"}],"items":false,"minItems":2,"maxItems":2}which is correct 2020-12. Cursor validates with draft-07 semantics:
prefixItemsis an unknown keyword anditems: falseis applied to every element, hence "boolean schema is false" for each tuple member. (The MCP TS SDK's own validator and the Workbench accept it.)Proposal
The framework controls the projection, so it can emit the one form both drafts read the same way:
prefixItems, dropitems: false(keepminItems/maxItems). A 2020-12 validator still checks the positions; a draft-07 validator sees a plain array of length 2 and passes. The only loss is "no extra items", already enforced bymaxItems.items: [...],additionalItems: false) for tools'outputSchema— but that breaks strict 2020-12 validators, so the first option is the interoperable one.docs/diagnostics.mdnote) when aresultSchemaproduces a construct known not to validate in a shipping host, and a test intests/mcp-server-runtime(or the packed contract matrix) that runs the emittedoutputSchemathrough a draft-07 Ajv against a real result.Until then, plugins must avoid tuples in
resultSchema(e.g.z.array(z.number()).length(2)), which the docs should say.Host reference: capability tables under
packages/agent-bundle/src/adapters/capabilities/cursor-*.jsonshould record that Cursor validatesstructuredContentagainstoutputSchemawith draft-07 semantics (observed on Cursor 3.18.25).