Ask any team that's wired an AI agent into a deploy pipeline how they handled a build that takes four minutes, and you'll get the same answer: they invented a job queue by hand. A create_deployment tool that returns instantly, a get_build_status tool the agent calls in a loop, and a promote_deployment tool it fires once the second tool finally says "done." Three tools, one bespoke state machine, repeated slightly differently in every MCP server that has to survive an operation longer than a single request-response round trip.
As of the 2026-07-28 MCP specification — the largest revision to the protocol since launch — that pattern has an official name and a standard wire format: the Tasks extension, promoted out of experimental status after AWS and others ran it in production long enough to justify a redesign. A single tools/call can now return a durable task handle instead of blocking or forcing the caller to invent a second and third tool just to check on the first one. For a deploy-from-chat MCP server — the exact shape of tool bex is building — that's the headline.
It's also not the whole story. Tasks standardizes how an agent asks "is it done yet," not what happens when the agent asks twice, loses the connection, or asks to cancel a build that's already finished. Those are still the platform's problem, and the spec says so explicitly.
What Tasks Actually Specifies
Strip away the announcement-blog framing and Tasks is a small, well-scoped addition: a durable state machine attached to a tool call, plus three new methods to operate on it.
When a client wants a tool call to run as a task, it augments the request; the server responds immediately with a task handle instead of blocking until the work finishes. From there:
tasks/get— poll the task's current statustasks/cancel— request that the task stoptasks/update(server → client) — push a status change instead of making the client poll blind
A task moves through five states — working, input_required, completed, failed, cancelled — and the state transitions are append-only: once a task lands in one of the three terminal states, it can never move again. That matters more than it sounds like it should, because it means a completed deploy can't quietly flip to failed an hour later just because some retry logic somewhere got confused. Every task also carries a server-set TTL — the spec's own example uses a one-hour retention window — after which the result simply isn't queryable anymore, task ID included.
That's genuinely useful plumbing. It replaces an earlier, blocking tasks/result design with pure polling via tasks/get, and it means an agent client, an SDK author, and a server implementer all now agree on what "check back later" looks like on the wire, instead of every MCP server picking its own shape for a status field.
Before and After: Collapsing the Bespoke Dance
Here's what a deploy tool chain looks like today, without Tasks, in the pattern teams have already converged on independently:
1. agent calls create_deployment(repo, branch) → returns { deployment_id }
2. agent calls get_build_status(deployment_id) → returns { status: "building" }
...agent polls this in a loop, sleeping between calls...
3. get_build_status(deployment_id) → returns { status: "succeeded" }
4. agent calls promote_deployment(deployment_id) → returns { url }Every field name, every poll interval, every "what does status mean here" is a decision the platform author made alone. An agent that's used one deploy MCP server has learned nothing transferable about the next one's status-polling shape.
With Tasks, the same operation collapses into one tool call the agent already knows how to drive, because the polling contract is now the protocol's, not the platform's:
1. agent calls deploy(repo, branch) as a task → returns { taskId, status: "working" }
2. agent calls tasks/get(taskId) → returns { status: "working" }
...same polling loop, but the shape is standard...
3. tasks/get(taskId) → returns { status: "completed", result: { url } }The functional difference is small — an agent still polls, still waits, still needs a sane interval — but the protocol difference isn't: create_deployment / get_build_status / promote_deployment are three platform-specific tools an agent has to learn from scratch. deploy returning a task and tasks/get polling it are two protocol-level operations every Tasks-aware client and SDK already implements. A deploy MCP server that adopts Tasks doesn't have to document its own polling contract at all — the agent's MCP client library already speaks it.
That's the concrete thing the spec revision buys a deploy-from-chat platform: one less bespoke API surface to design, explain, and keep stable across every long-running tool it ships, not just deploys — migrations, multi-region rollouts, anything that outlives a single request.
What Tasks Doesn't Solve
This is where the "first-class async primitive" framing runs ahead of what actually shipped, and it's worth being precise about the gap instead of waving it off, because each one lands directly on a deploy tool's blast radius.
Idempotency is explicitly out of scope. The protocol does not require idempotent task creation — nothing stops an agent from calling deploy(repo, branch) twice. Picture the ordinary failure mode: an agent calls deploy, the connection drops before the task handle comes back, and the agent — reasonably, since it has no confirmation the first call landed — calls deploy again. Tasks gives you a clean way to poll one deployment to completion. It gives you nothing that stops two deployments of the same branch from racing each other, and "which one won" is now a question your infrastructure has to answer, not the protocol.
Cancellation is cooperative, not guaranteed. tasks/cancel sends an acknowledgment that the server received the cancellation request — it does not promise the underlying operation actually stopped. A build that's 90% through compiling doesn't necessarily stop compiling because the client asked nicely. So a task can land in cancelled state while the deploy it represented finished anyway and is now serving traffic. An agent that trusts the task's terminal state as the truth about what's running in production is trusting the wrong source.
TTL is a retention policy, not a safety net. A short TTL is good practice — the spec's own guidance leans toward short-lived task IDs specifically so they can't be guessed or enumerated — but it means a multi-hour agent session (increasingly common as agents like Cognition's Devin do their own re-planning without stopping for human input) can easily outlive the task it started. Ask tasks/get about a deploy from two hours ago and the honest answer might just be "that task no longer exists," which is a materially worse answer than "it succeeded" or "it failed" for an agent deciding whether it's safe to retry.
None of this is a defect in the spec — the Tasks extension's own design notes are upfront that idempotent task creation is a known gap, not an oversight nobody flagged. It's a scoping decision: Tasks standardizes the shape of "long-running operation," not the safety properties around retries, cancellation, or state ownership. Reading it as "MCP now handles async deploys" instead of "MCP now handles async polling" is the mistake worth not making.
What a Deploy MCP Server Still Has to Build
None of the three gaps above are exotic problems — they're the same ones any job queue has always had to solve, and Tasks doesn't remove the obligation, it just narrows exactly where it lives. For a platform whose MCP server hands out real deploy and rollback authority, three things have to sit underneath Tasks rather than be assumed away by it:
-
A server-generated idempotency key, not an agent-supplied one. If the key comes from the caller, a confused or malicious agent can simply mint a new key each time and defeat deduplication entirely. Keying on something the platform already knows to be stable — app ID, target environment, and the git SHA being deployed — means a second
deploycall for the same commit against the same environment resolves to the same underlying operation, task-layer confusion notwithstanding. -
Server-side source of truth for "last known good," independent of any task's lifecycle. A task's TTL expiring, or an agent's context window losing track of which task ID mattered, can't be allowed to erase the platform's own record of what's actually running. The task is a status window into an operation; it should never be the only place that operation's outcome is recorded.
-
A reconciliation step after every cancellation, not a client-side assumption that
cancelledmeansstopped. If a task reports cancelled, the platform's own control loop still has to check whether the deploy it represented actually rolled back, actually stopped, or quietly finished anyway — and correct the record if the task's terminal state and reality disagree.
That list is a smaller, sharper version of what any deploy tool already owed an agent caller before Tasks existed. What changes is that the polling wire format — the part every platform used to reinvent slightly differently — is no longer bex's problem to design from scratch, freeing the actual engineering effort for the part that was never going to be solved by a protocol extension anyway: making sure two deploys of the same commit don't race, and that "cancelled" in the task log means what it says on the tin.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Building deploy and rollback tools that agents can call safely, including the idempotency and reconciliation work a wire-format spec was never going to do for us, is exactly the kind of detail an MCP server for real infrastructure has to get right. Star the repo on GitHub or deploy your first app today.



