The Day MCP Stopped Watching the Clock
Every developer who has integrated an AI with a real backend knows this pain. The user says, "Run the data refresh." The MCP tool kicks off a job. The job takes four minutes. The MCP request times out at thirty seconds. The model receives an error and confidently tells the user, "I was unable to run the data refresh," even though the refresh is, at this very moment, happily running.
For about a year, MCP had no good answer for this. You either polled, you faked it, or you accepted that long-running operations weren't really part of the protocol's worldview. The 2025-11-25 spec changed that by introducing Tasks: a first-class way to say "this isn't going to finish in the next thirty seconds, and that's fine."
This post is about what Tasks are, what they replace, and why the addition is more interesting than the headline makes it sound.
The Synchronous Trap
MCP, like JSON-RPC underneath it, started life as a request/response protocol. You send tools/call. You get back CallToolResult. The transport assumes that pair of messages will fit comfortably inside one HTTP round-trip, or one back-and-forth on a stdio pipe, or one quick SSE exchange.
For most tools, that assumption is fine. search_customers runs a database query and returns in 200ms. read_file reads a file and returns immediately. calculate is, well, calculation.
But real systems have operations that don't fit this shape:
- Deployments that take minutes to roll out.
- Data refreshes that scan terabytes.
- CI runs that compile, test, and lint at their own pace.
- External integrations that legitimately take a while because they're sending email, generating PDFs, or waiting on a human.
- Anything involving "approval" where a human has to actually look at something.
The original MCP answer was: don't do that. Make every tool fast. If you have something slow, kick off a background job from the tool and return a job ID, then have the model poll a separate get_job_status tool until done.
This worked, in the same way that walking up the stairs with a sofa works. You can do it. You won't enjoy it. And every team reinvented it slightly differently.
Enter Tasks
The 2025-11-25 spec added experimental support for Tasks (SEP-1686). The core idea is small and elegant: certain MCP requests (today, tools/call for clients, and sampling/createMessage/elicitation/create for servers) can be augmented with a task. The receiver returns a task handle instead of holding the original request open, and the requestor polls for status and eventually fetches the result. Capability negotiation declares exactly which request categories support task augmentation in each direction.
A task has a lifecycle. The spec defines explicit states:
- working: the task is running
- input_required: the task is paused, waiting for more input from the user
- completed: the task is done; the result is ready to fetch
- failed: the task ran but produced an error
- cancelled: the task was cancelled (by the client or the server)
That state machine alone solves a lot of problems. The "input_required" state is particularly clever: it gives a clean answer to operations that need to ask the user a question mid-flight, which previously required hacking around the request/response model. (Elicitation, which landed in 2025-06-18, becomes much more powerful when it can be invoked from inside a long-running task instead of having to fit inside a single tool call.)
Why This Is Not Just "Async"
The first time I read about Tasks, my reaction was: "Okay, MCP got promises. Took them long enough."
I was wrong. Tasks aren't just async/await for the protocol.
The interesting design choice is that the client decides whether to wait. In a normal async API, the server decides whether something runs synchronously or asynchronously, and the client lives with it. In MCP Tasks, the client (the requestor) decides whether to augment a request with a task, choosing between a synchronous call and a task handle. The server doesn't unilaterally upgrade a synchronous call; instead it controls which tools may be run as tasks by declaring each tool's execution.taskSupport as forbidden, optional, or required (a required tool returns an error if the client doesn't augment the call).
That's not promises. That's a negotiation about how long the client is willing to wait.
It also opens up patterns that weren't really possible before:
- Background work that survives client disconnects. Start a task. Close your laptop. Open it tomorrow. The task is still there. Fetch the result.
- Multi-step approvals. The task pauses at
input_required, the user gets a notification in their host UI, they answer, the task continues. - Cancellation that actually works. Previously, cancelling a tool call meant ignoring its result. Now there's an explicit
cancelledstate and a way for the server to clean up. - Resumable interactions across sessions, devices, or even different clients of the same MCP server.
These aren't new ideas in distributed systems. They're new in protocols designed for AI agents, and that's where the leverage is.
What This Means for Tool Design
The introduction of Tasks doesn't mean every tool should be a task. The default is still synchronous, because most tools really should be fast.
But for the operations where Tasks fit, they change how you should think about tool design:
1. Stop returning fake "job started" messages. If your tool kicks off background work, return a task handle. The client knows what to do with one. It doesn't know what to do with a string that says "Started job 47, check back later."
2. Stop polling from the model side. A model that calls get_status in a loop is burning your token budget and not actually waiting on anything useful. With Tasks, the client (Spring AI, Claude Desktop, whatever) handles polling for you, with proper exponential backoff, at a layer the model doesn't need to think about.
3. Use input_required instead of inventing your own approval dance. I have seen MCP servers implement approval flows by returning a tool result that says "Please call approve_deployment(approval_token=...) next." This is, charitably, a workaround. A task that transitions to input_required and uses elicitation to ask the question is the protocol-native version.
4. Design your tool's granularity around the task boundary. "Run database migration" is one task with several phases. "Run database migration phase 1, then phase 2, then phase 3" is three separate tools that the model has to coordinate, badly. Tasks let you fold internal complexity inside a single logical operation.
Where It's Going
Tasks are marked experimental in 2025-11-25. That word matters: SDKs may implement them at different speeds, transport semantics may shift, edge cases may produce surprising behaviour. If you're building production servers today, treat Tasks as something to plan for rather than something to bet the farm on this quarter. The current draft spec actually moves Tasks out of the core protocol and into an official extension (io.modelcontextprotocol/tasks) under SEP-2663, with a redesigned API (polling via tasks/get, client input via tasks/update). Watch that work carefully if you're writing against the experimental version today.
But the direction is clear. The 2026 MCP roadmap leans heavily into stateless servers, agent-to-agent coordination, and longer-lived interactions. All of those need a vocabulary for "this is going to take a while." Tasks give the protocol that vocabulary for the first time.
There's a deeper architectural point hiding in here. Up until Tasks, MCP encoded one specific kind of interaction: a human (or a model) initiates a step, something happens immediately, a result comes back. That's the shape of a conversation. With Tasks, the protocol can also encode the shape of an operation, something that has its own lifecycle, independent of any particular conversation turn.
Agents, especially multi-agent systems, fundamentally need both. A coordinator agent should be able to dispatch a long-running task to a worker agent, get a handle, do other things, and check back later. That's how real systems are built. Tasks are MCP catching up to that reality.
The Counterintuitive Bit
Here's the thing about Tasks that surprised me when I sat with it.
The most useful Task is not the one that runs for ten minutes. The most useful Task is the one that runs for fifteen seconds.
Long operations (minutes, hours, days) are obvious candidates. Everyone agrees those should be tasks.
But there's a vast middle ground of "long enough to be awkward, short enough that you didn't bother engineering for it." A tool that takes 8-12 seconds. A tool that takes 25 seconds on a bad day. A tool that has to call an external API whose latency is 95th-percentile-bimodal. These are the ones that quietly poison user experience. They're too short to engineer like a job. They're too long to ignore.
Tasks let you handle the "awkwardly long" case the same way you handle the "obviously long" case. That uniformity is the actual win. You stop deciding, per tool, which side of the synchronous/asynchronous fence to land on. You just return a task handle when it makes sense, and the client figures out the rest.
Reading the Tea Leaves
If you want to know which direction MCP is moving, the addition of Tasks is one of the strongest signals you'll find. The protocol started as a way to expose tool calls to AI models. It's becoming a way to coordinate work with AI models. Those are different products. The transition is mostly silent, but Tasks are one of the loud moments.
Two more developments to watch in the same direction:
- Agent Communication. The 2026 roadmap calls out an "Agent Communication" priority area, with an Agents WG closing operational gaps around Tasks (retry semantics, expiry policies). The combination of Tasks plus richer agent-communication semantics is what you'd build a multi-agent system on top of.
- Transport Evolution and Scalability. Also on the 2026 roadmap. Pairs well with Tasks: a stateless task handler can be horizontally scaled because the state lives in the task itself, not the server process.
The 2025-11-25 spec is the first time MCP felt less like an RPC and more like an orchestration substrate. Tasks are the centerpiece of that shift.
If you're building Spring AI + MCP servers and want a deeper look at long-running operations, asynchronous tool composition, and the patterns that surround Tasks (resilience, retries, idempotency), the MCP Architecture Patterns course has a thread of these patterns running through it. That course is coming soon.
If you want to understand the full evolution of the spec from the first 2024 release to today, the MCP Ecosystem module walks through every revision and what it added.
Either way: MCP stopped watching the clock. Your tools can finally take as long as they actually take.