all posts

The best MCP server hosting platforms in 2026

Ajay Kumar··10 min read

The Model Context Protocol went from a specification with a handful of reference servers to the default way tools get attached to models, and the centre of gravity moved with it. The early servers all ran over stdio: your client spawned the server as a subprocess on the same machine, talked to it over pipes, and hosting never entered the conversation. The interesting servers now run somewhere else, over HTTP, serving many users at once — and that is a hosting problem with some genuinely unusual properties.

This guide is about those properties first and the platforms second, because the platforms are easy to compare once you know what you are comparing them on. If you have deployed a stateless JSON API before, at least two of the three things below will surprise you.

What a remote MCP server actually needs

Start with the transport. The current remote transport is Streamable HTTP: one endpoint, usually something like /mcp, where the client POSTs JSON-RPC messages and the server may answer either with a plain JSON body or by upgrading that response into a Server-Sent Events stream. The server can also push messages down a separate long-lived GET stream. The older HTTP+SSE transport, with its split endpoint pair, is deprecated but still deployed widely enough that many servers speak both.

Both shapes mean the same thing for hosting: you need an origin that can hold a response open for minutes and forward bytes as they are produced. Any layer in the path that buffers a response body until it is complete converts your stream into a very slow single reply. This is the single most common way a working MCP server appears broken in production.

# Does your host stream, or does it buffer? Ask before you debug for a day.
curl -N -i \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
  https://your-server.example.com/mcp

# Streaming host:  headers arrive, then "event:" lines trickle in.
# Buffering proxy: nothing at all until the handler finishes, then
#                  the entire body lands at once.

Second, sessions. The protocol has an initialize handshake, and a server that wants continuity across requests issues an Mcp-Session-Id header the client echoes back. If you keep that session in process memory — which is what every quickstart does — then every request in a session must reach the same process. On a single long-lived server that is free. Behind a load balancer that round-robins, it is a bug that appears only under load. On a function platform where each invocation may be a fresh instance, it is a bug that appears immediately, and the fix is either sticky routing or moving session state into Redis or a database.

You can write a fully stateless MCP server, and if you can, do. Return no session id, keep every tool call self-contained, and the hosting question collapses into an ordinary API deployment. The moment a tool needs to remember something between calls — an open database cursor, a working directory, a browser session — you are back to the constraints above.

Third, and most people underrate this: what the tools do. An MCP server that reads from your own read-only database is a normal web service. An MCP server whose tools run code, execute shell commands, clone repositories, or open a browser is a multi-tenant execution platform, and the model on the other end is choosing the arguments. That is not a threat model a normal app host is built for, and it changes which of the options below are actually viable.

Authorization is part of the hosting decision

MCP's authorization story is built on OAuth 2.1: the server behaves as a protected resource, advertises its authorization server through metadata, and rejects unauthenticated requests with a 401 that tells the client where to go. Clients discover the rest and run the flow themselves.

Two consequences for hosting. Your server needs a stable public HTTPS origin, because that origin is baked into the metadata and into whatever the user registered. And the platform's own auth layer must not sit in front of the endpoint — a host that puts a login page or an access-token gate in front of every route will block the discovery request that makes the flow work. Check that you can expose a specific path unauthenticated before you commit.

The platforms

  • Cloudflare Workers — The most opinionated and, for a stateless-ish server, the most convenient: an official agents SDK, Durable Objects to hang session state on, and OAuth helpers. You commit to the Workers runtime, which is Web-standard rather than Node, so libraries that assume a filesystem or raw TCP need alternatives. Excellent if your tools call APIs; awkward if your tools need a machine.
  • Vercel — Good developer experience and a well-trodden MCP adapter path in Next.js apps. Server routes are functions, so read the session section again and check the maximum duration against your slowest tool call. Fine for API-shaped tools, wrong for tools that hold a resource open.
  • Fly.io — An ordinary long-lived process on a real VM, close to your users, with streams and sessions behaving exactly as they do on your laptop. A very sane default for a stateful server.
  • Render — Build command, start command, a persistent web service, managed Postgres alongside. The least surprising way to host a Node or Python MCP server that keeps state in process.
  • Railway — Fastest git-to-URL for an MCP server with a database attached, with per-branch environments that make protocol changes safe to test.
  • AWS Lambda with response streaming — Viable now that streaming responses exist, and attractive if you are already deep in AWS. You are still on a function platform, so sessions belong in DynamoDB or ElastiCache, not in a module-level Map.
  • A VPS behind Caddy or nginx — Cheapest, most control, and the place people most often lose streaming to a default proxy buffer. Turn buffering off explicitly and set a generous read timeout.
  • PandaStack — Git-driven with no Dockerfile: it detects the build, starts your server, injects PORT and HOST, and gives the app a stable HTTPS host for the OAuth metadata. Each app is a Firecracker microVM with its own kernel, so a tool that shells out, writes files, or spawns a browser is contained by a hypervisor boundary rather than a shared kernel. Scale-to-zero is snapshot restore, so a low-traffic server can sleep without paying a container cold start on the next call. Best when your tools execute things; less compelling if your server is a thin proxy over an API you already host elsewhere.

If your tools run code, host them differently

A run_code or run_shell tool is the whole reason many MCP servers exist, and it inverts the security model. The arguments are generated by a model that may be reading untrusted content — a web page, a document, an email — which means prompt injection reaches your tool arguments directly. Assume the input is hostile, because sometimes it is.

