MCP Tasks vs synchronous tool calls: how to handle long-running operations
A practical choice between standard MCP tools/call and experimental MCP Tasks for long-running operations: capability negotiation, states, polling, cancellation, security, testing, and rollout.
Article contents
- 01Short answer: use Tasks for durable execution, not for every slow tool
- 02Two negotiation layers prevent imaginary compatibility
- 03The Task state machine does not replace business-operation state
- 04Polling, progress, and backoff need a bounded budget
- 05Authorization binds a Task to context, not merely to a hard-to-guess ID
- 06The test matrix validates transitions, retries, and uncertain outcomes
- 07Rollout: experimental adapter, narrow canary, and verified fallback
Short answer: use Tasks for durable execution, not for every slow tool
Keep a normal tools/call when the operation fits within a bounded request timeout, the result can be returned immediately, and a connection loss does not require separate recovery. Consider an MCP Task when work runs for a long time, must outlive a single request, or needs polling, a deferred result, cancellation, or an intermediate input_required state. Tasks are part of the MCP 2025-11-25 specification but are marked experimental, so isolate them behind a capability gate rather than treating them as a universal baseline.
A Task is not a queue, workflow engine, or exactly-once guarantee. It standardizes the client-visible wrapper around a long-running operation: the receiver creates a task ID and reports state, while the requestor checks status and retrieves the result. The business operation still needs its own operation ID, authorization, idempotency, durable state, and reconciliation with the system of record. If the process can safely finish in one short call, a Task only adds states and cleanup.
- Short read or bounded compute → normal tools/call.
- Long-running work with a deferred result → Task candidate.
- Additional user decision required → Task with input_required.
- Consequential action → domain operation ledger independent of the protocol wrapper.
- Peer did not advertise the capability → do not send task augmentation.
Two negotiation layers prevent imaginary compatibility
Task support is negotiated during initialization. For a task-augmented tool call, the server advertises tasks.requests.tools.call; without that capability the client must not run the tool as a Task. The individual tool then refines the contract with execution.taskSupport: forbidden is the default, optional allows both modes, and required mandates a Task. Check both layers for every session and do not cache the conclusion based only on a server name or SDK version.
Build a capability manifest with the protocol version, server implementation version, session evidence, task operations list/get/result/cancel, and the tool-level mode. The router compares the manifest with workflow requirements before the first call. If a required capability is absent, make the fallback explicit: a short synchronous path, a separate job API, human handoff, or controlled refusal. Silently converting a required Task into a long HTTP request creates different timeout and recovery semantics.
The Task state machine does not replace business-operation state
An MCP Task starts in working and can move to input_required, completed, failed, or cancelled through the transitions allowed by the specification. The receiver generates taskId; TTL controls when the record may be deleted; pollInterval suggests how often to check. The requestor retrieves the actual result through tasks/result only after terminal completion. Persist the last observed status, lastUpdatedAt, correlation ID, and next allowed poll so a reconnect resumes observation instead of starting the work again.
Maintain a parallel DomainOperation with a stable idempotency key, actor, tenant, object version, intended effect, downstream receipt, and authoritative outcome. completed on the Task means the protocol result is ready; it does not prove that an external system accepted a payment, publish, or deployment. cancelled also does not guarantee compensation for an effect that already happened. The UI should separate transport/task state from the confirmed domain outcome, and an unknown outcome must go to reconciliation before retry.
- Task state → what the receiver reports about request execution.
- Domain state → what the system of record confirms about the business effect.
- Cancellation → request to stop further work, not automatic rollback.
- TTL expiry → protocol-record lifecycle, not permission to forget audit evidence.
Polling, progress, and backoff need a bounded budget
The client respects pollInterval, adds jitter, limits total duration, and stops polling after a terminal state. Polling more often does not speed up the job; it only increases load and rate-limit risk. Progress notifications can improve UX, but they must not reset the absolute deadline indefinitely. After a network loss, the client resumes observation of the known taskId instead of repeating the originating tool call.
Set separate budgets for create, status reads, result retrieval, and business reconciliation. Persist only the minimum response envelope needed: status, version, timestamps, sanitized message, and evidence handles. A tasks/get failure can mean expiry, authorization failure, or server loss; it does not convert an unknown domain outcome into failed. For large artifacts, return bounded metadata and an authorized handle rather than an unbounded payload in model context.
The test matrix validates transitions, retries, and uncertain outcomes
The contract suite covers initialize, missing capability, every taskSupport value, create, get, list, result, cancel, invalid transition, expired TTL, and pagination. Fault injection breaks transport before and after CreateTaskResult, between completed and result retrieval, during input_required, and after a cancel request. Assertions verify that the originating call is not repeated automatically, status changes only through allowed transitions, and duplicate delivery does not create a second domain effect.
Security cases try to read or cancel another tenant’s Task, guess an ID, retrieve a result after losing scope, tamper with related-task metadata, and inject prompts into status or result. Load tests measure bounded concurrent jobs, polling amplification, cleanup, and downstream backpressure. Model-in-the-loop evaluation is needed only where the model decides to invoke the tool or interprets its result; protocol conformance and authorization remain deterministic gates.
- Hard gate → cross-tenant access, invalid transition, duplicate effect, or secret leakage.
- Recovery gate → reconnect resumes the known Task without a second start.
- Cancellation gate → terminal semantics and domain reconciliation are verified separately.
- Compatibility gate → fallback works with a peer that does not support Tasks.
Rollout: experimental adapter, narrow canary, and verified fallback
Start with one read-only or reversible workflow where a Task provides measurable operational value: it survives a request timeout, reduces manual recovery, or makes a long operation observable. Pin protocol and SDK versions, the capability manifest, allowed tools, maximum TTL, concurrency, status retention, owners, and the kill switch. Shadow mode can compare the state projection with an existing job API, but it must not execute the business operation twice.
Promote the canary only after contract, security, load, and recovery gates pass. Rollback blocks new task-augmented starts, keeps a watcher for Tasks already created, reconciles in-flight domain operations, and restores the verified synchronous or job-API path. Retest after changes to the MCP specification, the experimental status of Tasks, SDK, transition schema, authorization context, transport, or downstream idempotency. This is a local compatibility verdict, not a claim that Tasks are better for every MCP server.
Practical examples
Long export with safe recovery
A tool creates a read-only export and returns a Task. The client stores taskId and the domain exportId, polls at the recommended interval, and after completed retrieves a manifest with a checksum and short-lived download handle. A network break resumes polling; it does not create a second export.
Deployment where cancelled does not mean rolled back
A Task wraps a deployment job. Cancel stops subsequent steps, but the controller separately checks whether part of the change was already applied. The UI shows a cancelled task and a reconciliation_required domain outcome until the system of record confirms either rollback or a stable release.
FAQ
Are MCP Tasks already stable for production?
Tasks are defined in MCP 2025-11-25, but the specification marks them experimental. Use explicit capability negotiation, pinned compatibility, a canary, and a fallback; recheck the current status before rollout.
Does a Task make tools/call asynchronous automatically?
No. The server and the individual tool must advertise support, and the receiver must implement durable execution, status, result retrieval, cancellation, authorization, and cleanup.
When is it better to keep a custom job API?
Keep it when clients do not support Tasks, the workflow needs richer orchestration semantics, or the existing job API already provides the required SLA, audit, and recovery. An MCP Task can be an adapter over that API rather than a replacement.
Can tools/call be repeated if the create response is lost?
Not blindly. Reconcile first using a client operation ID or idempotency key. Otherwise a lost response after a successful start can create a duplicate operation.