Build1 publisher2 min readPublished
MCP's stateless core moves a 30-minute approval pause into your database
A dev.to post says the 2026-07-28 specification dropped the initialize flow and Mcp-Session-Id, so MCP requests can land on any instance. The refund agent it walks through still breaks on the second pod.
The Engineer · Build desk

What happened
- The 2026-07-28 Model Context Protocol specification removed the session-oriented initialize flow and the Mcp-Session-Id header, according to a dev.to post, leaving requests self-describing.
- A request can now hit any of three MCP server instances behind a load balancer without the balancer pinning a client back to the instance that served its first call.
- In the version the post calls wrong, the paused run sits in a pending_runs dict in Server A's memory, so the approve request that the balancer sends to Server B returns KeyError.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- capability Once workflow state lives in Postgres, Redis or Temporal, any pod can pick up a run that paused on another one, and the agent process becomes disposable.
- decision Adopting the stateless transport forces a choice of durable store before the first workflow that waits on a human, not after the first lost approval.
- exposure A resumed run with no record of what it already called is free to hit the payment API twice, so the side-effect layer is what keeps a retry from moving money again.
- constraint Workflow status can no longer ride inside the model's message history, because a second process has to be able to read where execution stopped.
Statelessness at the transport layer buys one thing: the balancer stops caring which instance served the last call. According to the dev.to post, requests now carry enough information to be handled independently, with no Mcp-Session-Id to match and no session-oriented initialize flow to replay [2][3]. Three MCP server instances behind one balancer become interchangeable [4]. Session affinity comes out of the config [3].
The pause is where that stops helping. The post's worked example is a refund agent: it investigates the order, checks the refund policy, calculates the amount, requires human approval, and waits 30 minutes [6]. In that window the process might restart, a deployment might land, the request might reach another Kubernetes pod, or the machine might disappear, and the approval itself might arrive hours later [7].
The failing implementation is short. A module-level `pending_runs` dict, one write when the result comes back with `requires_approval`, one read in `approve(run_id)` [8]. The entry exists in Server A's memory, so the approve call that the balancer sends to Server B raises `KeyError` [9]. With two pods and even routing, half the approvals land on the pod that never held the run [15]. A deployment during the 30-minute wait makes it all of them, because the dict lives in the process that just exited [16]. An in-memory dict passes every test you run on one pod.
The post's fix is to persist workflow state outside the runtime, naming Postgres, Redis, Temporal and a durable runtime, so that any server can reconstruct the current workflow and the agent runtime becomes replaceable [10]. It then splits state three ways. Conversation state is messages, summary, user preferences and retrieved context, and it controls what the model knows [11]. Workflow state is `workflow_id: refund_39281`, `status: WAITING_FOR_APPROVAL`, `current_step: refund_confirmation`, `order_id: ORD_8821`, `refund_amount: 149.99`, and the author calls it distributed-system state, not prompt context [12]. Side-effect state is the flags for what already happened: `email_sent: true`, `refund_created: false`, `crm_updated: true`, and the post says that without it, retries become dangerous [13].
"MCP should not need to remember your connection," the author wrote. "Your agent absolutely needs to remember its work." [14]
One caveat on the record: this is a single post, and it describes the specification change in its own prose without reproducing the specification's wording [18].
The three-layer split costs a schema, a migration, and a read on every resume. It pays when a run can pause and when the resume can arrive at a different process [7]. An agent whose runs finish inside one request, calling tools that are safe to repeat, pays that cost and gets nothing back for it.
What to watch
- Whether the specification text itself matches the post's account that the initialize flow and Mcp-Session-Id are gone rather than deprecated.
- Whether MCP client SDKs keep session handling for compatibility with servers built against the older flow.
- Whether durable runtimes such as Temporal ship resume primitives shaped around the post's workflow and side-effect layers.