Stateless by design
Scale-to-zero via stateless sessions; now default with the MCP spec change.
Like many during the spring of 2026, I was excited to finally turn ideas into applications using generative language models. Given the increased interest in defence procurement, and with a background in security frameworks and compliance, an early project was to build an Azure compliance scanner to help defence contractors complete the Canadian Program for Cyber Security Certification (CPCSC) level 1 readiness assessments. More detail can be found at cpcsc-mcp.
After a review of options, and completing Anthropic’s MCP Advanced Topics course, decided to build cpcsc-mcp as a MCP server using Azure Container Apps that can scale to zero when it’s not being used.
The problem
MCP server sessions are usually stateful. The server hands the client a session ID,
the client sends it back with every request, and a long-running process keeps OAuth tokens and
context in memory. On a laptop using stdio that’s fine, because the process sticks around for the whole
conversation. This breaks when Azure Container Apps scale-to-zero with minReplicas: 0. Replicas shut
down between requests take the sessions in memory with them.
The decision
I addressed this by setting stateless_http and json_response to true for FastMCP. If the session disappears
when idle, each request has to carry everything it needs. Architecturally, this is set in the deployment config.
main.py reads MCP_STATELESS_HTTP at startup, and infra/main.bicep sets it to true next to minReplicas: 0.
When using cpcsc-mcp over stdio or HTTP-SSE locally, it runs unset.
Making the transport stateless does not make the the whole app stateless. The static API key survives a cold start because the server re-registers it every time it boots. OAuth tokens don’t survive, so if you come back after the app has idled out, you’ll sign in again. The assessment cache lives in each replica, so after a cold start the first request goes back to Azure and also waits for the container to spin up. This was the consequence for scaling to zero when the app sits idle.
Then the spec caught up
Not long afterwards, MCP revision spec 2026-07-28 dropped and stateless became the default for the protocol itself.
Sessions are gone from Streamable HTTP. There’s no more Mcp-Session-Id. The initialize
handshake is gone too, so every request now carries its own protocol version, capabilities and
identity. SSE resumability and the standalone GET stream were dropped, which means a broken
stream is handled by the client simply retrying with a new request ID. And if a server really
does need to remember something between calls, it hands back an explicit ID as a normal tool
argument and the model passes it along next time. The state travels with the request instead of
living on a particular server.
What that means in practice is that a remote MCP server no longer needs sticky sessions or a shared session store. You can put it behind an ordinary round-robin load balancer like any other web service, which is exactly what scale-to-zero platforms are built for.
Where my build currently sits
As often the case when specs evolve, this is a breaking change for my MCP server in a number of
other areas; and I am still not using MCP spec 2026-07-28. cpcsc-mcp is still pinned to 1.x of the MCP SDK. It’s
stateless in how it’s deployed, but still speaks the previous protocol, handshake and all.
Pinning to 1.x is intentional. Dependabot bumped mcp to 2.0.0 on its own, the new container failed
at startup, and Azure’s health checks rolled traffic back before anyone was affected. Since then
Dependabot blocks major mcp upgrades. Minor versions and patches, security fixes included, still come through automatically.
The 2.0 move is tracked as its own project, because it’s real work. FastMCP has been renamed,
the transport settings moved out of the constructor, and the OAuth provider interface, which this
server depends on more than anything else in the SDK, was reworked.
For now cpcsc-mcp is staying on 1.x. My MCP The server never calls back to the client for input
(no elicitation, no sampling, no roots/list), so I am not missing features from the new spec
forcing me to do the rewrite. Every tool finishes its work in a single call, so there’s no hidden
state between calls that I’d need to redesign. And nothing actually broke when the spec was released on July 28.
Deprecated features keep working through a transition period, which gives me time to do the port properly.
The takeaway
I decided to build cpcsc-mcp stateless to save money running Container Apps in Azure. The spec went stateless so MCP could
scale the way the rest of the web does.
If you’re putting MCP servers on Container Apps, Cloud Run, Lambda, Fargate or anything else that scales elastically, here’s some advice. Use Streamable HTTP. Get rid of anything that depends on sticky sessions or on memory lasting between requests. Have clients retry cleanly instead of trying to resume a dropped stream.
cpcsc-mcp checks Azure environments against Canada’s CPCSC controls. Throughout the fall will be porting
to MCP 2.0 and promoting cpcsc-mcp.
Hope this was useful - thanks for reading. Any questions please contact me at [email protected]