The Model Context Protocol spent its first two years assuming the thing on the other end of a tool call would answer quickly. Ask for a file, get a file. Run a query, get rows. The 2026-07-28 spec broke that assumption in the most consequential way possible: it made the protocol stateless. No held session, no long-lived connection the server can lean on between messages.
That is exactly the wrong shape for the work agents increasingly hand to tools. "Render this video." "Run this scan." "Re-index the corpus." Those don't return in milliseconds; they return in minutes. A stateless protocol and a ten-minute tool call are in open conflict — there's no connection to keep open and nowhere for the server to quietly remember what it's doing for you.
The Tasks extension (SEP-2663) is the reconciliation. And the interesting part isn't that MCP added async — everyone adds async. It's where the bookkeeping ended up.
Call now, fetch later#
The mechanic is the call-now, fetch-later pattern. A server can answer an ordinary tools/call not with a result but with a task handle — an id that says "I've started; check back." The client then polls tasks/get to read the current status and, once the work finishes, to pull the final result or error.
The entire client-side surface is three methods. tasks/get polls. tasks/update feeds input into a running task — the same channel an elicitation or a mid-run confirmation rides on. tasks/cancel stops it. That's the whole API. There's no blocking "wait until done" call; the old experimental tasks/result was deliberately replaced by polling, because blocking presumes a connection a stateless transport won't promise you.
One subtlety that bites implementers: the response is polymorphic. The same tool, called the same way, might return a finished answer one time and a task handle the next. A discriminator field (resultType: "task") is how the client tells which it got. Which leads to the second surprise —
You don't get to decide it's a task#
Task creation is server-directed. The client doesn't request a task. It advertises, in its per-request capabilities, that it's willing to handle one — and the server decides, per call, whether this particular invocation is going to run long enough to warrant a handle. A fast hit comes back inline; a slow one comes back as a ticket.
This is the right call for resource control — the server knows what's expensive, the client doesn't — but it means a robust client cannot treat "tasks" as an opt-in feature it turns on for specific tools. If you advertise support, any call to any tool might hand you a handle instead of an answer. Both paths have to be live in your code.
MCP didn't make tasks a mode you switch on. It made them a thing that can happen to any call you make.
The tell is what got deleted#
Here's the line in the spec worth reading twice. The redesign removed tasks/list — the endpoint that would let a client ask "what tasks do I have running?" The stated reason: it can't be scoped safely without sessions.
Sit with that. In a stateful protocol, the server holds a session, so "your tasks" is a coherent set it can enumerate. Strip the session out and there is no "your" anymore — no server-side notion of which caller owns which task that doesn't either leak across clients or smuggle back the exact session state the redesign just spent its whole budget removing. So the enumeration endpoint didn't get redesigned. It got deleted.
The consequence lands entirely on the client: you remember your own task ids. The protocol will not hand you a list. If your agent crashes mid-poll and didn't persist the handle, the task keeps running on the server and you have no supported way to find it again. The work is orphaned — still executing, billed, producing a result no one will collect.
That's the real story of Tasks, and it's a story about where state lives. The durable bookkeeping for in-flight work didn't disappear when the session did. It moved — off the server and onto the client, in the form of a handle you are now responsible for not losing.
When a handle isn't enough#
It's worth being clear about what Tasks does and doesn't buy you, because the call-now/fetch-later shape looks a lot like durable execution and isn't.
Tasks gives you async-with-polling: a handle, a status, a result, a cancel. It does not give you retries, timers, signals, or replay-after-crash. If the server process dies, the spec makes no promise your task survives — that's an implementation detail of whatever's behind the server. Engines like Temporal, Inngest, and Restate own a persistent store precisely to guarantee a workflow resumes exactly where it left off. They live outside the protocol.
So the decision is clean. Reach for Tasks to stop a slow tool from blocking an agent's turn — that's the gap it was built to close, and inside MCP it's now the standard way to do it. Reach for a durable engine when the workflow itself is the thing that must survive a crash and run exactly once. Tasks moved the ticket to the client. It didn't promise to hold your place in line.



