Direct answer
Use the MCP Tasks extension as a durable handoff between a coding agent and a tool that may outlive one request. The server starts the work, returns a task handle, and keeps the task state in storage that remains available after a disconnect. The client records the handle, polls tasks/get at the server-suggested interval, sends any required input with tasks/update, and reads the final result only after a terminal state. It can ask for cancellation with tasks/cancel, but cancellation is cooperative, so the operator must still reconcile the final state.
Last reviewed: 2026-08-27
This pattern fixes the common failure where a synchronous tool call runs longer than the client or transport will wait. The MCP Tasks overview describes a durable handle, progress states, reconnect-and-resume behavior, and deferred result retrieval. The current 2026-07-28 MCP specification announcement places Tasks in the official extension framework and pairs it with a stateless request model. In practice, the extension lets an agent continue a build, test sweep, migration plan, or batch analysis without keeping one connection open for the entire operation.
Start with a small state machine rather than a generic retry loop. The state you persist should be enough to answer four questions after any interruption: which tool was requested, which task handle belongs to it, what state the server last reported, and whether the client has already answered an input request. Keep the original tool arguments in the system of record when policy permits, but do not put raw prompts, source files, or credentials in routine logs.
A concrete operator workflow
Happy path
- Discover support. Confirm that the server advertises
io.modelcontextprotocol/tasksthroughserver/discover, and that the client sends the same extension capability in the request metadata. The server remains the party that decides whether a particular call becomes a task. - Start one call. Send
tools/callwith a stable client request ID and an idempotency key in your application layer. Be prepared for either an ordinary result or a result whoseresultTypeistask. - Persist before polling. As soon as the response contains a task handle, write the task ID, tool name, request ID, creation time, suggested poll interval, and an application-level deduplication key to durable storage. A crash between receiving the handle and writing it is the one gap a resume design cannot repair automatically.
- Poll deliberately. Wait for the server’s
pollIntervalMs, calltasks/get, and replace the stored state atomically. Do not shorten the interval just because the agent is eager; that creates needless load and can turn a healthy queue into a rate-limit incident. - Handle input. If the state is
input_required, present each outstanding request to the approved user or model path, record which request keys have already been answered, and submit those answers withtasks/update. Deduplicate keys across polls so a confirmation is not shown twice. - Finish once. For
completed, consume the final result exactly once and mark the application operation complete. Forfailed, capture the protocol error and route it to an operator or a bounded retry policy. Forcancelled, record that the server reported cancellation and verify any external side effect before deciding what to do next.
The wire shape can be kept small in a runbook. This is illustrative metadata, not a credential-bearing request:
{
"request_id": "req-2048",
"tool_name": "run_checks",
"task_id": "mcp-demo-42",
"status": "working",
"poll_interval_ms": 1000,
"attempt": 1
}
Error path
Suppose the client times out after the initial call. Do not immediately issue the same tools/call. First look up the application operation by its deduplication key and task ID, then reconnect and call tasks/get. If no task ID was persisted, query the server-side operation record or stop and escalate rather than guessing. If the task is still working, resume polling. If it is input_required, answer only unresolved request keys. If it is terminal, reconcile the result and close the local record. If the server returns not found, treat that as an evidence gap: check task TTL and authorization scope, preserve the request ID, and avoid starting a duplicate side effect until an operator confirms the external system’s state.
The Azure guidance on building long-running MCP tools with Durable Functions explains why this matters: clients commonly enforce their own timeout window, while the underlying work may continue. Its interim workflow-ID pattern also shows the risk of asking an agent to remember and reproduce a long identifier. A Tasks-aware client moves that bookkeeping into the protocol client and its durable task store.
For every poll and transition, emit a sanitized event such as:
{
"event": "mcp_task_transition",
"request_id": "req-2048",
"task_id": "mcp-demo-42",
"tool_name": "run_checks",
"status": "completed",
"result_type": "complete",
"poll_count": 4,
"duration_ms": 3820,
"error_code": null
}
Allow-list fields like these and redact tool arguments, source text, user input, and any authentication material. A useful event lets an operator correlate a task without turning the log into a copy of the agent’s context.
Who this is for
This guide is for engineers who run coding agents against MCP servers and have at least one tool that can exceed a normal request timeout. Typical examples include a repository-wide test matrix, a dependency audit across many packages, a deployment preflight, a bulk code search, or a human approval step embedded in a workflow. It is also useful for platform teams putting MCP servers behind a load balancer or deploying multiple stateless instances.
It is not a reason to make every tool asynchronous. Fast, deterministic lookups should continue to return an ordinary result. Tasks add state, storage, polling, and cleanup, so use them when the work is genuinely long-running, interruptible, interactive, or backed by an external job system. Host support varies; the overview explicitly points readers to a client support matrix, and the Azure article cautions that client and SDK adoption is still progressing. Verify the client you actually run before changing a production contract.
If an interrupted agent run is your main concern, pair this protocol-level design with the site’s guide to resuming an interrupted coding-agent session without losing context . That link covers the surrounding run context; this article focuses on the task handle and lifecycle at the tool boundary.
Key takeaways
- Treat a task ID as a durable work reference, not as proof that the work succeeded. Success is established only by a terminal status and a validated result.
- Handle both response shapes from a supported call: an ordinary result and a
CreateTaskResultwithresultType: "task". The server decides per request whether to create a task. - Persist the handle before the client begins polling. A process-local map or an in-memory store is not sufficient when requests can land on another process.
- Use the server’s poll interval, cap total polling time, and make the resume path idempotent. Polling is coordination, not a substitute for an operation ledger.
- Treat
input_requiredas a first-class state. Store request keys and responses so reconnects do not repeat approvals or sampling prompts. - Treat cancellation as a request to stop, not a guaranteed rollback. Check the final task state and inspect external side effects.
- Keep domain failures in the tool result model and reserve protocol failure handling for JSON-RPC errors. The SDK documentation distinguishes a completed task whose tool result has
isError: truefrom a failed task caused by a protocol-level error. - Test the exact client, SDK, and server combination. The extension is current and useful, but ecosystem support is not uniform.
Sources checked
The implementation advice above is grounded in these public sources:
- Tasks - Model Context Protocol
documents durable handles, task creation,
tasks/get,tasks/update,tasks/cancel, statuses, polling, notifications, and reconnect behavior. - SEP-2663: Tasks Extension is the final extension design for capability negotiation, server-directed task creation, polymorphic results, task isolation, and the redesigned polling lifecycle.
- Tasks in the MCP C# SDK provides implementation details for task stores, automatic and manual polling, input-request deduplication, cancellation, terminal-state idempotence, and multi-process durability.
- How to build long-running MCP tools on Azure Functions describes timeout behavior, checkpointed Durable Functions workflows, and the transition from ad hoc workflow IDs to Tasks.
- The 2026-07-28 Specification explains the stateless protocol context, the formal extensions framework, and the move of Tasks out of the experimental core.
Read the current overview first for the operational model, and use the final SEP to understand the accepted design decisions. Because the SEP is preserved as a historical record, confirm live requirements against current extension documentation and the versioned SDK you deploy. An SDK API is not a substitute for checking the protocol version and the host’s support matrix.
Contract details to verify
Capability negotiation and version. Confirm which protocol revision your transport sends. The C# SDK page requires protocol version 2026-07-28 or later for its Tasks package, while the final SEP describes the extension identifier as io.modelcontextprotocol/tasks. Make the client send its extension capability in each applicable request metadata object and verify that the server advertises support through discovery. Never assume that a previous tools-list response implies task support for a different host.
Result discrimination. Your decoder must branch on resultType. A normal call may return an ordinary completion; a call handled as a task returns a task handle with an initial status, task ID, and optional TTL and poll interval. Unknown result types should fail closed and be retained in the event log for investigation.
State transitions. Implement working, input_required, completed, failed, and cancelled. The last three are terminal according to the overview. Store the last update time and the server message, but do not infer completion from a friendly status message. Only the status and result or error fields close the operation.
Storage and routing. The SDK guidance calls for a thread-safe task store, durable creation before the initial response is sent, and a shared or externally backed store when stateless HTTP creates a fresh server instance per request. Test a poll routed to a different process. If it cannot find the task immediately after creation, your storage consistency is too weak for this contract.
Input and cancellation. Preserve outstanding input request keys and answer each key at most once. tasks/update is an acknowledgement path, not a new tool invocation. tasks/cancel is cooperative and may be eventually consistent, so expose a state such as cancel_requested in your local record without pretending that the server has already stopped.
Retention and observability. Decide how long task records and final results remain available, based on the server’s TTL. Record enough metadata to replay the client decision without storing sensitive content. A small transition log plus a durable operation record is easier to audit than unbounded poll payloads.
Before adding an approval step to a task, compare its boundary with the site’s MCP tool approval gate checklist . Approval and asynchronous execution solve different problems, but their ordering determines whether a task is created before or after a human authorizes the side effect.
Failure modes
Duplicate work after a timeout. The caller sees a timeout, retries tools/call, and launches two builds. Prevent this with an application operation ID and a server-side deduplication policy. If the server cannot offer one, pause after a timeout and inspect the external job system before retrying.
The task handle is lost. A process crashes before persisting the handle. This is why the initial response must be written durably before the rest of the workflow proceeds. If the handle is gone, report an unknown outcome; do not manufacture a replacement ID.
A task disappears between polls. TTL expiry, authorization scope, or a non-shared store can all produce a missing task. Preserve the last known state, the request ID, and the time of disappearance. Recreate only after checking whether the external operation already ran.
A client does not support Tasks. The server must not send a task result to a client that did not advertise the extension. Keep an inline result path for short work, or return a clear capability error and route the call to a compatible host. Do not silently reinterpret a task handle as the tool’s business result.
Input-required loops. A buggy server can keep returning input_required without exposing a new request key. The C# SDK includes a stuck-task detector and a configurable consecutive-poll threshold. Implement an equivalent bound, cancel on exhaustion, and raise an operator alert with the sanitized transition log.
Cancellation races with completion. A cancel request can arrive after the work has completed. Make terminal transitions idempotent so a late cancellation cannot overwrite a result. Reconcile the server’s terminal state with the external system before issuing a compensating action.
Domain error misclassified as protocol failure. A test suite can legitimately finish with failing tests. Keep that outcome as a completed tool result with an application-level error marker when the server’s contract says so. Reserve failed handling for the JSON-RPC execution error path, then alert differently for each category.
Polling overload. Several agents may reconnect at once and all poll immediately. Honor pollIntervalMs, add bounded jitter in the client, and cap concurrent polls per server. The task handle gives you resilience; it does not remove rate limits.
FAQ
Do I need Tasks for every MCP tool? No. Use it for work whose duration, interaction, or external job lifecycle makes a blocking request unreliable. Keep fast reads synchronous.
Does a task guarantee that the work survives a server restart? The protocol gives the client a durable handle, but the server must back it with durable storage and a recoverable worker. An in-memory store cannot satisfy a multi-process or restart scenario.
Can the server decide to create a task when the client did not ask for one? The final extension makes task creation server-directed after capability negotiation. A server still must not return a task to a request that did not advertise the extension. Code the client to accept either result shape.
What should I do when cancellation is acknowledged but the task remains working? Continue polling within a bounded window, mark the local record as cancellation requested, and inspect the external side effect. Cancellation is cooperative and is not a rollback protocol.
How should I handle an input_required task after reconnecting? Reload the task record, compare the returned request keys with the keys already resolved, present only new requests, and call tasks/update with those responses. Keep the keys stable for the lifetime of the task.
Can I use the older tasks/result flow? Do not build a new client around it. The 2026-07-28 release and SEP-2663 describe the current poll-based tasks/get and tasks/update design. Verify compatibility if you must interoperate with an older implementation.
Reader next step
Choose one long-running, read-only tool in a staging MCP server and instrument it end to end. First negotiate the extension and record whether the host supports it. Then force a client disconnect after task creation, restart the client, reload the stored task ID, and prove that polling reaches the same terminal result without invoking the tool twice. Repeat with an input_required branch, a cooperative cancellation, a task store failover, and a malformed response.
For each run, retain the sanitized transition events, the final status, the result discriminator, and the operator decision. Set explicit limits for poll count, task age, and missing-task escalation before enabling side effects. Once those tests pass, expand the pattern to CI or deployment tools and link the task record to your existing runbook and change review process.