A stateless remote MCP server runs as one Cloudflare Pages Function at functions/mcp.ts
DrFritzi · Reviewed · Updated 28 Sept 2026 · Markdown
Answer
Create functions/mcp.ts in a Cloudflare Pages project and export onRequest. Cloudflare maps the file to the route /mcp. Answer every POST with exactly one JSON-RPC message in and one application/json response out, return 202 for notifications, and return 405 for other methods. Read static files with env.ASSETS.fetch(). Run npx wrangler pages dev out to test. This page describes what the wiki at wiki.drfritzi.at runs as of 2026-09-28.
Details
File layout
Cloudflare's docs say the /functions directory structure determines the routes, so functions/mcp.ts serves /mcp. If no Function matches, the request falls back to a static asset. onRequest runs unless a more specific onRequestPost-style export exists. Here one onRequest handles every method, which keeps the 405 logic in one place.
The handler
export async function onRequest({ request, env }) {
if (request.method === 'OPTIONS') return new Response(null, { status: 204, headers: CORS });
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405, headers: { Allow: 'POST, OPTIONS', ...CORS } });
}
const msg = await request.json(); // one JSON-RPC message per POST
if (msg.id === undefined) return new Response(null, { status: 202, headers: CORS }); // notification
return json({ jsonrpc: '2.0', id: msg.id, result: await handle(env, msg) });
}
The full file also checks jsonrpc, answers parse errors with -32700 and unknown methods with -32601. No session ID is issued, so a request can land on any isolate.
Reading the static site
env.ASSETS is the default binding to Pages' asset server. This wiki's search_answers and get_answer tools fetch /search-index.json and /a/<slug>.md from the build:
const res = await env.ASSETS.fetch(new Request(new URL('/a/what-is-mcp.md', origin)));
Cloudflare's reference recommends pretty paths such as /users/. A direct .md path works here, and the smoke test checks it. Validate slugs against ^[a-z0-9]+(?:-[a-z0-9]+)*$ before building a path.
Two protocol eras in one endpoint
- Legacy (2025-11-25 and earlier):
initialize,ping,tools/list,tools/call, with no session. - 2026-07-28: a request is modern when
params._metacarriesio.modelcontextprotocol/protocolVersionor theMCP-Protocol-Versionheader names a modern version. The handler then requiresMCP-Protocol-VersionandMcp-Method(plusMcp-Namefortools/call) to match the body, else400with-32020. It answersserver/discover, putsresultType: "complete"on each result, and returns404with-32601for unknown methods.
Test locally
npm run build
npx wrangler pages dev out --port 8788
curl -sS -X POST http://127.0.0.1:8788/mcp \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}'
Cloudflare documents npx wrangler pages dev <DIRECTORY> and port 8788 as the default. The repo's scripts/check-mcp.sh starts wrangler, calls both eras and the error cases, and runs in CI.
Limits to know
Pages Functions requests count toward the Workers quota. On the Free plan that is 100,000 requests per day, shared with Workers, and static asset requests are free and unlimited. In the dashboard, Settings > Runtime > Fail open / closed decides what happens when the free allowance runs out. Fail open keeps serving static assets. Fail closed returns an error page. In both cases the Function no longer runs, so /mcp stops answering until the daily limit resets at midnight UTC.
When to use something else
Stateless is the shape 2026-07-28 allows. If you need per-session state, Cloudflare's Agents docs describe McpAgent, a stateful server backed by a Durable Object. As of 2026-09-28 those docs mark it deprecated and feature-frozen and recommend createMcpHandler for new stateless servers. Check the current docs before choosing.
See also
Sources
- Cloudflare Pages Functions — Routing
- Cloudflare Pages Functions — API reference
- Cloudflare Pages Functions — Advanced mode
- Cloudflare Pages Functions — Pricing
- Cloudflare Pages Functions — Local development
- Cloudflare Agents — Build a remote MCP server
- Cloudflare Agents — McpAgent API
- MCP Specification 2026-07-28 — Streamable HTTP