An agent tells your platform to deploy. The build takes nine minutes, the production promotion needs a human to say yes, and the client on the other end is a model that may time out, retry, or wander off mid-conversation. If your deploy interface is a single request that blocks until it returns a response, every one of those facts is a bug waiting to happen: the restarted client retries and double-deploys, the approval arrives after the HTTP connection is long dead, and nobody can say whether the rollback ran.
This was, until recently, the shape of every tool call in the Model Context Protocol. A client called a tool, blocked, and got a result — fine for "query the database," broken for "ship this to production." The MCP 2026-07-28 specification fixes it with two primitives that together form something the protocol never had before: a durable contract for long-running, approval-gated work. Tasks (the SEP-2663 extension) turn a tool call into an asynchronous handle you poll, update, and cancel. Multi-Round Trip Requests (SEP-2322) let the server pause mid-call, ask for more information or an explicit approval, and resume when the client answers.
This post maps those two primitives onto the thing an agent-operated PaaS actually needs: a deploy state machine — task creation, build progress, an approval boundary before production promotion, idempotent retry, cancellation, and rollback — that never pretends one HTTP round trip can safely represent a ten-minute build or an irreversible operation.
What MCP 2026-07-28 actually changed
Two shifts matter, and they arrived together because the second required the first. The 2026-07-28 revision makes the protocol stateless by default: protocol-managed sessions (Mcp-Session-Id) and the initialize handshake are gone, replaced by per-request metadata and a server/discover call. With no session to hang server-initiated traffic on, the old server-to-client requests — sampling/createMessage, elicitation/create, roots/list — had to go too. That forced both redesigns below.
| Dimension | Before (2025-11-25) | 2026-07-28 (SEP-2663 + SEP-2322) |
|---|---|---|
| Long-running work | Experimental core tasks; client requests task execution per call | Official io.modelcontextprotocol/tasks extension; client advertises support, server decides per request |
| Capability check | Multi-layer: global, per-tool, per-operation | Single extension flag |
| Mid-call input | Blocking elicitation side-channel; client stops polling, opens a blocking connection | Non-blocking: input_required status, request inline in tasks/get, answered via tasks/update |
| Approval / follow-up questions | Server-initiated elicitation/create on the session | MRTR: server returns input_required with inputRequests + requestState; client retries the original call with inputResponses |
| Result retrieval | Separate, blocking tasks/result | Inlined in the final tasks/get response |
| Task listing | tasks/list (paginated) | Removed entirely |
| Cancellation | Synchronous, returns task state | Ack-only and cooperative: the server acknowledges, then handles it asynchronously |
| Error model | One bucket | Protocol error → failed; tool returning isError: true → completed with that result |
The table is adapted from Vikram Vaswani's AAIF walkthrough, the clearest before/after account of the redesign. The headline: five task statuses (working, input_required, completed, failed, cancelled), three methods (tasks/get, tasks/update, tasks/cancel), one discriminator (resultType: "task"), and no blocking anywhere in the loop.
The durable deploy state machine
Here is the core deliverable: each phase of a production deploy, mapped to the primitive that owns it and the wire calls that implement it. If you take one artifact from this post, take this table.
| Deploy phase | Primitive | Wire shape |
|---|---|---|
| Queue the deploy | Task creation | tools/call deploy with task support advertised → immediate resultType: "task" handle + TTL, not a blocked connection |
| Build and push | Progress polling | Client polls tasks/get; server reports working with build/log tail in the response until the image exists |
| Await production approval | Approval boundary (MRTR) | Server returns input_required with an approval request; work pauses; nothing irreversible has happened yet |
| Approve or reject | Round trip | Client retries the original call carrying inputResponses and the opaque requestState echoed back unchanged |
| Promote and verify | Resumed task | Server continues the same task: rolling update, health checks, final tasks/get carries the completed result |
| Client died mid-deploy | Idempotent retry | Retry replays against the same task handle; the server deduplicates, so a retried call never becomes a second deploy |
| Operator aborts | Cancellation | tasks/cancel → acknowledged immediately, honored cooperatively; the task may still land completed if the abort loses the race |
| Release is bad | Rollback | A second, independent task with its own handle, approval round trip, and audit trail — never a mutation of the finished one |
Three properties make this a durable contract rather than a longer timeout. First, every phase survives the death of any single connection: handles and polls replace one held-open request. Second, the approval is a protocol state (input_required), not a webhook the server hopes someone is listening to — the deploy cannot proceed past it without an explicit round trip. Third, retry is safe by construction: because the task handle identifies the work, "I didn't get a response, so I'll ask again" converges on one deploy, not two.
Walking the wire: a deploy, round by round
Concretely, an agent shipping a service through an infrastructure MCP server now looks like this.
Round 1: the call that returns immediately. The agent calls a deploy tool with the service name, the image tag, and a target environment. It advertises the tasks extension in the request. The server validates the inputs, creates the deploy task, and answers within milliseconds with a task handle and a TTL. The nine-minute build is now the server's problem to run and the client's to observe — no open connection spans it.
Rounds 2–N: watching paint dry, cheaply. The agent polls tasks/get. Each response says working and carries whatever progress the server chooses to expose: build phase, log tail, image digest once pushed. If the agent's process restarts or the network drops, it resumes polling with the same handle. Nothing about the server's work depends on any particular poll arriving; the polls are reads, and rereading is harmless.
The approval boundary. The image is built and staged, and promotion to production requires a human. The next tasks/get returns input_required, with the approval request inline: what will be promoted, to where, and what the blast radius is. The task sits in that state for seconds or for a weekend — the protocol doesn't care, because no connection is being held. This is the exact pattern the C# SDK's MRTR documentation and the community's round-trip examples demonstrate with a literal "Approve production deployment?" elicitation: the server asks, the client answers on a later round trip, the original call completes across the gap.
The decision round trip. The human approves. The client retries the original tools/call, attaching inputResponses keyed to the input requests and echoing requestState back unchanged — the server's opaque bookmark proving this retry continues that task. The server validates the echo, records the approval decision in its own store, and resumes: rolling update, health checks, traffic shift.
The finish. A later tasks/get returns completed with the result inline: the new revision, the endpoints serving it, the verification outcome. If the rollout itself failed at the protocol level — the server crashed mid-promotion — the status is failed instead.
And if the deploy tool ran fine but reported its own failure (tests red, health checks never passed), that's completed with isError: true. The distinction between "the machinery broke" and "the machinery worked and the answer is no" is now explicit in the status — exactly what an agent needs to decide between retrying and escalating.
Two failure paths deserve emphasis because they are where the old request/response shape lied hardest. When the operator cancels mid-build, tasks/cancel is acknowledged at once but honored cooperatively — the task can still complete if the build finished first, so the agent must read the terminal status rather than assume the cancel won. And rollback is not an "undo" on the finished task; it is a fresh task with a fresh handle and its own approval round trip, which keeps the audit trail legible: deploy #4821 completed, rollback #4826 completed, each with its own decision record.
Why this looks like a Kubernetes Job
If the state machine above feels familiar, it should: it converges on the semantics Kubernetes worked out for batch work years ago. A Kubernetes Job has an identity separate from any client connection, a backoff-and-retry policy, a TTL after finishing, and terminal states you read rather than assume. MCP Tasks gives tool calls the same four properties: the handle outlives the connection, retries deduplicate against it, the TTL bounds how long the server keeps the receipt, and completed / failed / cancelled are states you observe via tasks/get, not outcomes you infer from a closed socket.
The parallel is more than aesthetic — it tells a platform team where to put the implementation. Microsoft's guide to long-running MCP tools on Azure Functions reaches for Durable Functions, checkpointing workflow state outside any single invocation, for exactly this reason. And as Mark Fussell put it in a recent conversation on durable execution for AI agents in Kubernetes, durable execution — not new agent vocabulary — is what actually changes when agent workloads go to production. The protocol now agrees: a deploy is durable execution with an approval gate, so model it as a task with rounds, not as a call with a long timeout.
The sharp edges
New primitives, new footguns. Five are worth pinning to the wall before you build on this.
Cancel is a request, not a command. tasks/cancel acknowledges receipt; the server processes it asynchronously, and a task can still reach completed. Any agent logic of the form "I cancelled it, therefore it didn't happen" is wrong. Always read the terminal state.
failed is not isError. A JSON-RPC-level failure during execution lands the task in failed; a tool that ran and returned isError: true lands in completed with the error as its result. Agents must branch on the status first and the payload second, or they will retry "tests failed" as if it were "connection dropped."
There is no tasks/list. The new extension deliberately removed task enumeration — in a stateless protocol there is no session scope to bind a listing to, and listings leak across authorization contexts. Your platform must track its own task handles per tenant and never depend on asking the server "what's running."
requestState must echo unchanged. MRTR's bookmark is opaque to the client for a reason: it is the server's proof of continuity. A client that drops, decodes, or "helpfully" normalizes it breaks the round trip. Treat it as an uninterpreted blob and send it back byte-identical.
Approvals must be server-authoritative. The round trip carries the human's decision, but the server — not the agent — must own the fields that matter: which environment, which revision, whether this promotion was actually approved. An agent must never be able to approve its own deploy by setting a flag on the retry; the server derives authority from the authenticated session and the recorded decision, and rejects a retry whose claimed approval doesn't match. The community's Kubernetes MCP server guides converge on the same rule from the other direction: read-only by default, an explicit approval gate for anything destructive, with the server holding the allowlist.
What this unlocks for a self-hosted PaaS
Step back and notice what changed about the division of labor. Before 2026-07-28, every agent-operated platform hand-rolled its own durability: job tables, polling endpoints, approval webhooks, idempotency keys — a bespoke workflow engine bolted onto the side of the tool interface, incompatible with everyone else's. Tasks plus MRTR standardize the 80% every platform was rebuilding: the handle, the poll, the pause-for-approval, the safe retry, the cooperative cancel. What remains platform-specific is the part that should be: what "deploy" means on your fleet, who may approve a production promotion, and how rollback is verified.
For a self-hosted PaaS, that standardization lands at the right moment. Agent-triggered deploys are already the majority workload on the big hosted platforms, and every one of those deploys is a long-running, approval-gated, occasionally-retried operation — precisely the shape the old synchronous tool call couldn't represent. A platform that exposes deploy, promote, and rollback as task-backed tools with MRTR approval boundaries gets a contract any spec-compliant agent can drive: the same agent, the same rounds, whether the fleet underneath is three Hetzner machines or three hundred.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Agents are first-class operators: deploy, promote, and roll back through durable, approval-gated contracts instead of bare API calls. Star the repo on GitHub or deploy your first app today.



