Comparison

Where should you deploy an MCP server?

Since the 2026-07-28 spec removed protocol sessions, most MCP servers can run on plain serverless behind a round-robin load balancer. Three questions decide it: do you serve change notifications, how long does your slowest tool call run, and do you need per-user OAuth. Here is the decision, target by target.

Mark

Head of Marketing, MCPOrbit

Published
Updated
· Updated
Read time
· 9 min read
A decision diagram routing an MCP server to one of four deployment targets. Three labelled gates read subscriptions/listen, slowest tool call, and per-user OAuth, and they branch to cards for edge runtime, serverless function, container, and local stdio.

Deploy your MCP server to ordinary serverless or an edge runtime unless one of three things is true. You need a long-lived host, meaning a container or a VM, if your server offers change notifications through subscriptions/listen, if a single tool call can run longer than your platform's response timeout, or if you terminate per-user OAuth and want the token exchange to stay on infrastructure you control. Everything else is now a normal stateless HTTP service, because the 2026-07-28 specification deleted the protocol-level session that used to force sticky routing.

Where should you deploy an MCP server?

Answer three questions in order. The first one that comes back yes picks your target, and if all three come back no you can use anything, including the cheapest serverless tier you have.

  • Does your server implement `subscriptions/listen` for change notifications? If yes, you need a runtime that allows long-lived connections. That rules out most short-timeout function platforms and points at a container, a VM, or an edge runtime with explicit streaming support.
  • Can any single tool call outlast your platform's maximum response duration? If yes, either move that work behind the tasks extension and return a handle immediately, or host on something without a hard request ceiling.
  • Do you terminate per-user OAuth yourself rather than delegating to an external authorization server? If yes, you are running stateful credential storage next to the server, and a container with a real datastore is usually simpler than assembling the same thing from serverless parts.

Notice what is not on that list: request volume, number of tools, and whether the server is public. None of those change the deployment shape any more. Before 2026-07-28 they interacted with session affinity, so they did.

What changed in the 2026-07-28 spec that made serverless viable?

Two removals, and they compound. SEP-2567 removed protocol-level sessions and the Mcp-Session-Id header from the Streamable HTTP transport. SEP-2575 removed the initialize and notifications/initialized handshake, so every request now carries its own protocol version and client capabilities in _meta, under the keys io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities.

Together those mean a request no longer depends on anything that happened on a previous request. There is no handshake to have completed and no session to have been pinned to. A cold function instance that has never seen this client can answer a tools/call correctly on the first try.

That is what makes the deployment story boring, which is the goal. You put N identical instances behind a plain round-robin load balancer with no shared Redis, no sticky cookies, and no consistent hashing. Servers that genuinely need cross-call state now mint explicit handles and pass them back as ordinary tool arguments, which makes the state visible in the transcript instead of hidden in the transport.

Does your server need a long-lived connection?

This is the question that actually rules platforms out, so answer it precisely. The 2026-07-28 spec replaced the old HTTP GET endpoint and the resources/subscribe and resources/unsubscribe methods with a single method called subscriptions/listen. It opens one long-lived POST-response stream, and the client opts in to specific notification types: toolsListChanged, promptsListChanged, resourcesListChanged, and resourceSubscriptions.

If you implement subscriptions/listen, that stream stays open for as long as the client wants notifications. A function platform that caps a response at a fixed number of seconds will cut it, and the client will see the subscription die repeatedly. That is the single clearest signal that you want a container or a VM.

The important nuance is what does not need that stream. Request-scoped notifications, including notifications/progress and notifications/message, still flow on the response stream of the request they belong to. They do not use subscriptions/listen. So a server that reports progress during a slow tool call is not a subscribing server, and it does not need a long-lived listen stream. It only needs its own single response to stay open long enough to finish.

Why a dropped stream now costs more than it used to

This one gets missed, and it cuts against the general direction of travel. SEP-2575 removed SSE stream resumability and message redelivery from Streamable HTTP. The Last-Event-ID header and SSE event IDs are gone. If a response stream breaks, the in-flight request is lost, and the client must re-issue it as a new request with a new request ID.

So the spec made servers easier to scale and simultaneously made dropped connections more expensive. There is no resume. Every broken stream is a full retry of whatever work was in progress, and if that tool call had side effects you now care a lot about whether it was idempotent.

Practically: find your slowest tool call, take its p99, and compare it to the hard response ceiling of the platform you are considering. If the p99 is anywhere near the ceiling, you will be paying for silent full retries under load. Either pick a host without that ceiling or stop holding the request open and return a task handle instead.

MCP deployment targets compared

Read this as a decision table, not a ranking. The right-hand columns are the constraints that actually differ between targets once the protocol is stateless.

Target              Sticky   Long-lived   Cold     Best for
                    routing  streams      starts
-------------------------------------------------------------------------
Local stdio         n/a      n/a          none     Filesystem, git, local
                                                   dev tools, secrets that
                                                   must never leave the box

Edge runtime        no       varies by    minimal  Public read-mostly
(Workers-class)                platform            servers, wrapped REST
                                                   APIs, global latency

Serverless function no       usually no   yes      Bursty internal servers,
(Lambda-class)                                     short deterministic
                                                   tool calls

Container / VM      no       yes          none     subscriptions/listen,
(Cloud Run, ECS,                                   long tool calls, self
 Fly, plain VM)                                    terminated OAuth

Behind a gateway    no       inherits     inherits Multi-tenant metering,
(API GW, CF, nginx)            from origin  origin  per-method rate limits

