Production-ready Model Context Protocol (MCP) server on Cloudflare Workers. Bearer-token auth, structured logging, JSON-RPC 2.0 over HTTP. Stateless, edge-distributed, scale-to-zero. ~200 lines of TypeScript.
Companion to: MCP Servers in Production: When to Build, When to Skip
A working MCP server that:
- Speaks JSON-RPC 2.0 over HTTP POST (the modern MCP transport).
- Runs on Cloudflare Workers — globally distributed, scale-to-zero, no servers to manage.
- Authenticates via bearer token (set as a Worker secret).
- Exposes one sample tool —
getCompanyFacts— that you replace with your own.
The boring production hygiene is already there: error handling with proper JSON-RPC error codes, request validation, structured logging, a health endpoint, and a landing page so visitors who hit the URL directly get something meaningful.
# 1. Install
npm install
# 2. Pick a strong random token. This is the auth secret your MCP client will pass.
TOKEN=$(openssl rand -base64 32)
echo "Your MCP_TOKEN: $TOKEN"
echo "(save this — you'll paste it in your client config below)"
# 3. Set the token as a Worker secret
npx wrangler secret put MCP_TOKEN
# (paste the token from step 2)
# 4. Deploy
npm run deployThe Worker is now live at https://cf-mcp-template.<your-subdomain>.workers.dev.
WORKER=https://cf-mcp-template.<your-subdomain>.workers.dev
TOKEN=<the token you saved above>
# Initialize
curl -s -X POST "$WORKER" \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'
# List tools
curl -s -X POST "$WORKER" \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
# Call a tool
curl -s -X POST "$WORKER" \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"getCompanyFacts","arguments":{"ticker":"NET"}}}'Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or the equivalent on your platform:
{
"mcpServers": {
"company-facts": {
"url": "https://cf-mcp-template.<your-subdomain>.workers.dev",
"headers": {
"Authorization": "Bearer <your-MCP_TOKEN>"
}
}
}
}Restart Claude Desktop. The getCompanyFacts tool will now be available in conversations.
In Cursor → Settings → MCP Servers → Add Server, paste:
{
"url": "https://cf-mcp-template.<your-subdomain>.workers.dev",
"headers": {
"Authorization": "Bearer <your-MCP_TOKEN>"
}
}Two places to edit in src/index.ts:
TOOLSarray — add a new entry withname,description, and a strict JSON Schema forinputSchema. The description is how the LLM knows when to call your tool — be specific.callToolfunction — add anif (name === 'yourToolName') { ... }branch. Validate inputs. Return{ content: [{ type: 'text', text: '...' }] }.
For tools that hit external data (D1, KV, R2, third-party APIs):
- Uncomment the relevant binding in
wrangler.jsonc. - Add it to the
Envinterface insrc/index.ts. - Pass
envthrough tocallTool(currently the function takes onlynameandargs— extend the signature).
The template is suitable for development and small-team use as-is. Before exposing to a wider audience, add:
- Rate limiting. Configure Cloudflare's Rate Limiting Rules on your zone, scoped to this Worker's URL.
- Per-tool caching. If a tool returns expensive-to-compute data that's cacheable, cache the result by
name + argshash with a short TTL using Cloudflare KV or the Cache API. - Output size limits. Tool outputs that overflow the LLM's context window cause hallucinations. Pre-truncate or paginate.
- Tool timeouts. Long-running tools should return a job ID + provide a
getJobStatustool for polling, not block. - Per-tool authz. If different clients need different tool access, add a permission map keyed by token rather than a single shared
MCP_TOKEN. - Audit logging. Log every tool invocation with token-hash + tool-name + duration + success. Workers Logs is free.
- Not stdio — the Worker speaks HTTP. For local stdio MCP servers, see Anthropic's official SDK.
- Not OAuth-protected. Bearer token is sufficient for trusted internal use; switch to OAuth 2.1 (the MCP-spec preferred auth) for external developer audiences.
- Not stateful. Each request is independent. For tools that need session state, add Cloudflare Durable Objects.
MIT — see LICENSE.
SetKernel Digital Inc. — a Cloudflare-native engineering studio. We design, build, and operate AI-augmented products on the edge. Need an MCP server architected for your specific tools? Write a brief.