AI Engineer World's Fair 2026
MCP Tasks (async)/ Why the heck aren't any agents supporting MCP tasks/async?
Read the talk
MCP Tasks (async): Why the heck aren't any agents supporting MCP tasks/async?
A purchase-order workflow exposes the hard parts of asynchronous MCP: human approval, connection recovery, durable task handles, and the limits of polling.
From a talk by Cornelia Davis
Before you start: Familiarity with MCP tool calls and basic client-server request-response behavior will help; no Temporal experience is required.
Why isn’t an asynchronous tool enough?
Why aren’t more agents supporting MCP Tasks? Returning before a tool finishes sounds straightforward, but implementing the rest of that interaction is substantial work. The November 2025 Tasks specification was explicitly experimental. Cornelia Davis’s opening answer is that client builders have good reasons to wait: the protocol is complicated, and its design is changing.
Davis, a technologist at Temporal whose distributed-systems work spans Cloud Foundry, Kubernetes, GitOps, and Weaveworks, grounds the problem in a purchase-order application. Her walkthrough moves from that application to the November specification, which she calls v1, and then to a substantially revised design she calls v2, anticipated for July at the time of the talk. The distinction matters because the demonstrations and protocol previews do not all use the same version.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
An invoice that cannot finish in one request
A purchase order arrives. The application records that goods were received, then starts two branches in parallel: back-office work, including inventory updates and notifications, and invoice payment. The invoice branch invokes an MCP tool.
That tool contains a workflow of its own. It validates the invoice against an ERP, may request human approval, reconciles against the ERP again, and may need additional human input. Invoice processing therefore has pauses inside it, not merely a slow final calculation. The caller needs to interact with work that is already running.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Start the work, signal it later, preserve it through failure
A single request-response exchange cannot carry this entire business process conveniently. The important operation in Davis’s code snippets is approve or reject: a signal delivers a decision into a long-running process. Starting the process, supplying input, and querying its status are separate interactions. MCP Tasks provides the corresponding asynchronous tool model: invoke the tool, let it run in the background, and retrieve its response later. Davis also promises to share the demonstration code.
At the protocol level, the first step looks simple. Invoke a tool and receive a task handle instead of the completed result. Use that handle for subsequent interactions. The difficulty appears when the time between those interactions grows: a network connection drops, an approver goes on vacation, the agent crashes, or the MCP server stops.
The task must outlive the connection used to create it. Davis describes durability as the obligation to keep launched tasks available through infrastructure failures so the client can interact with them after recovery. That does not mean indefinite retention: the original specification permits deletion after a task’s TTL expires, including before completion. Within that retention contract, reconnecting must not mean starting the business process from scratch. Her earlier MCP Dev Summit talk covers server-side durability; this walkthrough extends the problem to the client.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
A submission survives the missing processes
The first demonstration uses a dashboard rather than chat, letting Davis trigger the workflow with a button. She submits a purchase order, but nothing appears to advance: she has not started the processes. In two terminal windows, she starts the MCP server above and the MCP client below, identifying FastMCP on the client side. Returning to the dashboard shows that the submission was captured even though those processes were not running when she clicked Submit.
The invoice display has progressed through submitted and working to input required. Separate execution dashboards expose the two sides: invoice processing on the backend and purchase-order processing on the caller’s side. The invoice has validated against the ERP and is waiting for approval. The purchase-order workflow has recorded goods receipt and invoked the invoice task, while its back-office branch proceeds independently. A child execution named Task tracker workflow is Davis’s custom MCP client implementation.
Davis opens Input Required, approves the invoice, and submits the decision. The approval arrives as a signal in the backend execution history, allowing invoice processing to continue. Programmed retries appear before the ERP operation succeeds; the line item is paid, the task completes, and the purchase-order dashboard shows completion.
The observed recovery is specifically a submission made before process startup. Davis says she could also terminate the server midway through processing and recover, but she does not perform that interruption in this demonstration. The concrete result is that the missing client and server processes did not lose the submitted purchase order.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
V1 separates lifecycle from result delivery
The task lifecycle makes the dashboard’s behavior meaningful. A task can be working, enter input_required, and return to working once input arrives. Its terminal outcomes are completed, cancelled, and failed. These protocol states describe the externally visible condition of the task; additional semantics govern how input and results travel between client and server.
V1 retains tools/call for invocation. Asynchronous execution is requested through params.task; this is distinct from related-task information carried in _meta. The remaining operations manage the returned handle:
| Operation | Role in the v1 walkthrough |
|---|---|
tools/call | Start work and return a task handle |
tasks/get | Read task status |
tasks/cancel | Request cancellation |
tasks/list | Discover retained tasks |
tasks/result | Wait for and retrieve the result |
The first four use request-response exchanges in Davis’s diagram. tasks/result blocks while work remains unfinished, keeping a connection open. That difference becomes consequential when the tool needs human input.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Rediscovery and elicitation complicate recovery
The first complication is tasks/list. A returning client can ask the server which tasks it holds after a disconnection, a client failure, or a long wait for human input. That provides a rediscovery path when the server retains the task state.
Davis contrasts a few tasks with a hypothetical backend holding a million tasks across many agents. Without a filter, finding a particular task can require searching the available listing. V1 supports pagination, so this need not be one enormous response, but pagination does not supply the missing targeted lookup when the client has lost the handle. Her example concerns discovery cost at scale, rather than a measured million-task run.
The second complication is tasks/result. Once the invoice enters input_required, the exchange is no longer simply start, poll, and collect. The client opens a long-running results connection, and the server uses that interaction to elicit input from the client. The approval conversation sits inside the result-retrieval path.
Davis skips a dedicated demonstration of that exchange for time and turns to its architecture. FastMCP already supplies server support and some client support, so the opening question should not be read as a literal absence of all task-capable clients. The difficult component is the MCP client protocol handler: it must maintain the exchange, detect a broken connection, and resume correctly when the client returns. A durable invoice alone does not solve that client-side recovery problem. V2 simplifies this handler, but it does not remove the need to implement it.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
A stateless protocol with explicit input updates
The redesign follows the direction described in Angie Jones’s May article, MCP Is Growing Up: a stateless protocol core and a formal separation between that core and extensions. Tasks becomes an extension. MCP UI provides another example of functionality that can sit outside the core. For Davis, the important change is reducing dependence on persistent protocol sessions in a distributed system.
In the v2 preview, tasks/list disappears. Human input also gets a different route: instead of participating in elicitation through a long-running results connection, the client sends an explicit update to the task. For the invoice workflow, approval becomes a direct message into existing work, analogous to the Temporal signal shown earlier. Davis’s preview retains tasks/result without the old session-based exchange and preserves the task lifecycle.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Stateless exchanges still need durable application state
The invoice processor still has its own domain state machine. Validating against an ERP, waiting for approval, reconciling, and paying are application states; MCP exposes a smaller lifecycle to callers. The server maps between them. A compact Python mapping expresses that boundary:
python
from enum import Enum
class InvoiceState(Enum):
VALIDATING = "validating"
AWAITING_APPROVAL = "awaiting_approval"
RECONCILING = "reconciling"
PAYING = "paying"
PAID = "paid"
CANCELLED = "cancelled"
FAILED = "failed"
def task_status(state: InvoiceState) -> str:
return {
InvoiceState.VALIDATING: "working",
InvoiceState.AWAITING_APPROVAL: "input_required",
InvoiceState.RECONCILING: "working",
InvoiceState.PAYING: "working",
InvoiceState.PAID: "completed",
InvoiceState.CANCELLED: "cancelled",
InvoiceState.FAILED: "failed",
}[state]
Here, several distinct invoice steps all appear externally as working. An approval allows the invoice workflow to leave its waiting state; it does not itself mean the invoice has been paid. The mapping exposes progress without asking the MCP client to implement the ERP workflow.
Removing task listing also changes the client’s responsibilities. The proposal says clients SHOULD persist task IDs, and warns that a lost ID cannot be recovered through the protocol. Davis argues that this deserves an uppercase MUST: a durable task is of little practical use to a caller that has forgotten how to address it. The normative wording is SHOULD; the stronger requirement is her engineering judgment. With many agents processing purchase orders, clients must retain and manage multiple handles, not just remember the most recent task.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Multiple approvals expose the client workflow
For the second demonstration, Davis submits several purchase orders. Time constraints keep the live system on v1; she does not switch to v2, whose high-level interface she says would look the same. Multiple purchase orders are now in flight together.
Concurrency exposes another implementation issue. Davis reports that the v1 reference implementation handled multiple input_required interactions FIFO: the client could respond only to the first pending interaction. Her custom protocol implementation works around that restriction. This is a limitation she attributes to that implementation, rather than a general rule that every task must be approved in submission order.
Opening the Task tracker reveals how the client is built. Its protocol steps are represented as a workflow, including elicitation handling from the server back to the client. The long sequence diagram has become executable, recoverable client behavior rather than a transient conversation held only in a connection. Davis then returns to the v1 and v2 diagrams: the revised exchange is much cleaner and easier to implement, even though the client still has work to track.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
From a working client to efficient task tracking
The implementation summary brings the responsibilities together: durable servers, clients that hold many task handles, multiple workflow steps inside the MCP client, and efficient execution. Davis displays an architecture picture and a repository, promising to make the slides available there. The protocol simplification reduces the interaction machinery; it does not supply all of these implementation properties automatically.
Efficiency remains an open problem. Davis asks what happens when a million tasks have a million clients repeatedly issuing individual status requests. Her objection is architectural: polling every task independently creates load even when most tasks have not changed. No polling interval, throughput measurement, or latency target accompanies that hypothetical scale example.
Her proposed next step is notification-assisted discovery:
- A client checks a shared endpoint for changes.
- The response identifies which tasks changed.
- The client fetches the state of those tasks instead of polling every task indiscriminately.
Davis describes the notifications protocol as promising work she has not yet explored far enough. V1 status notifications are optional and do not replace polling guarantees; the shared change-discovery arrangement is her proposed direction, not a completed demonstration.
The other unfinished effort is packaging the implementation so developers do not have to recreate the protocol workflow themselves. Davis targets a simpler implementation over the following month or two, with the goal of integrating it into FastMCP so teams can keep using their existing MCP framework. The destination is an asynchronous tool interface backed by durable execution and durable client tracking, with less protocol machinery for each application developer to build.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Resources
From the talk
The November 2025 task lifecycle and RPC contract underlying the talk's v1 walkthrough.
Angie Jones explains the move toward a stateless protocol and a formal extension process.
Python examples for starting background operations, checking status, awaiting results and registering callbacks.
Updates since the talk
The later extension retains the task lifecycle vocabulary but makes cancellation cooperative. This is an update to the historical preview described in the recording.
- SEP-2663: Tasks ExtensionDocumentation
The later proposal uses tasks/update for input, removes tasks/list and tasks/result, and returns results through tasks/get. The recorded v2 preview still retained tasks/result.
Read the transcript and explore slides
Loading transcript and slides…