You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tasks pose a challenge for backwards compatibility. A tool call can now return its usual result, or a CreateTaskResult.
One way to deal with this would be to have separate functions and types on the server for registering a tool that might be a task. The handler's return type would be some union of CallToolResult and CreateTaskResult. We'd also need a separate function on the client.
A better idea, although it's a bit ugly, is to add a CreateTaskResult field to CallToolResult. When a tool is called as a task, it sets this field and leaves all the others empty. Callers of ClientSession.CallTool would have to recognize that state. Maybe we could have ClientSession.CallToolAsTask that returns a CreateTaskResult – no union type is needed on the client side. But that method would not be able to mark the call as being a task call: that bit is set in the call's params (arguments), not its metadata (see https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1732/files#r2524912400 for my failed pushback). In our SDK, CallToolParams.Arguments is an any, so we can't easily look at it. (One hideous possibility: we require that the arguments marshal to JSON, so we could marshal, unmarshal into a map[string]any, and set the task info.) We may be able to do better on the server side, recognizing that a call is a task call, because we already unmarshal into a map[string]any.
There doesn't seem to be a central ticket for all SDK implementations of a particular change, so sharing this here for SEP-1686 -- the following revisions have been merged on top of the base specification PR for tasks:
Remove an incorrectly-added field from notifications/cancelled: PR
Adjust schema to better-communicate where tasks are valid: PR
Rename/move annotations.taskHint to execution.taskSupport, tweak its enum values, and change a SHOULD to a MUST for the behavior when this is declared:
The actual functional changes should be limited to taskHint, but the rest should help clarify some details around polling.
To save the trouble of going through each of the above PRs one by one, you can also go to the file on the "Files changed" tab here, searching for docs/specification/draft/basic/utilities/tasks.mdx, and then using the "Display the rich diff" button, as shown:
Given the number of changes here, and the fact that this is still experimental, we probably won't get to this until next week. My hope is that it still makes it into the next release.
#755 has had no activity since Feb 18 (~6 months) — per the project's own timeout policy, treating this as stalled rather than in-progress.
Following @jba's suggestion in #755 to split this into smaller PRs instead of one big one: proposing to start with capability negotiation only (declaring task support, no execution yet), as a first mergeable step. Before writing code — the open design question from #755 still needs an answer: how does a tool call transparently become a task, and who owns lifecycle management once it does? Happy to draft options for that here if useful.
@JoannaaKL — if you're still planning to continue #755, happy to collaborate instead of duplicating. Otherwise I'll open a fresh PR scoped to capabilities only.
A concrete, proxy-visible consequence of this being unimplemented, in case it is useful for scoping.
mcp.Tool has no field for the tool-level execution block, so a tool's declared task support does not survive a decode/encode round trip through the SDK. Observed with a TypeScript server (@modelcontextprotocol/sdk 1.29) behind a Go SDK-based gateway:
// direct from the upstream
{"name":"get-time", "execution":{"taskSupport":"forbidden"}, ...}
// the same tool, proxied through a Go SDK client + server
{"name":"get-time", ...} // execution dropped
For an application this is invisible. For anything that re-serializes tools — gateways, proxies, aggregators, caches — it is silent data loss on a field a client may reasonably act on, and the loss is not detectable from the Go side since the field never appears in the struct.
Not asking for the task lifecycle itself to be prioritized: just noting that modeling the tool-level execution field (or preserving unknown members on Tool) fixes the pass-through case independently of implementing tasks, if the two can be separated.
A client-side consequence of the missing execution field, in addition to the proxy case above.
MCP 2025-11-25 says a client that doesn't do task-augmented calls must not offer a taskSupport: "required" tool as a plain tool, because the server answers every plain call with an error; server-everything's simulate-research-query returns -32601 ... requires task augmentation. Because mcp.Tool drops execution when the tools/list result is decoded, and the result arrives already typed, no client middleware can recover it. So a go-sdk client can't filter these tools, and they reach the model as callable tools that always fail.
Adding Execution *ToolExecution (with TaskSupport string) to Tool would fix this independently of implementing the task lifecycle.
I'd like to help move this forward on top of #755, following the split @jba suggested there (capabilities first, then the task data structure and ID generation). Two small pieces that leave the open lifetime question alone:
Task type and task ID generation, from the tasks extension: taskId, status, statusMessage, createdAt, lastUpdatedAt, ttlMs, pollIntervalMs, plus an unexported generator using crypto/rand.Text (the spec requires unguessable IDs). Stacked on #755 as a draft PR until it lands. No CreateTaskResult, no tasks/* methods, no change to how a tool call becomes a task.
Tool.Execution with TaskSupport, as a separate PR on main, so execution.taskSupport from 2025-11-25 servers survives decoding (the proxy and client cases @blakebauman and @chaholl described above). It only preserves the field; nothing acts on it.
Both add exported API, so I'm flagging them here first. Happy to open a separate proposal issue if you'd prefer. @JoannaaKL, I don't want to step on your next steps, so tell me if either overlaps with what you're planning.
My use case is github/github-mcp-server#3406 (watch_workflow_run), which would return a task once the SDK can serve one.
Activity
jba commentedon Nov 13, 2025
Tasks pose a challenge for backwards compatibility. A tool call can now return its usual result, or a CreateTaskResult.
One way to deal with this would be to have separate functions and types on the server for registering a tool that might be a task. The handler's return type would be some union of CallToolResult and CreateTaskResult. We'd also need a separate function on the client.
A better idea, although it's a bit ugly, is to add a CreateTaskResult field to CallToolResult. When a tool is called as a task, it sets this field and leaves all the others empty. Callers of ClientSession.CallTool would have to recognize that state. Maybe we could have ClientSession.CallToolAsTask that returns a CreateTaskResult – no union type is needed on the client side. But that method would not be able to mark the call as being a task call: that bit is set in the call's params (arguments), not its metadata (see https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1732/files#r2524912400 for my failed pushback). In our SDK, CallToolParams.Arguments is an
any, so we can't easily look at it. (One hideous possibility: we require that the arguments marshal to JSON, so we could marshal, unmarshal into amap[string]any, and set the task info.) We may be able to do better on the server side, recognizing that a call is a task call, because we already unmarshal into amap[string]any.LucaButBoring commentedon Nov 21, 2025
Relaying here from
#general-sdk-dev:LucaButBoring commentedon Nov 24, 2025
One very last change (we're doing a hard cutoff on additional changes here): adding a
lastUpdatedAtto theTaskobject: modelcontextprotocol/modelcontextprotocol#1875findleyr commentedon Nov 25, 2025
Given the number of changes here, and the fact that this is still experimental, we probably won't get to this until next week. My hope is that it still makes it into the next release.
He-Pin commentedon Nov 29, 2025
@jba modelcontextprotocol/java-sdk#671 yes
findleyr commentedon Dec 10, 2025
Unfortunately, I don't think this is going to make v1.2.0, for which we want a prerelease this week.
Since this is an experimental feature, that may be acceptable. However, we should land this in master shortly after cutting the release.
[-]SEP-1686: implement Tasks[/-][+]SEP-1686 (experimental): Implement Tasks[/+]IgorKasianenko commentedon Aug 11, 2026
#755 has had no activity since Feb 18 (~6 months) — per the project's own timeout policy, treating this as stalled rather than in-progress.
Following @jba's suggestion in #755 to split this into smaller PRs instead of one big one: proposing to start with capability negotiation only (declaring task support, no execution yet), as a first mergeable step. Before writing code — the open design question from #755 still needs an answer: how does a tool call transparently become a task, and who owns lifecycle management once it does? Happy to draft options for that here if useful.
@JoannaaKL — if you're still planning to continue #755, happy to collaborate instead of duplicating. Otherwise I'll open a fresh PR scoped to capabilities only.
JoannaaKL commentedon Aug 14, 2026
Hey folks sorry for dropping the work on this one. I am happy to continue working on #755 and descope it to only capability negotiation as suggested 😄
blakebauman commentedon Aug 15, 2026
A concrete, proxy-visible consequence of this being unimplemented, in case it is useful for scoping.
mcp.Toolhas no field for the tool-levelexecutionblock, so a tool's declared task support does not survive a decode/encode round trip through the SDK. Observed with a TypeScript server (@modelcontextprotocol/sdk1.29) behind a Go SDK-based gateway:For an application this is invisible. For anything that re-serializes tools — gateways, proxies, aggregators, caches — it is silent data loss on a field a client may reasonably act on, and the loss is not detectable from the Go side since the field never appears in the struct.
Not asking for the task lifecycle itself to be prioritized: just noting that modeling the tool-level
executionfield (or preserving unknown members onTool) fixes the pass-through case independently of implementing tasks, if the two can be separated.mcp: add tasks extension capability negotiation
chaholl commentedon Oct 2, 2026
A client-side consequence of the missing
executionfield, in addition to the proxy case above.MCP 2025-11-25 says a client that doesn't do task-augmented calls must not offer a
taskSupport: "required"tool as a plain tool, because the server answers every plain call with an error; server-everything'ssimulate-research-queryreturns-32601 ... requires task augmentation. Becausemcp.Tooldropsexecutionwhen thetools/listresult is decoded, and the result arrives already typed, no client middleware can recover it. So a go-sdk client can't filter these tools, and they reach the model as callable tools that always fail.Adding
Execution *ToolExecution(withTaskSupport string) toToolwould fix this independently of implementing the task lifecycle.Context: found while moving PromptKit's MCP client onto go-sdk (AltairaLabs/PromptKit#2141).
MehdiMamas commentedon Oct 3, 2026
I'd like to help move this forward on top of #755, following the split @jba suggested there (capabilities first, then the task data structure and ID generation). Two small pieces that leave the open lifetime question alone:
Task type and task ID generation, from the tasks extension: taskId, status, statusMessage, createdAt, lastUpdatedAt, ttlMs, pollIntervalMs, plus an unexported generator using crypto/rand.Text (the spec requires unguessable IDs). Stacked on #755 as a draft PR until it lands. No CreateTaskResult, no tasks/* methods, no change to how a tool call becomes a task.
Tool.Execution with TaskSupport, as a separate PR on main, so execution.taskSupport from 2025-11-25 servers survives decoding (the proxy and client cases @blakebauman and @chaholl described above). It only preserves the field; nothing acts on it.
Both add exported API, so I'm flagging them here first. Happy to open a separate proposal issue if you'd prefer. @JoannaaKL, I don't want to step on your next steps, so tell me if either overlaps with what you're planning.
My use case is github/github-mcp-server#3406 (watch_workflow_run), which would return a task once the SDK can serve one.
mcp: omit Tool.Execution on tools/list outside 2025-11-25
mcp: omit Tool.Execution when tools/list has no protocol version