Repository navigation
Description
What is the problem this feature would solve?
Add opt-in support for the io.modelcontextprotocol/tasks MCP extension, providing durable async tool execution without making tasks part of the base, version-neutral MCP schema.
This is distinct from the experimental task API in MCP v2025-11-25, which is not wire-compatible with the extension.
What is the feature you are proposing to solve the problem?
Introduce a small internal MCP extension boundary, then implement
io.modelcontextprotocol/tasks as its first consumer.
This extension must:
- negotiate support using the extension identifier in the client's per-request capabilities and the server's revision-appropriate capabilities;
- allow
tools/callto return either its normal result or a task result withresultType: "task"; - implement
tasks/get,tasks/update, andtasks/cancel; - provide durable task creation before returning a task handle;
- support polling, cancellation, and
input_requiredtask states; - expose task-specific schemas and APIs from a dedicated Tasks module, rather than adding them to
McpSchemaorMcpServer.
The design should remain extension-local where core MCP code may expose only the minimal generic seams needed for extension negotiation, RPC registration, and an alternate result. It must not become a generic public task abstraction.
Context
The extension supersedes the experimental v2025-11-25 task design:
- it is server-directed rather than enabled with
params.task; - it uses per-request extension capabilities;
tasks/listandtasks/resultare removed;tasks/getreturns the terminal underlying result or JSON-RPC error;tasks/updatecarries responses to in-progress server input requests.
This work therefore depends on, or should be developed alongside, the stateless MCP protocol/lifecycle work. The current session-initialization model cannot safely stand in for per-request extension negotiation.
Acceptance criteria
- A client that advertises
io.modelcontextprotocol/taskson an eligible request can receive either the ordinary result orCreateTaskResult. - A server never returns a task result to a client that did not advertise the extension on that request.
- Task creation is durable before its handle is returned.
-
tasks/getreturns task state and, once terminal, the original result or JSON-RPC error. -
tasks/updatesubmits responses for outstanding input requests. -
tasks/cancelrecords cooperative cancellation. - Task support is opt-in and does not alter behavior for servers or clients that do not configure the extension.
- Task types, storage, and helpers are exposed from a dedicated Tasks module, not the base
McpSchema/McpServerAPI. - Tests cover negotiation, ordinary-result fallback, lifecycle, cancellation, input-required updates, and reconnect/polling behavior.
- The implementation pins the Tasks extension draft/revision it targets.
Non-goals
- Implementing the legacy v2025-11-25 experimental Tasks API.
- General-purpose persistence backends beyond the task-store contract and a
suitable in-memory implementation. - Supporting arbitrary third-party extensions in this issue.
What alternatives have you considered?
This would be part of the work for #6617 and #6706 after resolving #6829
Activity
pullfrog commentedon Aug 1, 2026
lloydrichards commentedon Aug 5, 2026
Some explorations into how Task might be implemented as a public facing Schema while behind the scenes is wired separately for v2025-11-25 (experimental) and v2026-06-28 (extensions).
params.task{ task: Task }resultType: "task"plus Task fields.tasks/get, optional notifications.tasks/result.tasks/get.tasks/result.inputRequestsfrom get; responses sent bytasks/update.tasks/list.ttl,pollInterval.ttlMs,pollIntervalMs.Ultimately this is going to need to be something that we can wrap up the current
Toolkitwith so its possible define what tools can be long lived and how we provide the storage layer. TheMcpTaskshould be a shared domain that is agnostic of the wire implementations:This could then be used something like:
So what we should be aiming for is a version-neutral model for long-running tool calls which can have different wire implementations depending on the protocol version. I think there are still some parts missing for this like adding
v2026-06-28which will introduce the state-less lifecycle as well as a cleaner seam for the extensions.Open to other suggestions or recommendations.
lloydrichards commentedon Oct 17, 2026
An update on the direction in #8631. I've moved away from the separate
McpTaskStore,TaskTransition, andMcpTaskRuntimeproposed in my earlier comment. Those would duplicate execution and recovery behavior thatWorkflowalready provides.The current approach registers task tools as Workflows and uses the existing MCP toolkit code to decode arguments and encode results. Workflow itself is unchanged.
flowchart TD A["tools/call"] --> B["McpTasks: resolve owner and start Workflow"] B --> C["Workflow: run handler and Activities"] B --> D["Return handle once identity is readable"] E["tasks/get"] --> F["McpTasks: check owner and poll Workflow"] F --> G["Return status and tool result"] H["tasks/cancel"] --> I["McpTasks: check owner and request Workflow interruption"]What the API looks like
Tools keep their normal schemas.
McpTasks.toolkittakes the handlers and returns a registration layer directly:Provide the existing SQL-backed
ClusterWorkflowEngineinstead for recovery across runner restarts. The toolkit and handlers stay the same.owner: "shared"allows every client to access tasks. Applications can instead supply an Effect that resolves a trusted owner ID.Why use Workflow
Workflow owns execution, results, Activity replay, interruption, and history cleanup. McpTasks adds only two immutable facts through
DurableDeferred: the owner and creation time, and a finished timestamp recorded by a Workflow finalizer. There is no separate task database or mutable status record.tasks/getreads those facts and polls Workflow. The normal path takes two engine lookups while working and three once terminal, regardless of Activity history size. There is no history scan or background poller.On SQL recovery, Workflow replays the handler and reuses completed Activity results. Code outside Activities can run again, so side effects that need replay protection belong in Activities. Original request metadata remains available during replay. Handler changes that cannot replay older executions need a new toolkit name.
What changes from the earlier proposal
This first version targets the July
io.modelcontextprotocol/tasksextension, without a legacy experimental Tasks adapter. Task tools require extension support. Ordinary inline tools still useMcpServer.toolkit, with no automatic fallback.Interactive input and
input_requiredare deferred, so this narrows the issue's acceptance criteria.tasks/updatechecks ownership and acknowledges but ignores input responses. Progress and task-specific expiry are also outside this version.Cancellation requests cooperative interruption. Its acknowledgement does not guarantee that execution has stopped. Tasks report
ttlMs: null, while the engine's history retention determines how long handles remain usable.The SQL tests reconstruct runners to check retained results, ownership, timestamps, Activity replay, and original request metadata. This gives us a smaller Tasks implementation that uses the existing Workflow engines for recovery rather than adding another runtime to maintain.