A deploy agent that triggers a Kubernetes rollout faces an awkward choice. It can hold the connection open for the twenty minutes a careful rolling update takes, pinning server memory and a load-balancer route to a conversation that is mostly waiting. Or the server can stash session state somewhere — a session store, sticky routing, an SSE stream it nurses back to health on reconnect — so the agent can ask "are we there yet" later. Both options make the infrastructure stateful to serve a workflow that is conceptually simple: start a rollout, watch it, and ask a human before promoting to production.
The MCP specification released on July 28, 2026 removes that awkwardness at the protocol layer. The headline for agent builders is a stateless core: no initialize handshake, no Mcp-Session-Id, any server instance able to serve any request behind a plain round-robin load balancer.
On top of that core sit the two primitives this post builds with. The Tasks extension lets a tool call return a task handle immediately while the work runs in the background. Multi Round-Trip Requests let the server ask for input mid-call — including a human approval — without ever initiating a request of its own. Together they are exactly the machinery a deploy agent needs: return fast, report status on demand, and stop for a human decision at the dangerous step, all without the server holding session state.
This post walks through a concrete Kubernetes implementation: three tools, a task lifecycle wired to Deployment conditions, an approval gate with an expiry rule, and a durable audit trail. The design target is a self-hosted PaaS where an agent can ship a preview, wait out the rollout, and request a human-approved production promotion — and where an abandoned approval can never silently become a privileged action later.
What the July 2026 release actually changed
Three mechanisms matter for this design, and each replaces a stateful pattern with a stateless one.
Tasks graduated to an official extension (io.modelcontextprotocol/tasks, SEP-2663). A tools/call can now answer with resultType: "task" — a handle, not a result. The client drives the lifecycle from there: tasks/get polls status, tasks/update submits input back into a running task, tasks/cancel stops one. The redesign deliberately replaced the old blocking tasks/result call with polling and removed tasks/list entirely, and servers may return task handles unsolicited, without the client opting in per request. Status can also stream as notifications/tasks over the consolidated subscriptions/listen channel with a taskIds filter. One gating rule to remember: the client must declare the Tasks extension, or the server is not allowed to answer with a task at all.
Server-initiated requests are gone, replaced by Multi Round-Trip Requests (SEP-2322). Previously, if a server needed input mid-call — an elicitation prompt, a sampling request — it issued its own JSON-RPC request down an open stream. Under MRTR the server instead returns an InputRequiredResult: resultType: "input_required" plus inputRequests keyed by server-assigned names, plus an opaque requestState blob. The client gathers the answers and re-issues the original call carrying inputResponses and the echoed requestState. Because everything the server needs arrives in the retry payload, any instance can pick it up.
A companion rule (SEP-2260) tightened the other side: server-initiated prompts may only happen while the server is actively processing a client request — a user is never prompted out of nowhere, and every elicitation traces back to something the agent started.
The transport went stateless. The initialize handshake (SEP-2575) and the Mcp-Session-Id session (SEP-2567) are removed; version, client info, and capabilities travel in per-request _meta, and a mandatory server/discover RPC advertises what the server supports. Requests carry Mcp-Method and Mcp-Name headers so gateways route without inspecting bodies. The old guardrails of Roots, Sampling, and Logging are formally deprecated, and tool schemas get full JSON Schema 2020-12.
The mapping for a deploy agent is direct:
| Old stateful pattern | New stateless primitive |
|---|---|
| Hold an SSE stream open while the rollout runs | Return a task handle; client polls tasks/get |
| Server-initiated elicitation down that stream | InputRequiredResult plus client retry with inputResponses |
| Session store plus sticky routing to resume after reconnect | Self-contained retries; any instance serves any request |
| Mid-task human input needs a side channel | Mid-task input arrives via tasks/update |
The deploy agent design, end to end
The server exposes three tools: deploy_preview, promote_production, and rollback_service. The interesting one is promote_production, because it exercises both new primitives in a single flow: a long wait followed by a human decision. Its input schema takes the deployment name, namespace, target image digest, and an approval timeout; its first response is never a result, only a handle:
{
"resultType": "task",
"taskId": "promo-7f3a2c",
"status": "running",
"statusMessage": "Rollout started for web/api: watching 12 replicas"
}Behind that handle, the server is not holding a connection. It records the task (task ID, requested image digest, the Deployment's current observedGeneration, timestamps) in durable storage and returns. Everything after this point is client-driven polling plus Kubernetes truth. Each tasks/get for promo-7f3a2c re-reads the Deployment and translates its conditions into task status:
updatedReplicas < replicasor theProgressingcondition still churning:running, with astatusMessagelike "8 of 12 replicas updated, 2 unavailable".ProgressingreportingProgressDeadlineExceeded, orAvailabledegraded past a threshold: terminalfailed, with the condition message inline so the agent can reason about it.- All replicas updated and
Available=Trueat the expectedobservedGeneration: the rollout leg is done, and the task parks atinput_requiredwith an approval request attached.
That parked state is where MRTR and Tasks meet. The tasks/get response carries inputRequests with a single elicitation — approve or deny, plus a reason — scoped to the exact revision the server watched:
{
"taskId": "promo-7f3a2c",
"status": "input_required",
"inputRequests": {
"approve": {
"type": "elicitation",
"message": "Promote web/api to sha256:9f2c… in production? Rollout healthy: 12/12 available.",
"schema": {
"type": "object",
"properties": {
"approved": { "type": "boolean" },
"reason": { "type": "string" }
},
"required": ["approved"]
}
}
},
"expiresAt": "2026-09-14T23:30:00Z"
}The client surfaces that prompt to a human, then answers through tasks/update with the decision. The server re-validates against live cluster state — same image digest, same observedGeneration, rollout still healthy — and only then flips production traffic and returns the terminal result. rollback_service follows the identical shape: its task watches the rollback rollout, then elicits approval before the rollback is declared complete, so a panicked agent cannot thrash production between revisions without a human seeing each step.
Notice what the server never does: hold a stream, remember a session, or push a prompt uninvited. Every request is self-contained. A load balancer can spray tasks/get polls and tasks/update answers across replicas freely, and a server restart loses nothing because the task record and the Deployment conditions both live in durable stores the new process re-reads.
The approval gate that cannot become a footgun
An approval prompt for a production promotion is a loaded mechanism: it converts a human click into a privileged action. Four rules keep it safe, and each exists because a specific failure mode is otherwise guaranteed.
Approvals expire, and expiry denies. Every elicitation carries an expiresAt, and a tasks/update that arrives after it gets a terminal denied, never a promotion. The same goes for the task itself: a task-level TTL sweeps parked tasks that nobody ever answers. This is the rule the whole design hinges on — an approval requested on Friday must not be answerable on Monday after the weekend changed everything. There is no "approve later"; there is only re-request against fresh state.
Approval is invalidated by state change. Between elicitation and answer, the world moves: someone pushes a new image, an autoscaler shifts replica counts, a node drains. Before acting on an approval, the server re-checks the revision it promised — image digest, observedGeneration, replica health. If anything drifted, the approval is void and the task re-elicits against the new state. A human who approved revision N never accidentally authorizes revision N+1.
Denial and expiry travel the same call path as success. A denied or expired approval returns through the normal task result, not an out-of-band error the agent has to special-case. The agent sees "promotion denied: reason" as the terminal outcome and can do the useful things: report back, fix the issue, request a fresh approval. A gate that strands the agent in a suspended call is a gate operators learn to bypass.
Every decision lands in a durable audit trail. Task creation, each status transition, the elicitation request, the approval or denial with its reason, the revision actually promoted — all appended to an audit log that outlives the task. Concretely: Kubernetes Events on the Deployment for operator visibility, plus an append-only record (who approved, which digest, which observedGeneration, when, and whether expiry or invalidation ever fired) for post-incident review. When a sub-agent rolls back a live service at 3 AM, "which human approved what, exactly" must be answerable from the log, not from chat history.
What to watch before you ship
Four integration realities can still bite, and each has a concrete guard.
First, the client must declare the Tasks extension for the server to answer with a task at all. A deploy agent that forgets the declaration gets an error where it expected a handle. Advertise the extension in server/discover, and fail closed with a message that names the missing declaration rather than a bare capability error.
Second, the elicitation capability is optional on the client side. Some clients will never show your approval prompt. Decide the fallback up front: queue the approval in a dashboard or webhook and park the task, or refuse promotion tools entirely for clients without elicitation. What you must not do is treat "no human available" as approval.
Third, an in-band elicitation answer is not proof a human approved. A compromised or confused client can fill in approved: true by itself. Human-in-the-loop confirmation belongs in host UI the server trusts, with the answer sealed (signed request state, capability-scoped grants) so a bare boolean from the wire is never sufficient. Keep destructive tools out of the advertised schema for clients that cannot meet that bar, and never put an approval field in the tool's own input schema where a hijacked model could fill it directly.
Fourth, check SDK support for the pair, not just one half. Tasks and MRTR shipped together but SDKs adopt them unevenly: the C# SDK's v2 line aligns with the 2026-07-28 revision, the TypeScript and Rust SDKs implement the task lifecycle including mid-task tasks/update, and frameworks like FastMCP gate task behavior to modern connections. Verify the exact combination your agent and server use — task creation, polling, mid-task elicitation, cancellation — against a real 2026-07-28 peer before relying on graceful degradation that may not exist yet.
Waiting is now a feature, not a failure mode
The stateless turn in MCP reads, at first glance, like transport plumbing: headers, discovery, cache TTLs. But for deploy agents it changes what is buildable. A rollout that takes twenty minutes no longer needs a twenty-minute connection or a session store babysitting it. A promotion that needs a human no longer needs a side channel to reach one. Task handles, polled status, mid-task elicitation, and self-contained retries compose into an agent that starts work, walks away, and comes back exactly when a decision is needed — running on infrastructure no more exotic than a replicated HTTP deployment.
That composition is also what makes agents safe operators rather than fast ones. The expiry rule, the state-change invalidation, and the audit trail are not extras bolted onto the protocol; they are the natural shape of a flow where every step is explicit, every prompt is traceable to a request, and nothing privileged happens on stale authority. Build the agent on those guarantees, and "deploy from chat" stops sounding like a dare.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with agents as first-class operators. Star the repo on GitHub or deploy your first app today.



