Stateless MCP, Durable Agent State
A load balancer should not need a good memory. If an MCP tool works only when the next request reaches the same process, the transport is carrying part of your application architecture by accident.
MCP 2026-07-28 makes that boundary harder to ignore. The protocol core uses self-contained requests and per-request capabilities. Durable business work still needs state. The useful change is that you now have to decide where that state belongs.
My recommendation is to keep MCP request handlers replaceable and make workflow state explicit. That gives backend teams a clearer contract to test, operate and secure. It does not give them exactly-once execution for free. No protocol upgrade comes with that particular gift.
What actually changed
The July specification removes the initialize/initialized handshake and protocol session IDs from the modern core. Requests carry protocol version, client information and capabilities in metadata. A client can ask server/discover about supported behavior, but discovery is not a required opening ceremony.
On Streamable HTTP, every request is a POST. MCP-Protocol-Version, Mcp-Method and, where applicable, Mcp-Name mirror request information into headers. The body remains authoritative; mismatches must be rejected. Routing on the header and executing a different body would turn an infrastructure convenience into a security bug.
This describes the 2026-07-28 contract. Older clients and servers still exist, and a dual-era implementation has to preserve their negotiated behavior. Do not remove a legacy session dependency merely because the newer SDK can compile your code.
Three kinds of state deserve three different homes
Request metadata says what this client supports now. It is not your workflow database, and client identity metadata should not be mistaken for authenticated authority. Validate the caller through your authorization boundary.
Continuation data explains how to resume a protocol exchange. Multi Round-Trip Requests, or MRTR, let a server return input_required with requests for additional input. The client answers and retries the original operation with matching inputResponses. The continuation must remain meaningful when another server instance handles the retry.
Business state records what the application has actually done: which export was accepted, which records were processed, which approval applies and where the resulting artifact lives. Store that state in infrastructure whose durability and access rules you control. Treat it as a product contract, not a side effect of keeping a socket open.
A concrete example: an export that outlives the request
Consider a hypothetical agent preparing a customer-data export. The request identifies the tenant, dataset and requested purpose. The tool may need a person to confirm the scope before starting. After confirmation, the export could run for several minutes. Nothing here should depend on a particular web worker remaining alive.
Use MRTR for the missing input exchange. Keep the pending confirmation scoped to the exact operation and authorized principal. On resumption, revalidate the current permission and the confirmed scope. An old confirmation should not authorize a newly expanded dataset.
For execution that needs to continue asynchronously, use the Tasks extension only when both sides support it. The extension introduces a task result and operations including tasks/get, tasks/update and tasks/cancel. A server must durably create a task before returning its handle. Tasks is an opt-in extension, not a capability every MCP client can be assumed to implement.
If your supported client does not implement Tasks, define and document an application-level job tool contract instead: start an export, inspect the job and fetch its result. That is your interface design, not a claim that the core protocol supplies asynchronous execution automatically. Prefer a clear unsupported-capability response to silently holding an HTTP request open indefinitely.
Illustrative application record — not an MCP wire payload job_id: export_7f3 tenant_id: tenant_42 requested_by: principal_18 scope_digest: sha256(...) operation_key: customer_export_20261103_001 status: queued | running | awaiting_review | succeeded | failed revision: 4 result_uri: null expires_at: ...
This record deliberately separates a business job from the protocol task that exposes it. Your application may keep a job longer than the extension retains its task metadata. Define that mapping and its cleanup policy rather than assuming the two lifetimes are identical.
A handle is an address, not permission
An opaque job ID is useful because it lets any authorized request handler locate the work. It should not grant access just because the model knows it. Check tenant and principal on every lookup, update and download. Bind the handle to the approved scope, and reject a cross-tenant request even if it presents a valid-looking ID.
For the export example, make the operation key unique within the tenant and operation type. Commit job creation and dispatch intent atomically, then let a worker claim work using a guarded transition. Those are engineering recommendations for this application, not MCP requirements. They make a retry after an ambiguous response less likely to create a second export.
A protocol request ID correlates a response. Do not use it as the sole business deduplication key. Persist the business operation identity before an irreversible action and define what happens when the caller retries with the same key but different arguments. Rejecting that mismatch is usually easier to reason about than guessing which request should win.
Model the result carefully too. In the Tasks extension, completed can contain a tool result with isError:true; failed represents a JSON-RPC execution error. Your business outcome must be read from the result, not inferred from the pleasant sound of the word completed. Also specify result retention and what callers see after the task expires.
Migrate with failure cases, not just a new happy path
The most useful migration test is to change the server instance between requests. Then repeat it while changing credentials, withholding capabilities and losing responses. A small matrix can expose assumptions that normal integration tests never touch.
- Modern client and modern server: execute without initialization; reject unsupported versions clearly and test a mutually supported retry.
- Dual-era client and legacy server: verify the documented fallback. A recognized modern error must not be treated as proof that the server is legacy.
- Header/body disagreement: reject the request before executing the tool; verify the gateway and service interpret the same operation.
- MRTR resumption on another instance: accept matching input, reject altered scope or the wrong principal, and ensure no action was committed before required confirmation.
- Tasks capability missing or task creation interrupted: return the documented fallback or error; never expose a task handle that cannot be retrieved.
- Duplicate start, worker restart and lost response: recover the same application job through its operation key and guarded state transitions.
- Expired task and revoked access: make retention behavior explicit and deny result access after the caller loses authority.
Measure these tests through stored records and downstream side effects. A reassuring assistant answer is weak evidence that the export happened once, used the right scope or remained inaccessible to another tenant.
Key takeaways
- Keep MCP request handling replaceable; store durable business work behind explicit application handles.
- Separate per-request capabilities, protocol continuation and business state. Each has a different lifetime and trust boundary.
- Negotiate Tasks explicitly, distinguish task status from business success, and document retention and fallback behavior.
- Test cross-instance resumption, authorization changes and ambiguous retries before removing legacy session assumptions.
Nieve's published Generative AI & Agentic Workflows offering includes secure, tailored workflow integration.
I would make the state boundary explicit before optimizing the transport. Once the job identity, authority and recovery contract are clear, a stateless MCP endpoint becomes an ordinary service you can replace. Without that boundary, you have merely moved the same fragile workflow behind a newer protocol.

Chief Technical Officer