The Sticky routing column reads no on every hosted row, and that is the whole point. Under the previous spec revision it would have read yes on all of them, which is what made MCP awkward to deploy on anything without session affinity.

For a worked example of the edge row, including the fetch handler shape and a one-command deploy, see https://mcporbit.com/blog/deploy-stateless-mcp-server-cloudflare-workers

What can you put in front of an MCP server now?

More than before, and this is a genuine operational upgrade. SEP-2243 made Mcp-Method and Mcp-Name required headers on Streamable HTTP POST requests. The method and the tool name are now readable from the headers alone.

That means a gateway, rate limiter, or WAF can route and meter MCP traffic without parsing a JSON-RPC body. You can rate-limit one expensive tool by name, send tools/list to a cache and tools/call to the origin, or bill per method, all in gateway config. Details on the headers: https://mcporbit.com/blog/mcp-routing-headers-mcp-method-mcp-name

Caching got a sanctioned path too. SEP-2549 requires ttlMs and cacheScope on results from tools/list, prompts/list, resources/list, resources/read, and resources/templates/list. ttlMs is a freshness hint in milliseconds, and cacheScope is either public or private, which controls whether a shared intermediary may cache the response. A public scope on tools/list means a CDN in front of your server is a supported design rather than a hack. See https://mcporbit.com/blog/cache-mcp-tool-list-ttlms-cachescope

When should you not deploy at all?

When the server's whole job is on the user's machine. A server that reads the local filesystem, drives a local git checkout, or holds a developer's own API keys should run as a stdio subprocess launched by the client. There is no endpoint to attack, no credential to store centrally, and no deployment to operate.

Note that the deprecations in 2026-07-28 nudge this way too. Logging was deprecated (SEP-2577), and the suggested migration is to write to stderr on stdio or use OpenTelemetry. Deprecated features keep working for a minimum twelve-month window under the new feature lifecycle policy, so nothing breaks today, but new servers should not adopt them.

The transport choice and the deployment choice are the same decision made twice. If you are unsure which transport you want, start here: https://mcporbit.com/blog/mcp-transport-stdio-vs-streamable-http


Frequently asked questions

Can I run an MCP server on AWS Lambda or Cloud Functions?
Yes, for request/response servers. Since the 2026-07-28 spec removed protocol sessions and the initialize handshake, a cold instance can answer any request correctly. The limits to check are the platform's maximum response duration against your slowest tool call, and whether you need subscriptions/listen, which needs a long-lived connection that most function platforms will cut.
Do I still need sticky sessions or a shared session store for MCP?
No. SEP-2567 removed protocol-level sessions and the Mcp-Session-Id header. Every request carries its own protocol version and capabilities in _meta, so a plain round-robin load balancer is enough. Servers that need cross-call state mint explicit handles and pass them as ordinary tool arguments instead.
How long can a single MCP tool call run?
As long as your host allows the response to stay open, because the spec does not set a ceiling. That is the problem: SEP-2575 removed stream resumability, so if the connection drops the request is lost and the client must re-issue it with a new request ID. For work that can outlast a request, return a handle through the io.modelcontextprotocol/tasks extension rather than holding the stream.
Can I put a CDN in front of an MCP server?
Yes, for list and read results. SEP-2549 requires ttlMs and cacheScope on tools/list, prompts/list, resources/list, resources/read, and resources/templates/list. A cacheScope of public tells shared intermediaries they may cache the response. Do not cache anything marked private, which is how per-user tool lists are flagged.
Is stdio still a supported way to run an MCP server?
Yes. stdio is not deprecated and remains the right choice for servers that operate on the user's own machine, such as filesystem, git, and local development tools. What was deprecated in 2026-07-28 is the old HTTP+SSE transport, which was already deprecated in 2025-03-26 and is now formally in the deprecated state.
Does deploying to the edge change how I do OAuth?
It changes where your tokens live, not the protocol. Delegating to an external authorization server works fine on edge and serverless. Terminating OAuth yourself means storing client credentials and refresh tokens somewhere durable, which is usually simpler on a container next to a real datastore. Note that 2026-07-28 also deprecated Dynamic Client Registration in favor of Client ID Metadata Documents.
  • Make an MCP server stateless (2026-07-28 spec): https://mcporbit.com/blog/make-an-mcp-server-stateless
  • Deploy a stateless MCP server to Cloudflare Workers: https://mcporbit.com/blog/deploy-stateless-mcp-server-cloudflare-workers
  • MCP transport: stdio vs Streamable HTTP: https://mcporbit.com/blog/mcp-transport-stdio-vs-streamable-http
  • MCP routing headers, Mcp-Method and Mcp-Name: https://mcporbit.com/blog/mcp-routing-headers-mcp-method-mcp-name
  • Cache tool lists with ttlMs and cacheScope: https://mcporbit.com/blog/cache-mcp-tool-list-ttlms-cachescope
  • Do you need to migrate your MCP server for July 28: https://mcporbit.com/blog/do-you-need-to-migrate-mcp-server-july-28
  • Handle long-running tasks in an MCP server: https://mcporbit.com/blog/mcp-server-long-running-tasks

About the author

Mark

Head of Marketing, MCPOrbit

Mark leads marketing at MCPOrbit, the free desktop client for the Model Context Protocol. He writes the build-it and reliability guides, and the code in them is run before it ships.

Share this post

MCPOrbit

Test an MCP server in 60 seconds.

Download MCPOrbit for free — no account, no telemetry. Hear about a server and test it before the curiosity wears off.

macOS 14+ · Apple Silicon & Intel · No account needed