# A stateless remote MCP server runs as one Cloudflare Pages Function at functions/mcp.ts

> Put an onRequest handler in functions/mcp.ts, answer each POST with one JSON-RPC message and one JSON response, read your static build through env.ASSETS, and test it with wrangler pages dev.

## 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

```ts
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:

```ts
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._meta` carries `io.modelcontextprotocol/protocolVersion` or the `MCP-Protocol-Version` header names a modern version. The handler then requires `MCP-Protocol-Version` and `Mcp-Method` (plus `Mcp-Name` for `tools/call`) to match the body, else `400` with `-32020`. It answers `server/discover`, puts `resultType: "complete"` on each result, and returns `404` with `-32601` for unknown methods.

### Test locally

```sh
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

- [[mcp-streamable-http-sessions]]
- [[mcp-initialize-handshake]]
- [[mcp-2026-07-28-changes]]
- [[mcp-server-typescript-minimal]]
- [[mcp-add-server-claude-code-cursor]]

## Sources

- [Cloudflare Pages Functions — Routing](https://developers.cloudflare.com/pages/functions/routing/)
- [Cloudflare Pages Functions — API reference](https://developers.cloudflare.com/pages/functions/api-reference/)
- [Cloudflare Pages Functions — Advanced mode](https://developers.cloudflare.com/pages/functions/advanced-mode/)
- [Cloudflare Pages Functions — Pricing](https://developers.cloudflare.com/pages/functions/pricing/)
- [Cloudflare Pages Functions — Local development](https://developers.cloudflare.com/pages/functions/local-development/)
- [Cloudflare Agents — Build a remote MCP server](https://developers.cloudflare.com/agents/guides/remote-mcp-server/)
- [Cloudflare Agents — McpAgent API](https://developers.cloudflare.com/agents/api-reference/mcp-agent-api/)
- [MCP Specification 2026-07-28 — Streamable HTTP](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http)

## Sources

- [Cloudflare Pages Functions — Routing](https://developers.cloudflare.com/pages/functions/routing/)
- [Cloudflare Pages Functions — API reference](https://developers.cloudflare.com/pages/functions/api-reference/)
- [Cloudflare Pages Functions — Advanced mode](https://developers.cloudflare.com/pages/functions/advanced-mode/)
- [Cloudflare Pages Functions — Pricing](https://developers.cloudflare.com/pages/functions/pricing/)
- [Cloudflare Pages Functions — Local development](https://developers.cloudflare.com/pages/functions/local-development/)
- [Cloudflare Agents — Build a remote MCP server](https://developers.cloudflare.com/agents/guides/remote-mcp-server/)
- [Cloudflare Agents — McpAgent API](https://developers.cloudflare.com/agents/api-reference/mcp-agent-api/)
- [MCP Specification 2026-07-28 — Streamable HTTP](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http)