Skillforge Field notes on shipping with AI tools

MCP Server Hosting: 4 Options Compared and Priced

mcpapis

We earn commissions when you shop through the links below, at no extra cost to you. We only link products we would use ourselves.

Short answer: for a normal tool server, hosting is now a boring decision, and that is new. The current MCP revision (2026-07-28) removed protocol sessions, removed the initialize handshake, and removed the GET stream endpoint. A conforming remote server is one POST route that can answer with a JSON object. That runs anywhere.

We host ours on Cloudflare Workers at $5/month, on the same Worker that already does our browser rendering, so the MCP surface costs us nothing extra. If you would rather have a long-running container, a Fly.io machine starts at $3.32/month for 512MB and DigitalOcean App Platform at $5/month. Vercel works too, and its function duration cap is the one number that can actually bite you.

Below: what the spec requires of a host as of today, the four options with prices we read off the vendors' own pricing pages this morning, and a conformance check that caught a bug in our own server while we were writing this.

What changed on 2026-07-28 (and why it lowers the hosting bar)

Most hosting advice for MCP servers still tells you to provision a session store: Redis, a Durable Object, something to keep per-connection state alive between requests. That advice is describing features that no longer exist.

From the 2026-07-28 changelog, the major changes include:

  1. "Remove protocol-level sessions and the Mcp-Session-Id header from the Streamable HTTP transport."
  2. "Make MCP stateless: remove the initialize/notifications/initialized handshake. Every request now carries its protocol version and client capabilities in _meta."
  3. "Add server/discover: servers MUST implement this RPC to advertise their supported protocol versions, capabilities, and identity."
  4. "Replace the HTTP GET endpoint and resources/subscribe/ resources/unsubscribe with subscriptions/listen."

And the transport page is now blunt about the shape: "The server MUST provide a single HTTP endpoint path (hereafter referred to as the MCP endpoint) that supports POST." Per-request, the server answers with either Content-Type: application/json or an SSE stream scoped to that one request. Stream resumability via Last-Event-ID is gone. Servers that need state across calls are told to mint explicit handles and pass them as ordinary tool arguments, which is application state in your database, not transport state in your host.

The versioning page confirms 2026-07-28 is the current revision, not a draft.

Two consequences for hosting:

  • You do not need sticky sessions, a session store, or long-lived connections for a plain tool server. A stateless function is a complete answer.
  • You do need a place where one POST can stay open for a while if you stream progress notifications from a slow tool, or if you support subscriptions/listen, whose response stream is designed to stay open indefinitely. That is where hosts differ, and it is a duration question, not a state question.

The vendors have already moved. Cloudflare's remote MCP guide now says "Use createMcpHandler for a new stateless server" and warns that its own quick-deploy templates "still use the deprecated McpAgent path. Do not use that path for a new server." Vercel's mcp-handler 2.0 serves 2026-07-28 natively with, in its own words, "both protocols with no Redis dependency or session storage."

The four hosts, priced

Prices read from each vendor's pricing page on 2026-08-26. Nothing here is modeled or estimated; where a number depends on region or plan, that is stated.

Cloudflare Workers: cheapest floor, no wall-clock ceiling

  • Free: 100,000 requests per day, 10 ms of CPU time per invocation.
  • Paid: $5/month per account, 10 million requests included then $0.30 per additional million, 30 million CPU milliseconds included then $0.02 per additional million.
  • CPU per invocation: up to 5 minutes on Paid (default 30 seconds).
  • Wall clock: "No limit" on both plans while the client stays connected.
  • Memory: 128MB per isolate. Subrequests: 50 per request on Free, 10,000 on Paid. Script size: 3MB Free, 10MB Paid after compression.

The 10 ms CPU limit on Free is the catch, and it is less of a catch than it looks for MCP: CPU time excludes time spent waiting on I/O, so a tool that mostly awaits an upstream API can fit. Anything that parses a large payload or does real work per call will not.

The pairing that matters for streaming: Workers bill CPU, not wall time, and impose no wall-clock limit on an HTTP request while the client is connected. A subscriptions/listen stream that sits idle for twenty minutes costs almost nothing. No other option here has both properties.

If you do want per-connection state, Durable Objects are billed separately: 1 million requests per month then $0.15 per million, 400,000 GB-s then $12.50 per million GB-s, with SQLite storage at 5 GB-month included then $0.20 per GB-month (that storage billing began January 2026). Under the current spec you probably do not need them, and Cloudflare's own guidance now points away from the Durable-Object path for new servers.

Vercel Functions: fine, but read the duration table

  • Max duration: Hobby 300s default and maximum. Pro and Enterprise 300s default, 800s maximum, 1800s extended maximum (beta, requires function-level configuration and specific runtimes).
  • Memory: Hobby 2GB / 1 vCPU. Pro and Enterprise up to 4GB / 2 vCPU.
  • Edge runtime: must begin sending a response within 25 seconds to keep streaming, then can stream for up to 300 seconds.
  • Body size: 4.5MB maximum for request or response, else HTTP 413.
  • Billing: active CPU time plus provisioned memory time, where waiting on I/O does not count toward active CPU.

Vercel's documented path is the mcp-handler package, with withMcpAuth for OAuth. The ergonomics are the best of the four if you are already a Next.js shop. Note that the deploy guide (last updated 2026-03-19 at the time of writing) still shows the 2025-era shape, exporting one handler for GET, POST, and DELETE at /api/mcp, while mcp-handler 2.0 requires Node 20+, Zod 4, and @modelcontextprotocol/server in place of @modelcontextprotocol/sdk, and returns 410 Gone on the removed /sse and /message routes. Read the changelog alongside the guide.

The ceiling is real though. A subscriptions/listen stream on Hobby dies at 300 seconds, and your client has to re-open it. That is survivable for change notifications and fatal for anything you want to treat as a persistent channel.

Fly.io: a real process, cheapest always-on

  • shared-cpu-1x with 256MB: $2.02/month. 512MB: $3.32. 1GB: $5.92. Those are Amsterdam-region figures and the page notes pricing varies by region, with roughly $5 per 30 days per additional GB of RAM.
  • Egress: $0.02/GB to the internet in North America and Europe, $0.04 in Asia Pacific, Oceania and South America, $0.12 in Africa and India. All inbound transfer is free.
  • Stopped machines: "Each 1GB of rootfs for a Machine stopped for 30 days is $0.15."

You get an ordinary long-lived process, so open streams, in-memory caches, and any Node or Python library you like all just work. If your tools need Chromium, a native binary, or a filesystem, this is the shortlist. We made the same call for a different workload in our render API versus a self-hosted droplet writeup, and the tradeoff has not changed: a machine is more capable and more yours to babysit.

DigitalOcean App Platform: containers, transfer included

  • Free tier: $0 for 3 apps with static sites, 1 GiB data transfer per app.
  • Containers: shared 1 vCPU / 512 MiB at $5/month with 50 GiB transfer, 1 vCPU / 1 GiB at $10/month with 100 GiB, 2 vCPU / 4 GiB at $50/month with 250 GiB.
  • The old Basic and Professional tier bundles are gone; you pick instance sizes directly.

The included transfer is the quiet advantage: 50 GiB at the $5 tier, where Fly.io bills egress per GB on top of the machine. If your MCP tools return images, PDFs, or large extracts, price the egress before the compute.

Which one, for which server

  • Plain tool server, calls out to APIs, JSON in and JSON out: Workers Free until 10 ms of CPU stops being enough, then Workers Paid at $5.
  • You want subscriptions/listen to stay open for hours: Workers Paid (no wall-clock limit, CPU-time billing) or a Fly machine. Not Vercel Hobby.
  • Tools need a real filesystem, a native binary, or a headless browser: Fly.io or App Platform.
  • Heavy byte output: App Platform for the included transfer, or Workers if the bytes come from a Cloudflare service anyway.
  • Already on Next.js and want one repo: Vercel, with maxDuration set deliberately rather than left at the default.

Our own server, measured

Ours is one file of about 200 lines on Workers, sharing the Worker (and the $5/month account) that serves the render API. The protocol layer holds no state at all; KV is touched only to check an API key and a credit balance. Probed from a US client this morning, one sample each, so read these as round trips and not as a benchmark:

POST initialize      200  application/json  144ms
POST tools/list      200  application/json   34ms
POST resources/list  200  application/json   31ms
POST bogus/method    200  application/json   14ms   (JSON-RPC -32601)
GET  /mcp            404  application/json   81ms

Two honest defects in that output, both ours:

  1. The GET should be a 405, and was a 404. Under the current revision, a modern-only server "SHOULD" answer 405 Method Not Allowed to a GET or DELETE on the MCP endpoint, so that older clients can tell a modern server from a legacy HTTP+SSE one. Ours returned a generic 404 from the router's fallthrough. We found it while writing this article, by running the check below against our own endpoint, and fixed it the same day: GET and DELETE now answer 405 with Allow: POST and a JSON-RPC error body. The trace above is the pre-fix measurement, left as measured.
  2. It advertises 2025-06-18. That is an initialize-era revision, two behind current. It works with clients today, and it means we owe ourselves the server/discover RPC, the _meta per-request version fields, and header validation for Mcp-Method and Mcp-Name.

We would rather publish the gap than a rounded-up version of it. The point of the check is that it is thirty seconds of work and it finds things.

The conformance check worth running on your own endpoint

Under the current revision, every POST must carry MCP-Protocol-Version and Mcp-Method, plus Mcp-Name for tools/call, resources/read, and prompts/get. A server that processes the body "MUST" reject a header that disagrees with the body, with HTTP 400 and JSON-RPC error -32020 (HeaderMismatch).

# 1. tools/list with the headers the current revision requires
curl -sD- https://your-server.example/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: tools/list' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}'

# 2. GET should be 405 on a modern-only server, never a bare 404
curl -sI -X GET https://your-server.example/mcp

# 3. Header/body mismatch should be 400 with code -32020
curl -s https://your-server.example/mcp \
  -H 'content-type: application/json' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: tools/list' \
  -d '{"jsonrpc":"2.0","id":1,"method":"prompts/list","params":{}}'

Three more spec details that decide whether a host is a fit, all of which are easy to miss:

  • When you open an SSE response stream, servers "SHOULD" send X-Accel-Buffering: no so proxies stop buffering. If your host puts an nginx in front of you and you cannot set that header, streaming will feel broken in a way that looks like your code.
  • On a long-lived subscriptions/listen stream, emit an SSE comment line (:\r\n) as a keep-alive so intermediaries and idle timeouts do not close it. Any host with an idle timeout shorter than your quiet periods needs this.
  • An unimplemented method must come back as HTTP 404 with JSON-RPC -32601. Ours returns the right JSON-RPC error inside an HTTP 200, which is another item on our own list.

If you are figuring out how to get paid for the thing once it is hosted, that is a separate layer underneath the protocol, and we wrote up both rails we tested in how to charge for an MCP server.

FAQ

Do I still need Redis or Durable Objects to host an MCP server? Not for the transport. The 2026-07-28 revision removed protocol sessions and the Mcp-Session-Id header, so there is no per-connection state the host has to keep between POSTs. You still need a database for your own application state, and the spec's suggested pattern is to mint explicit handles and take them back as ordinary tool arguments.

Can I run a remote MCP server on a free tier? On Workers Free, yes, within 100,000 requests per day and 10 ms of CPU per invocation. I/O wait does not count against that CPU budget, so an API-forwarding tool can fit while a parsing-heavy one will not. Vercel's Hobby plan also serves functions free within its limits. DigitalOcean's free App Platform tier covers static sites only, not a container, so an MCP server there starts at $5/month.

What is the maximum time a single MCP tool call can take? It is a hosting limit, not a protocol limit. Workers: no wall-clock limit while the client is connected, with CPU capped at 5 minutes per invocation on Paid (default 30 seconds). Vercel: 300s on Hobby, 800s on Pro, 1800s in the extended beta. Fly.io and App Platform run ordinary processes with no platform cap.

Is SSE dead in MCP? No, its role narrowed. The old standalone GET stream is gone, but a server may still answer any single POST with an SSE stream scoped to that request, and subscriptions/listen is explicitly a long-lived SSE response stream. What went away is resumability: Last-Event-ID and event IDs are removed, so a broken stream loses the in-flight request and the client must re-issue it with a new request ID.

Do I have to support the old revisions? Only if your clients do. A modern-only server should answer 405 to GET and DELETE, ignore any Mcp-Session-Id it receives without minting or echoing one, and ignore Last-Event-ID. Clients probing a legacy server look at whether a 400, 404, or 405 came back with a recognizable modern JSON-RPC error body, so returning a clean error body is part of interoperating.

Where should I put the endpoint? /mcp is the convention in Cloudflare's docs and ours; Vercel's example uses /api/mcp. Any single path works as long as it accepts POST, since the spec only requires one endpoint.

Verified against the MCP specification revision 2026-07-28, and against Cloudflare, Vercel, Fly.io, and DigitalOcean pricing and limits pages, on 2026-08-26.