modelcontextprotocol / modelcontextprotocol/typescript-sdk
[v2] Tasks extension: tools/call rejects CreateTaskResult but accepts an omitted discriminator as complete
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 13.4k
- Forks
- 2.2k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 4
Description
What happened?
On a 2026-07-28 connection, after the client declares io.modelcontextprotocol/tasks in its per-request capabilities and the server advertises the same extension, the SDK has no end-to-end path for the CreateTaskResult that the extension permits in place of a CallToolResult:
McpServer.registerTool()types the callback asCallToolResult | InputRequiredResult, so returningCreateTaskResultrequires a cast.- If the handler returns an explicit
resultType: "task", the server preserves it on the wire, butClient.callTool()rejects it withSdkErrorCode.UnsupportedResultType:Unsupported result type 'task' for tools/call. - If a dynamically typed handler returns the same task fields but accidentally omits
resultType, the server stampsresultType: "complete", addscontent: [], andClient.callTool()resolves successfully. The task fields survive only as unknown extra keys, so application code sees an empty final tool result instead of a task handle or an error.
The last case is not a conforming handler result; it is included because the current encode path converts that server error into a valid-looking success rather than surfacing it.
Measured behavior
| Handler return | Wire result | Client.callTool() |
|---|---|---|
Task handle with resultType: "task" |
resultType: "task", plus content: [] |
Rejects with UNSUPPORTED_RESULT_TYPE |
Same task fields without resultType |
resultType: "complete", plus content: [] |
Resolves as an empty result |
Ordinary { content: [] } control |
resultType: "complete", content: [] |
Resolves normally |
Every request in the reproduction declares extensions: { "io.modelcontextprotocol/tasks": {} }; the server advertises the same capability. This matters because the extension forbids returning a task handle to a client that did not declare support.
Code to reproduce
The standalone script pins @modelcontextprotocol/server, /client, and /node to 2.0.0, starts a real HTTP server and SDK client, records the exact wire result, and asserts all three outcomes:
git clone https://github.com/AndresSaa/mcp-durable-tasks.git
cd mcp-durable-tasks
git checkout faf4943ddc8fda43cb7b559a6505d0cee9b857f0
corepack pnpm install --frozen-lockfile
corepack pnpm --dir examples/conformance-reproductions run result-type
The observed output for the conforming case is:
wireResultType: task
client: rejected
clientCode: UNSUPPORTED_RESULT_TYPE
clientMessage: Unsupported result type 'task' for tools/call
For the missing-discriminator case:
wireResultType: complete
wireContent: []
client: resolved
clientKeys: [_meta, content, createdAt, lastUpdatedAt, status, taskId, ttlMs]
Why this appears inconsistent
The Tasks extension says that, once the extension is negotiated, a server may return CreateTaskResult instead of CallToolResult, and the client must be prepared to handle either: https://github.com/modelcontextprotocol/ext-tasks/blob/2c1425d9a288b9b1f489430fe1e00bb392b47e48/specification/draft/tasks.md#L59-L61. It also requires the task discriminator to be explicit — servers MUST set resultType to "task" when returning a CreateTaskResult: https://github.com/modelcontextprotocol/ext-tasks/blob/2c1425d9a288b9b1f489430fe1e00bb392b47e48/specification/draft/tasks.md#L95-L102.
On current SDK main, the server-side encode contract stamps every absent discriminator as "complete" while allowing an explicit non-complete value through for tools/call: https://github.com/modelcontextprotocol/typescript-sdk/blob/cc4b41617ce3601b1290d67216ea0b194a3cd9ac/packages/core-internal/src/wire/rev2026-07-28/encodeContract.ts#L45-L85.
The client codec recognizes input_required, but treats every other non-complete discriminator, including task, as unsupported: https://github.com/modelcontextprotocol/typescript-sdk/blob/cc4b41617ce3601b1290d67216ea0b194a3cd9ac/packages/core-internal/src/wire/rev2026-07-28/codec.ts#L177-L234.
The public tool callback types likewise include CallToolResult | InputRequiredResult, but not an extension task result: https://github.com/modelcontextprotocol/typescript-sdk/blob/cc4b41617ce3601b1290d67216ea0b194a3cd9ac/packages/server/src/server/mcp.ts#L1218-L1252.
Expected
After both peers declare io.modelcontextprotocol/tasks, there should be a typed server path for returning CreateTaskResult and a client path that exposes that result instead of rejecting it. A handler that intends to return a task should not be silently projected to a successful final result when its required discriminator is missing.
This looks like a focused acceptance gap under #2189. The API-shape question is whether extension support should widen callTool() and ToolCallback to discriminated unions directly, or install an extension-specific result handler through the planned extension framework. Either shape would resolve the measured wire/client mismatch; I do not want to assume which one fits the v2 design.
SDK version
@modelcontextprotocol/server@2.0.0, @modelcontextprotocol/client@2.0.0, @modelcontextprotocol/node@2.0.0, Node.js v24.18.0.
Area
Server and Client
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the linked conformance reproduction, then read packages/core-internal/src/wire/rev2026-07-28/encodeContract.ts, codec.ts, and packages/server/src/server/mcp.ts. Trace the negotiated tasks path for tools/call and compare the typed callback, wire discriminator, and client result handling. Done means a negotiated CreateTaskResult is type-safe and exposed by the client, while a missing required discriminator is not converted into a successful empty result.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100