The practical answer is to separate the two jobs: a small, boring server that speaks the protocol and holds sessions, and a disposable environment per execution that the server drives over an API. The server never runs the code itself. If the environment is destroyed after the call, the blast radius of a malicious argument is one throwaway machine.

// The MCP server stays boring. Execution happens somewhere disposable.
import { Sandbox } from "@pandastack/sdk";

server.tool(
  "run_python",
  { code: z.string() },
  async ({ code }) => {
    await using sb = await Sandbox.create({
      template: "code-interpreter",
      ttlSeconds: 120,
    });
    const out = await sb.runCode(code, "python");
    return { content: [{ type: "text", text: out.stdout || out.stderr }] };
  },
);

// The sandbox is gone before the tool call returns. Nothing the model
// generated ever touched the process serving the protocol.

Pick by situation

  • Stateless tools that call APIs you already have → Cloudflare Workers or Vercel. Low latency, low operational surface, no session problem to solve.
  • Stateful sessions in process memory → a long-running server: Fly.io, Render, Railway, or PandaStack. Do not fight a function platform for this.
  • Tools that execute code, run shells, or drive browsers → a host with per-workload isolation, and a disposable environment per call regardless of host.
  • Internal server, small team, on your own infrastructure → a VPS behind a proxy, with buffering turned off and a long read timeout.
  • You need OAuth and a stable public origin → anything with a real custom domain and the ability to leave a path unauthenticated. Check that second one early.

The short version

Decide whether your server is stateful before you pick a platform, because that single answer eliminates most of the list. Stateless servers are happy on functions and at the edge. Stateful ones want a process that stays alive, and every workaround for that on a function platform is more work than moving.

Then verify streaming with curl on the real deployed URL — not locally, where there is no proxy — and confirm your platform lets you expose an unauthenticated discovery path. Those two checks take ten minutes and prevent the two failures that are hardest to diagnose after the fact.

Frequently asked questions

Do I need to host an MCP server at all, or can it stay local?

Local stdio servers remain the right answer for anything personal: a server that reads your filesystem, drives a local database, or wraps a CLI you already have installed has nothing to gain from a network hop, and running as a subprocess means no auth, no TLS, and no hosting bill. Remote hosting earns its keep when the server needs to be shared across a team, reached from a client you do not control, kept running when your laptop is closed, or given credentials you would rather not distribute to every user's machine. Many projects end up publishing both — the same tool implementations behind a stdio entrypoint for local use and a Streamable HTTP entrypoint for everyone else — which is worth designing for from the start because retrofitting the transport split later means untangling assumptions about where state lives.

Why does my MCP server work locally but hang when deployed?

Almost always response buffering somewhere between your process and the client. SSE and Streamable HTTP both depend on every hop forwarding bytes as they arrive; one layer that waits for the complete body turns a stream into a single delayed reply, and nothing logs an error because from the proxy's point of view the request succeeded. Locally there is no proxy, which is why it only breaks in production. Test the deployed URL with curl -N and watch whether headers and the first event arrive before the handler finishes. If you run your own nginx, proxy_buffering off and a raised proxy_read_timeout usually fix it. On a managed platform that buffers, there is normally no setting to change and the fix is a different platform. The second most common cause is an idle timeout shorter than your stream, which produces the same symptom on long tool calls only.

Can I run an MCP server on serverless functions?

Yes, with one condition: the server has to be genuinely stateless, or its state has to live outside the process. Functions are fine at answering tools/list and executing a self-contained tool call. They are bad at anything that assumes the next request lands on the same instance, because it often will not — an in-memory session map, a cached connection, a partially completed operation. If you keep sessions, move them to Redis or a database and treat every invocation as cold. Also check two limits before committing: maximum invocation duration against your slowest tool, and whether the platform supports streaming responses, since a function platform without streaming cannot implement the transport properly at all.

How do I secure an MCP server whose tools execute code?

Treat every tool argument as attacker-controlled, because a model reading untrusted content can be steered into generating whatever an attacker wrote. Authentication is necessary but not sufficient — it tells you which legitimate user is connected, not whether the arguments they caused to be generated are safe. The structural fix is to stop executing anything in the process that speaks the protocol. Have the server call out to a disposable environment per execution, with its own kernel, no credentials it does not need, egress restricted to what the tool genuinely requires, and a lifetime measured in seconds. Then a malicious argument gets a fresh machine that is destroyed shortly afterwards, instead of a foothold on the host holding every user's session.

What is the difference between SSE and Streamable HTTP for MCP?

They are two generations of the same idea. The original HTTP+SSE transport used two endpoints: a GET that opened a long-lived event stream for server-to-client messages, and a separate POST endpoint the client used for its own messages. Streamable HTTP collapses that into a single endpoint where the client POSTs and the server chooses per request whether to answer with a plain JSON body or upgrade the response to an event stream, with an optional standalone GET stream for unsolicited server messages. Streamable HTTP is the current transport and the one to implement for anything new; HTTP+SSE is deprecated but still common enough that libraries often support both. For hosting the distinction barely matters, because both need the same thing from your platform: long-lived, unbuffered responses.

Keep reading

Run code in a microVM in one API call.

49ms p50 cold start. Fork, snapshot, and scale to zero.

Start free
Written by Ajay Kumar, Founder, PandaStack.