Tasks
The ext-tasks repository contains the full specification and documentation for MCP Tasks.
modelcontextprotocol/ext-tasks
Full specification and documentation for MCP Tasks.
Not every tool call returns instantly. Some operations — CI pipelines, batch processing, human approvals — take seconds, minutes, or longer. MCP Tasks let servers return a durable handle instead of blocking, so clients can poll for progress, provide input when needed, and retrieve the final result after reconnecting.
Why not just block?
You could hold the connection open until the work finishes. Tasks solve problems that blocking cannot:
- No long-lived connections. Blocking ties up a connection for the duration of the operation. Many clients and transport intermediaries impose timeouts that make this impractical beyond a few seconds.
- Crash resilience. A task ID is a durable handle. If the client disconnects or restarts, it can resume polling with the same ID.
- Progress visibility. Tasks carry status metadata (
working,input_required,completed,failed,cancelled) and optional status messages, giving clients visibility into progress. - Mid-flight interaction. When a task needs input (e.g., an elicitation for
user confirmation), it moves to
input_requiredand surfaces the request. The client responds viatasks/update— no second connection or unsolicited server-to-client messages required. - Server-directed. The server decides per-request whether to create a task. Clients opt in once via the extension capability and handle whichever result shape arrives. No per-tool warmup or per-request flag.
How Tasks work
Tasks extend the standard request flow. When a server decides a request will be long-running, it returns a task handle instead of the final result. The client polls for completion.
Capability negotiation. The client includes
io.modelcontextprotocol/tasksin its per-request capabilities. The server advertises the same extension in its ownserver/discovercapabilities.Task creation. In response to a supported request, the server returns a
CreateTaskResult(identified byresultType: "task") containing ataskId, initial status, TTL, and suggested polling interval. The task is durably created before the response is sent.Polling. The client calls
tasks/getwith thetaskId. The response carries the current status and, for terminal states, the final result or error.Mid-flight input. If the task moves to
input_required, thetasks/getresponse includes aninputRequestsmap with elicitations or other server requests. The client fulfills these viatasks/update.Completion. When the status reaches
completed, theresultfield contains what the original request would have returned synchronously. If the status isfailed, theerrorfield contains the JSON-RPC error.Cancellation. The client can send
tasks/cancelat any time. Cancellation is cooperative — the server acknowledges the intent but is not obligated to stop the work.
sequenceDiagram
participant Client
participant Server
Client->>Server: tools/call (with tasks capability)
Server-->>Client: CreateTaskResult (taskId, status: working)
loop Poll until terminal
Client->>Server: tasks/get (taskId)
Server-->>Client: Task (status: working)
end
Note over Client,Server: Server needs user input
Client->>Server: tasks/get (taskId)
Server-->>Client: Task (status: input_required, inputRequests)
Client->>Server: tasks/update (taskId, inputResponses)
Server-->>Client: ack
loop Poll until terminal
Client->>Server: tasks/get (taskId)
Server-->>Client: Task (status: working)
end
Client->>Server: tasks/get (taskId)
Server-->>Client: Task (status: completed, result)
When to use Tasks
Tasks are a good fit when your use case involves:
Long-running operations. CI pipelines, batch data processing, or model training jobs that take minutes or hours.
Human-in-the-loop workflows. Approval gates, review steps, or any operation
that pauses for user confirmation. The task moves to input_required and the
client presents the request.
External job systems. If your server wraps an API that already uses job IDs (cloud deployments, async APIs, queued work), return a task when you create the job and resolve it when the job completes.
Unreliable connections. Mobile clients, intermittent networks, or environments where connections drop. Task IDs survive disconnects.
Batch processing. Operations that process many items (bulk imports, mass updates) where partial progress is meaningful. Status messages report progress.
Task lifecycle
| Status | Meaning |
|---|---|
working | The operation is in progress. |
input_required | The server needs client input before continuing. See inputRequests. |
completed | The operation finished. The result field contains the final output. |
failed | A JSON-RPC error occurred during execution. The error field has details. |
cancelled | The operation was cancelled (not always honored). |
completed, failed, and cancelled are terminal — once reached, the task’s
state does not change.
Notifications
Servers can push status updates via notifications/tasks. Clients opt
into these through the subscriptions/listen mechanism. Each notification
carries the full task state, eliminating the need for an extra tasks/get
round-trip.
Polling is the default. If a server supports notifications, clients can rely on them instead of polling.
Implementation guide
For MCP clients
To consume task-augmented responses, your client must:
Declare support
Include the extension in its per-request capabilities:
{
"jsonrpc": "2.0",
"id": 1,
"method": "...",
"params": {
// Other fields...
"_meta": {
// Other fields...
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {
"io.modelcontextprotocol/tasks": {},
},
},
},
},
}Handle polymorphic results
When issuing a supported request (e.g., tools/call), be prepared to receive
either the standard result or a CreateTaskResult with resultType: "task".
Poll for completion
Call tasks/get with the returned taskId, respecting the pollIntervalMs
value. Continue polling until the task reaches a terminal status (completed,
failed, or cancelled).
Handle input requests
If the task status is input_required, read the inputRequests map, present
the requests to the user or model, and submit responses via tasks/update.
Persist task IDs
Store task IDs durably so polling can resume after a client crash or restart.
For MCP servers
To return tasks from your server:
Advertise support
Include the extension in your server/discover capabilities:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
// Other fields...
"capabilities": {
"extensions": {
"io.modelcontextprotocol/tasks": {},
},
},
},
}Check client capabilities
Before returning a CreateTaskResult, verify that the client included the
extension in its per-request capabilities. Never return a task to a client that
did not declare support.
Return CreateTaskResult
When a request will be long-running, respond with resultType: "task" and a
Task object containing a unique taskId, initial status, ttlMs, and
pollIntervalMs. The task must be durably created before sending the response.
Serve tasks/get
Return the current task state on each poll. For terminal states, include the
result (on completed) or error (on failed) field.
Handle tasks/update
Accept inputResponses keyed to outstanding inputRequests. Acknowledge with
an empty result. Ignore responses for unknown or already-satisfied keys.
Handle tasks/cancel
Acknowledge cancellation requests with an empty result. Honor them when
possible, but cancellation is cooperative — the task may still reach a
non-cancelled terminal status.
Client support
See the client matrix for extension support across clients. Task support requires explicit opt-in from both client and server.
Specification
The Tasks extension is specified in the ext-tasks repository. It uses the standard MCP extension negotiation mechanism: clients declare support in the extensions field of the io.modelcontextprotocol/clientCapabilities they send in each request’s _meta, and servers advertise theirs in the capabilities returned by server/discover.