Contents
Figure 1: Best Platforms to Host and Deploy MCP Servers — a normal web deployment with three extra opinions
Your MCP server works on your laptop. Now you want a URL. Which platform should host it?
There is no single winner, and I have not deployed to any cloud. I read each platform's own docs and tested a demo server locally. This guide covers what those docs say and where they say nothing.
What a Hosted MCP Server Needs
A remote MCP server needs five things:
- Streamable HTTP. Stdio servers run as a subprocess on your machine, so a hosted server has to speak HTTP.
- Authentication. A public URL that runs tools needs a lock.
- Origin and Host checks. The spec says servers must return 403 on a bad
Origin. - A stateless design. More below. It is the biggest decision you will make.
- A health route. Platforms probe your server before sending traffic.
What I Tested
My demo is a small Python server wrapped with a bearer-token check, DNS-rebinding protection and an open /healthz route. I started it as several replicas and probed each one. The results:
| Probe | Result |
|---|---|
| No token | /mcp returned 401 with a WWW-Authenticate: Bearer header. Health stayed open at 200. |
Bad Origin |
403 |
Bad Host |
421 |
| 5 MB body | The server dropped the connection. It refused the request but did not give a clean error. |
| Stateless mode, two replicas | Alternating requests all returned 200, and each reply named the replica that answered. |
| Stateful mode | A session created on replica C worked there. The same session ID sent to replica D returned 404 "Session not found." |
A load balancer will send your next request to a different replica, so stateless servers scale and stateful ones need sticky routing. I only ran two local processes, so this shows the behaviour and says nothing about any platform's speed.
Cloudflare Workers
Cloudflare's guide deploys over Streamable HTTP. For new servers, it says to use createMcpHandler, which is stateless [2]. The quick-deploy templates use the older McpAgent path, and the page says "Do not use that path for a new server" [2]. That is a sarcasm-worthy trap: the fastest template is the one the docs tell you not to use.
- Auth: two options — none, or sign-in through Cloudflare Access or any OAuth 2.0 provider. The GitHub example needs three secrets and a KV namespace
[2]. - Deploy:
npx wrangler@latest deploy[2]. - Missing: the page does not mention pricing or limits.
- Gotcha: opening
/mcpin a browser will not work, and the page warns you about it[2].
Vercel
Vercel's route is mcp-handler, which adds an MCP endpoint to a Next.js App Router app (Node.js 20+). Clients connect over Streamable HTTP at /api/mcp [3]. Version 2 of the package removed the legacy HTTP+SSE transport [3].
The auth section is the most honest of the group. The package verifies bearer tokens and publishes OAuth protected-resource metadata, but "the package does not issue tokens or run an authorization server" [3]. You bring the authorization server. Invalid tokens get 401, and tokens missing a required scope get 403 [3].
The page also lists Fluid compute, Instant Rollback, a firewall and preview protection [3]. It gives no limits or prices in the text I read.
Google Cloud Run
Cloud Run's page is blunt: it supports "streamable HTTP transport, but not MCP servers with stdio transport" [1].
Deploy from an image (gcloud run deploy --image IMAGE_URL --port PORT) or from source (--source .) [1].
Its auth story fits teams already on Google Cloud. Options include IAM invoker permission, OIDC ID tokens and IAP, plus gcloud run services proxy for local clients [1]. The page does not cover timeouts, scaling or minimum instances. Check those yourself before you rely on long-running tools.
AWS Bedrock AgentCore Runtime
AgentCore expects your container at 0.0.0.0:8000/mcp [4]. It supports both modes: the docs recommend stateless (stateless_http=True) for basic servers and offer stateful for elicitation, sampling and progress notifications [4]. The platform adds an Mcp-Session-Id header if a request lacks one [4].
- Auth: OAuth via a custom JWT authorizer. Missing auth returns 401 with a
WWW-Authenticateheader[4]. - Setup: a Cognito user pool or Auth0, and Auth0 needs an
audienceparameter or it returns opaque tokens[4]. - Deploy:
agentcore deploy[4]. - Missing: the page states no quotas, payload limits or timeouts.
Fly.io and Railway: Read the Right Page
Both platforms changed their docs recently, and my first two URLs pointed at the wrong pages.
- Fly.io: the blueprint page my notes came from now 404s —
fly.io/docs/blueprints/deploy-mcp-server/redirects todocs.fly.io/blueprints/deploy-mcp-server, which is gone. The current material is a Model Context Protocol section atdocs.fly.io/model-context-protocol:fly mcp launchfornpx,uv,go runor Docker stdio servers, then four choices covering transport, deploy method, deploy target and access control[5]. Its hub page notes that because MCP is asynchronous and stateful, their examples run on a single machine, so clients reach the same server that got the request[5]. - Railway: two different pages.
docs.railway.com/ai/mcp-serverdocuments Railway's own server for managing Railway infrastructure.docs.railway.com/guides/mcp-serveris the one about hosting yours: a TypeScript server on Express, one/mcpendpoint, a fresh transport per request, a public domain, bearer-token middleware, private networking as an alternative, and Postgres when you need state[5].
I am still not ruling either platform out, and I still would not pick from these docs alone. Fly.io's hub page gives no commands of its own and defers to sub-pages I did not read in full; Railway's guide is complete enough to follow but publishes no quotas or timeouts.
Quick Comparison
| Platform | Transport (per docs) | Auth (per docs) | Stateless needed? |
|---|---|---|---|
| Cloudflare Workers | Streamable HTTP | None, Access or any OAuth 2.0 | New server path is stateless [2] |
| Vercel | Streamable HTTP | Bring your own OAuth server | Not stated |
| Cloud Run | Streamable HTTP (stdio unsupported) | IAM, OIDC, IAP | Not stated |
| AgentCore | Streamable HTTP | OAuth (custom JWT) | Both supported [4] |
| Fly.io | Streamable HTTP per its MCP section | Access-control page, not read in full | Docs run examples on one machine [5] |
| Railway | Streamable HTTP | Bearer middleware, OAuth helpers | Fresh transport per request [5] |
Which One Should You Pick?
- Already on Google Cloud: Cloud Run, with IAM so you do not write auth code.
- Already on AWS: AgentCore. It is the only one here that documents stateful mode.
- Already on Next.js: Vercel, and plan to bring your own authorization server.
- Want the smallest deploy: Cloudflare, using
createMcpHandler. - Already on Railway: its guide is the shortest path from a container to a
/mcpURL. - Own a container and a preference: test it yourself. The docs I read will not decide it for you.
A Pre-Launch Checklist
- Serve Streamable HTTP at one path, and make it stateless unless you need elicitation or sampling.
- Put a token check in front of
/mcp, and keep secrets in environment variables. - Turn on Origin and Host validation. Expect 403 and 421 in your tests.
- Expose an open health route.
- Set a request-size limit, and test the failure. Mine dropped the connection instead of answering.
- Run two replicas before you call it done.
Common Pitfalls
- Starting from Cloudflare's quick-deploy template. The docs say it is the wrong path for a new server
[2]. - Assuming the platform runs OAuth for you. Vercel validates tokens; it does not issue them
[3]. - Testing stateful mode on one replica. The failure only appears on the second one.
- Skipping Host validation. Origin checks alone will not catch a proxy rewriting the host.
- Opening
/mcpin a browser and concluding the server is broken[2]. - Deploying without a health route, then wondering why the platform marks it unhealthy.
- Leaving body limits at the default and never testing an oversized request.
- Reading a product page instead of the hosting guide. Railway has both
[5]. - Assuming stdio works remotely. Cloud Run says outright that it does not
[1].
Best Book to Read
| Cover | Book | Description | Get it |
|---|---|---|---|
![]() |
OAuth 2 in Action | the authorization half this article keeps deferring to platforms: what a bearer token actually proves, why scopes belong on the resource server, and how to reason about a public endpoint that runs tools. | View on Amazon |
As an Amazon Associate I earn from qualifying purchases. Links on this page are affiliate links, and I may earn a commission at no extra cost to you.
Wrapping Up
Hosting an MCP server is a normal web deployment with three extra opinions: HTTP transport, stateless design and real authentication. Most platforms here handle the first; the other two are on you.
Build the stateless version first, prove it on two local replicas, then pick the platform that already holds the rest of your stack.
If you are still writing the server, MCP server development in Python covers the build, how MCP transports work covers the stdio-versus-HTTP split, and securing an MCP server covers the permissions layer every platform above leaves to you. For what to run once it is live, see the reviewed MCP servers.
Frequently Asked Questions
Which platform is best for hosting an MCP server?
Pick the one that already holds the rest of your stack. Cloud Run fits teams on Google Cloud because IAM invoker permission means you do not write auth code. AgentCore fits AWS teams and is the only platform in this comparison that documents a stateful mode for elicitation, sampling and progress notifications. Vercel fits Next.js apps but requires you to bring an authorization server. Cloudflare has the smallest deploy if you use createMcpHandler rather than the older McpAgent templates.
Does a hosted MCP server need to be stateless?
It should be unless you need elicitation, sampling or progress notifications. A stateful server binds a session to the replica that created it, so a load balancer that sends the next request elsewhere returns a session-not-found error. In a local test, a session created on one replica worked there and returned 404 on a second replica. Stateless requests let any replica answer any request, which is why the 2026-07-28 spec revision formalised statelessness and retired the initialize handshake and session header.
Why does my MCP server return 421 from a hosted platform?
Because Host validation rejected the request. The same DNS-rebinding protection that makes you return 403 for a bad Origin also checks the Host header, and a reverse proxy or platform hostname that does not match your allowlist produces 421. In a local probe, a deliberately wrong Host header returned 421 while a wrong Origin returned 403. Check the allowlist against the public hostname the platform actually serves before you blame the transport.
Do I need OAuth for a public MCP server?
You need authentication, and OAuth is one way to get it. Vercel's mcp-handler validates bearer tokens and publishes protected-resource metadata but explicitly does not issue tokens or run an authorization server, so you bring the provider. Cloud Run can lean on IAM invoker permission and OIDC tokens instead of writing an OAuth flow. AgentCore documents OAuth through a custom JWT authorizer. For internal use, a bearer check in front of the endpoint is the minimum, not the ideal.
Which serverless platform supports stdio MCP servers?
None of them, and that is expected. Stdio servers run as a subprocess on the client machine, so a hosted deployment has to speak Streamable HTTP. Cloud Run states this bluntly: it supports streamable HTTP transport but not MCP servers with stdio transport. If you want a hosted server, serve HTTP at a single path such as /mcp. Keep the stdio version for local clients and treat the two as separate deployments.
What should I test before deploying an MCP server?
Six things: the endpoint serves Streamable HTTP at one path, a missing token returns 401 with a challenge header, bad Origin and Host headers are rejected, an open health route answers, an oversized body fails cleanly, and two replicas both answer requests. That last test is the one that catches statefulness. Run the replicas locally before you pay for a platform, because the failure mode is identical and much cheaper to find at home.
Sources
[1]Google Cloud, "Host MCP servers on Cloud Run" — the streamable-HTTP-only statement, image and source deploys, IAM invoker permission, OIDC ID tokens, IAP andgcloud run services proxy: cloud.google.com/run/docs[2]Cloudflare, "Remote MCP server" guide —createMcpHandleras the stateless path for new servers, the warning against the olderMcpAgenttemplates, Access or OAuth 2.0 auth, the three-secret GitHub example with a KV namespace,npx wrangler@latest deployand the/mcp-in-a-browser warning: developers.cloudflare.com[3]Vercel, "Deploy MCP servers to Vercel" —mcp-handleron the Next.js App Router,/api/mcp, version 2 dropping HTTP+SSE, the "does not issue tokens or run an authorization server" statement, 401 versus 403, Fluid compute, Instant Rollback and preview protection: vercel.com/docs/mcp[4]AWS, "Deploy MCP servers in AgentCore Runtime" — the0.0.0.0:8000/mcpcontract,stateless_http=True, stateful mode for elicitation, sampling and progress notifications, theMcp-Session-Idbehaviour, the custom JWT authorizer, Cognito or Auth0 with itsaudiencerequirement, andagentcore deploy: docs.aws.amazon.com[5]Fly.io and Railway — Fly.io's Model Context Protocol section (fly mcp launch, transport, deploy-with, deploy-on, access control, and the single-machine note for stateful examples), whose older blueprint URL now 404s; and Railway's "Build and Deploy Your Own MCP Server" guide (Express, one/mcpendpoint, fresh transport per request, bearer middleware, private networking, Postgres for state), alongside Railway's separate infrastructure-server page: docs.fly.io/model-context-protocol, docs.railway.com/guides/mcp-server, docs.railway.com/ai/mcp-server
