Skip to content

Generated schema leaves input unions unconstrained and omits required resultType discriminators #14

Description

@AndresSaa

While implementing the server-side Task Store role for io.modelcontextprotocol/tasks, I used schema/draft/schema.json for runtime validation. Two constraints described by the TypeScript source and normative prose are absent from the generated JSON Schema, and both matter to an implementation that is not written in TypeScript.

This report uses ext-tasks commit 2c1425d9a288b9b1f489430fe1e00bb392b47e48: schema.json (blob d6ccaff7e3fb2131b5d752dd8b6f34096e58e976) and schema.ts (blob 2634c47c2b25ac8fafe7fadaa7dd3f3b732c0abc).

1. InputRequest and InputResponse collapse to “anything”

schema.ts declares:

type InputRequest = CreateMessageRequest | ListRootsRequest | ElicitRequest;
type InputResponse = CreateMessageResult | ListRootsResult | ElicitResult;

Those names come from @modelcontextprotocol/sdk/types.js. The current generation does not inline their definitions, so in schema.json both become:

{ "anyOf": [{}, {}, {}] }

Expected: InputRequest and InputResponse contain structural constraints for their three permitted request or response families.

Actual: each exact definition is {"anyOf":[{},{},{}]}. Because an empty JSON Schema accepts every instance, these definitions also accept a value such as { "definitely": "not an MCP request" }.

2. resultType appears nowhere in the generated schema

The extension prose and the JSDoc in schema.ts require CreateTaskResult.resultType to be "task", and require "complete" for GetTaskResult, UpdateTaskResult, and CancelTaskResult.

Expected: CreateTaskResult requires resultType with the constant "task", and the three task-operation results require "complete".

Actual: the string resultType does not occur anywhere in schema.json, so none of the four definitions contains or requires the discriminator.

The normative requirement is present in the extension prose and repeated in schema.ts JSDoc, but the structural result aliases do not encode the literal discriminators. The generated schema contains no corresponding "task" or "complete" constraint, so the requirement is not machine-checkable from that artifact.

Reproduction

The following script prints and asserts the exact generated structures described above. The conclusion that each empty branch accepts every instance follows directly from JSON Schema semantics:

https://github.com/AndresSaa/mcp-durable-tasks/blob/fd0db84500fffa059b24e888c3b1093d50db2adc/examples/conformance-reproductions/schema.mts

From a clone of that commit:

cd examples/conformance-reproductions
node --experimental-strip-types schema.mts

The repository vendors both upstream files with their commit and blob provenance, so this remains a reproducible record of the schemas inspected even after upstream changes.

Question

Should generation inline the six imported request and result member types, or should a resolved bundle be published next to schema.json? And should the structural result types encode the literal resultType constraints so the generated artifact enforces the normative requirement?

Together, those changes would make the generated artifact enforce the constraints described by the source and normative prose. Today, a runtime implementation has to maintain its own mirror of the SDK request/response shapes and result discriminators, which creates an avoidable drift risk.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions