Deploy a stateless MCP server on Cloudflare Workers' free tier
Umesh Malik walks through deploying a read-only MCP server on Cloudflare Workers using a `wrangler.toml`, a single `wrangler deploy` command, and the critical `run_worker_first = true` flag to intercept `/mcp` routes before the static-assets binding.
Score breakdown
The post identifies `run_worker_first = true` as the single configuration detail that prevents a silently broken `/mcp` endpoint when co-hosting an MCP server alongside static assets on Cloudflare Workers.
- 01A read-only MCP server is stateless — every `tools/call` is self-contained with no session store or connection pool — making it a natural fit for edge runtimes.
- 02`run_worker_first = true` in `wrangler.toml` is the critical setting that lets the Worker intercept `/mcp` before the static-assets binding can short-circuit it.
- 03Without `run_worker_first = true`, requests to `/mcp` can silently return a 404 or an HTML page instead of a JSON-RPC response.
Umesh Malik's post covers the deployment half of building a production MCP server, picking up after the server logic is written and getting it running on Cloudflare Workers' free tier on a custom domain. The core argument is that a read-only MCP server — one whose tools only fetch data — is inherently stateless: every `tools/call` is self-contained, there is no session store to persist, no database connection pool to warm up, and scaling is automatically parallel. That profile matches exactly what edge runtimes do best, making Workers the straightforward choice rather than a creative one. The free tier covers 100,000 requests per day across 300+ global locations and scales to zero when idle.
Without that flag, Cloudflare's default behavior checks for a matching static file first and only falls through to the Worker if none exists — silently breaking any API route like `/mcp`.
The practical deployment consists of a `wrangler.toml` that sets `main` to the Worker entry point, optionally binds a `./build` directory as static assets via an `[assets]` block, and critically sets `run_worker_first = true`. Without that flag, Cloudflare's default behavior checks for a matching static file first and only falls through to the Worker if none exists — silently breaking any API route like `/mcp`. With the flag set, the Worker runs first, handles `/mcp` and `/.well-known/mcp/server-card.json` explicitly, and falls back to `env.ASSETS.fetch(request)` for everything else. Malik also notes that `handleMcp` receives `env.ASSETS` so tools can read files the static site already publishes (such as a JSON feed), keeping a single source of truth. Local testing uses `npx wrangler dev` on `http://localhost:8787`, exercised with a plain `curl` POST. A key gotcha: Wrangler v4+ requires Node.js 22 or newer, which is the most common reason a deploy succeeds in CI but fails locally.
Key facts
- 01A read-only MCP server is stateless — every `tools/call` is self-contained with no session store or connection pool — making it a natural fit for edge runtimes.
- 02`run_worker_first = true` in `wrangler.toml` is the critical setting that lets the Worker intercept `/mcp` before the static-assets binding can short-circuit it.
- 03Without `run_worker_first = true`, requests to `/mcp` can silently return a 404 or an HTML page instead of a JSON-RPC response.
- 04The Cloudflare Workers free tier supports 100,000 requests per day across 300+ global edge locations.
- 05Wrangler v4+ requires Node.js 22 or newer; mismatched Node versions are the most common cause of local deploy failures.
- 06Local testing uses `npx wrangler dev` on `http://localhost:8787`, testable with a plain `curl` POST.
- 07The `[assets]` block in `wrangler.toml` is optional — standalone MCP servers can omit it entirely.
Topics
Summary and scoring are generated automatically from the original article. We always link back to the publisher and never republish images or paywalled content. Last processed Jun 13, 2026 · 08:58 UTC. How this works →