Repository navigation
wire-schema validation rejects SEP-2663 task envelopes - no resultType "task" branch #424
Description
Activity
Confirming this independently from a second SDK. Still reproduces at
0.2.0-alpha.11.We hit exactly the pattern you describe in nexusphp/mcp (PHP, spec
2026-07-28): 8 of our 10 server-side tasks scenarios fail exactly one check each,wire-schema-valid, withCallToolResult: must have required property 'content'. Every other check in those scenarios passes. The two that don't fail (tasks-required-task-error,tasks-status-notifications) are the two that never emit aCreateTaskResulton atools/call. Our envelope is field-identical to the one in your report.Two things from the referee's own artefacts that may help when weighing this:
The suite contradicts itself on the same message. In
tasks-capability-negotiation,tasks-per-request-meta-opt-inpasses because the response is aCreateTaskResult, andwire-schema-validfails on that same response for not being aCallToolResult. Both checks, same envelope, same run.Reference-fixture CI cannot catch this.
requirements/2026-07-28.yamlmarks every tasks scenarionot_scoredwithnote: io.modelcontextprotocol/tasks (SEP-2663); pending against the reference fixture, run for visibility, andtypescript-sdk'sspec.types.2026-07-28.tscontains noCreateTaskResultat all (versus 2 occurrences in itsspec.types.2025-11-25.ts). So the only implementations that trip this are ones that have already shipped the extension.On the proposed fixes, option 1 looks right on spec grounds, beyond just being cheaper. SEP-2663 §"Polymorphic Results" defines the discriminator as open:
type ResultType = "complete" | "input_required" | "task" | string;
and the core schema's
ResultTypeis a bare"type": "string"describing onlycompleteandinput_required. So an unrecognisedresultTypeis precisely the "this is an extension result" signal, and falling back to the genericJSONRPCResultResponseenvelope check generalises to the next extension without vendoring anything. Option 2 would fix tasks and leave the same hole for the next extension result type.Happy to test a patch against our suite if that's useful.
- added a commit that references this issue
on Sep 6, 2026 Awesome. Confirming the fix. Ran the tasks scenarios against mcpkit's SEP-2663 server (
examples/tasks-v2, Go, spec2026-07-28) on both sides of the merge:before ( 74edef3)after ( a983ba9)scenarios with a wire-schema-validfailure8 0 tasks checks 36 pass / 8 fail 44 pass / 0 fail The 8 were one
wire-schema-valideach, allCallToolResult: must have required property 'content', with every other check in those scenarios passing. Same pattern reported for nexusphp/mcp, down to which two scenarios never failed:tasks-required-task-errorandtasks-status-notifications, neither of which emits aCreateTaskResulton atools/call.Thanks for taking option 1. Just a note - the merged version is a bit broader than what we originally for. The
x-acme/streamedcase means a private extension result that happens to satisfy the method's own schema passes too, so the next extension needs a code change here only if it also changes the result shape.@paulbalandan since you offered to test a patch against your suite, and this is on main now if you want to re-run it.
Re-ran against
a983ba9.0.2.0-alpha.11a983ba9scenarios with a wire-schema-validfailure8 0 tasks checks 36 pass / 8 fail 44 pass / 0 fail All ten server-side tasks scenarios,
--spec-version 2026-07-28 --force. The eight per-check entries in ourexpected-failures.yamlnow report as stale. The rest of the server suite is unchanged at 145 passed. The one new failure elsewhere wasresource-parameter-matches-prmfrom #488, a real bug on our side, fixed.On the breadth: a private extension result that satisfies the method's own schema is wire-valid by construction, so passing it fits the check's name. The case it misses is an extension result that is both unrecognised and malformed. That belongs to the extension's own schema once one is versioned.
We will drop the eight baseline entries when this ships in a release.
The wire-schema validator introduced in #399 discriminates
resultType === 'input_required'before falling back to the request method's result definition, but has no branch forresultType === 'task'. A well-formed SEP-2663 task envelope answeringtools/callis therefore validated againstCallToolResultand fails withmust have required property 'content'.The envelope shape cannot be present in the core spec schema, since tasks moved to an extension as of SEP-2663. So any SDK that implements the tasks extension and passes wire validation on everything else still fails these checks. In our runs (mcpkit) all 8 tasks scenarios fail exactly one check each (
wire-schema-valid) for this reason but every other check in those scenarios passes.Sample violation from
tasks-lifecycle. here is the response that the validator rejected:{ "origin": "implementation", "specVersion": "2026-07-28", "context": "response to 'tools/call'", "errors": [ "CallToolResult: must have required property 'content' (result of 'tools/call')" ], "message": { "jsonrpc": "2.0", "id": 2, "result": { "_meta": { "io.modelcontextprotocol/serverInfo": { "name": "tasks-v2-demo", "version": "0.1.0" } }, "createdAt": "2026-07-29T20:36:13Z", "lastUpdatedAt": "2026-07-29T20:36:13Z", "pollIntervalMs": 1000, "resultType": "task", "status": "working", "taskId": "task-42451aab21bbd828f4db0c0d", "ttlMs": 300000 } } }The result carries
resultType: "task"and the SEP-2663 envelope fields. There is nothing forCallToolResult.contentto match because it is not a CallToolResult.Possible fixes, in rough order of preference:
resultTypevalue as an extension result and skip typed validation for it (fall back to the genericJSONRPCResultResponseenvelope check), sinceresultTypeis exactly the discriminator the final revision added for this purpose.taskbranch, if extension-aware validation is wanted.Happy to contribute a PR for either direction if maintainers have a preference.