Build and Deploy an MCP Server on Cloudflare Workers — Complete Guide (2026)
Nikhil Tiwari
MCP Playground
📖 TL;DR — Key Takeaways
- Cloudflare Workers is the fastest path to a globally deployed, remotely accessible MCP server — no containers, no cold starts, 300+ edge locations
- Two official approaches: McpAgent (Agents SDK — recommended for stateful servers) and workers-mcp (CLI approach — simpler for beginners)
- Free tier: 100,000 requests/day — enough for personal projects and early production. Paid plan from $5/month for 10M requests
- Deploy with
wrangler deploy— your MCP server is live atyour-worker.your-account.workers.dev/mcpin under a minute - Cloudflare KV gives your MCP tools persistent storage; Durable Objects enable per-session state with zero infrastructure
Most MCP server tutorials end at "it works on localhost." You get a stdio-based server running in Claude Desktop, and everything is great — until you want to share it with your team, expose it to a remote MCP client, or run it in production without keeping your laptop open.
Cloudflare Workers solves this. It gives you a serverless runtime that is genuinely global — your MCP server runs within milliseconds of any user, anywhere in the world, on Cloudflare's edge network. No VPS to maintain, no Docker containers to orchestrate, no cold start delays. Deploy a TypeScript file and you have a remote MCP server with Streamable HTTP transport that any MCP client can connect to.
Cloudflare has made MCP a first-class citizen in its developer platform. The official Agents SDK ships with an McpAgent class that handles transport, session state, and tool registration out of the box. The older workers-mcp package offers a simpler CLI-based approach for teams who want to translate existing TypeScript Workers into MCP tools without rewriting anything.
This guide covers both approaches with real code, explains when to use each, adds persistent KV storage, and walks through deploying and connecting to Claude Desktop.
Why Cloudflare Workers for MCP?
Running an MCP server on Cloudflare Workers gives you advantages that are genuinely hard to replicate with other deployment options:
⚡ Zero Cold Starts
Workers use V8 isolates, not containers. Your MCP server responds in milliseconds — no spinning up waiting for a Lambda to wake
🌍 300+ Edge Locations
Your server runs in the nearest Cloudflare datacenter to each user — sub-50ms latency globally, not just in us-east-1
🆓 Generous Free Tier
100,000 requests/day free forever. Enough for personal projects, team tools, and early-stage production without paying anything
🔌 Streamable HTTP Native
The Agents SDK handles the Streamable HTTP transport (the current MCP spec standard) automatically — no manual transport code
🗃️ KV + Durable Objects
Persistent key-value storage (KV) and per-session stateful objects (Durable Objects) built in — no external database needed for most use cases
🚀 One Command Deploy
wrangler deploy ships your server to the global edge. No Docker, no CI/CD setup, no infrastructure configuration
Compared to other deployment options: a VPS (Hetzner, DigitalOcean) requires server management and is a single location. AWS Lambda has cold starts and per-region configuration. A local Python server means keeping your machine running 24/7. Cloudflare Workers removes all of these tradeoffs for most MCP use cases.
Two Approaches: McpAgent vs workers-mcp
Cloudflare has two official paths to building MCP servers on Workers. Understanding the difference saves significant confusion:
| McpAgent (Agents SDK) | workers-mcp | |
|---|---|---|
| Package | cloudflare/agents | workers-mcp |
| Approach | Extend the McpAgent class, define tools in init() | CLI translates TypeScript class methods into MCP tools via a local proxy |
| Transport | Streamable HTTP (built-in, no config) | Stdio proxy → HTTP (local Node.js proxy required) |
| State | Durable Object per session — built-in stateful | Stateless (Workers stateless model) |
| Remote clients | Yes — any MCP client connects via URL | Local only via stdio proxy |
| Best for | Production, remote MCP, stateful tools, team sharing | Quick prototypes, existing Workers you want to expose as MCP tools |
| Recommended | ✅ Yes — current recommended approach | Simpler for getting started locally |
This guide covers McpAgent first (the recommended production path), then workers-mcp for simpler use cases.
Prerequisites
- Node.js 18+ installed
- A Cloudflare account — free to create
- Wrangler CLI:
npm install -g wrangler - Claude Desktop installed (for testing)
Authenticate Wrangler with your Cloudflare account before starting:
wrangler login
Approach 1: McpAgent (Recommended)
The Agents SDK's McpAgent class is Cloudflare's first-class approach to MCP servers. It handles Streamable HTTP transport automatically, creates a Durable Object per session for state management, and deploys as a standard Cloudflare Worker.
Step 1 — Scaffold the Project
Use the official Cloudflare remote MCP template — the quickest path to a working server:
npm create cloudflare@latest -- my-mcp-server \
--template=cloudflare/ai/demos/remote-mcp-authless
cd my-mcp-server
npm install
This gives you a minimal Worker with McpAgent pre-configured. Alternatively, start from scratch:
npm create cloudflare@latest -- my-mcp-server
cd my-mcp-server
npm install cloudflare/agents
Step 2 — Project Structure
my-mcp-server/
├── src/
│ └── index.ts # Your MCP server
├── wrangler.jsonc # Cloudflare Worker config
├── package.json
└── tsconfig.json
Step 3 — Write Your MCP Server
Open src/index.ts and build your MCP server by extending McpAgent. Tools are defined in the init() method — they have access to this (the agent instance), which means they can read and write state:
import { McpAgent } from "cloudflare/agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
// Define your environment bindings
export interface Env {
MY_KV: KVNamespace; // optional — add if using KV storage
MCP_OBJECT: DurableObjectNamespace;
}
// Extend McpAgent with your Env type
export class MyMcpServer extends McpAgent {
server = new McpServer({
name: "my-mcp-server",
version: "1.0.0",
});
async init() {
// Tool 1: Simple calculation tool
this.server.tool(
"add_numbers",
"Add two numbers together",
{
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
},
async ({ a, b }) => ({
content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }],
})
);
// Tool 2: Fetch data from an external API
this.server.tool(
"get_weather",
"Get current weather for a city",
{
city: z.string().describe("City name"),
},
async ({ city }) => {
const res = await fetch(
`https://wttr.in/${encodeURIComponent(city)}?format=3`
);
const text = await res.text();
return {
content: [{ type: "text", text: text.trim() }],
};
}
);
// Tool 3: Read from KV storage
this.server.tool(
"get_note",
"Retrieve a saved note by key",
{
key: z.string().describe("The note key to retrieve"),
},
async ({ key }) => {
const value = await this.env.MY_KV.get(key);
return {
content: [
{
type: "text",
text: value ?? `No note found for key: ${key}`,
},
],
};
}
);
// Tool 4: Write to KV storage
this.server.tool(
"save_note",
"Save a note with a key",
{
key: z.string().describe("The key to save under"),
value: z.string().describe("The note content"),
},
async ({ key, value }) => {
await this.env.MY_KV.put(key, value);
return {
content: [{ type: "text", text: `Saved note under key: ${key}` }],
};
}
);
}
}
// Export the Worker handler — McpAgent.serve() handles routing
export default McpAgent.serve("/mcp", MyMcpServer);
Note: McpAgent.serve("/mcp", MyMcpServer) creates the Worker's fetch handler and routes POST /mcp requests to your agent. The Streamable HTTP transport is handled entirely by the SDK — you don't write any transport code.
Step 4 — Configure wrangler.jsonc
{
"name": "my-mcp-server",
"main": "src/index.ts",
"compatibility_date": "2025-11-01",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [
{
"name": "MCP_OBJECT",
"class_name": "MyMcpServer"
}
]
},
"kv_namespaces": [
{
"binding": "MY_KV",
"id": "YOUR_KV_NAMESPACE_ID"
}
],
"migrations": [
{
"tag": "v1",
"new_classes": ["MyMcpServer"]
}
]
}
If you are not using KV storage, remove the kv_namespaces section entirely. The Durable Objects binding is required for McpAgent session state.
Approach 2: workers-mcp (Simpler, Local)
The workers-mcp package takes a different approach: you write a normal TypeScript Worker class with regular methods, and the CLI translates those methods into MCP tools. A local Node.js proxy acts as the stdio bridge between Claude Desktop and your remote Worker.
This approach is simpler to get started with, but the local proxy means it only works for stdio-based MCP clients (like Claude Desktop locally). It does not support remote MCP connections without extra configuration.
Step 1 — Create a Worker and Install workers-mcp
npm create cloudflare@latest -- my-worker
cd my-worker
npm install workers-mcp
Step 2 — Write Your Worker as a TypeScript Class
import { WorkerEntrypoint } from "cloudflare:workers";
import { ProxyToSelf } from "workers-mcp";
export default class MyWorkerMCP extends WorkerEntrypoint<Env> {
/**
* Returns a greeting message for the given name.
* @param name {string} The name to greet
* @return {string} A personalized greeting
*/
async greet(name: string): Promise<string> {
return `Hello, ${name}! This response came from a Cloudflare Worker.`;
}
/**
* Fetches the current time in a given timezone.
* @param timezone {string} IANA timezone string e.g. "America/New_York"
* @return {string} Current time in that timezone
*/
async getCurrentTime(timezone: string): Promise<string> {
return new Date().toLocaleString("en-US", { timeZone: timezone });
}
// Required: routes MCP requests to the correct method
async fetch(request: Request): Promise<Response> {
return new ProxyToSelf(this).fetch(request);
}
}
Important: JSDoc comments on your methods are required — workers-mcp uses them to generate the MCP tool descriptions. The @param and @return tags become the tool's input schema and output description.
Step 3 — Run the CLI Setup
npx workers-mcp setup
This command deploys your Worker to Cloudflare and generates the local proxy configuration. It outputs a config block you add to Claude Desktop.
Adding KV Storage to Your MCP Tools
Cloudflare KV is a globally distributed key-value store — ideal for saving notes, caching API responses, storing user preferences, or maintaining tool state between sessions. Here's how to create a KV namespace and bind it to your Worker:
Create the KV Namespace
# Create the namespace and note the ID it returns
npx wrangler kv namespace create "MY_KV"
# Output:
# ✅ Successfully created namespace
# Add the following to your wrangler.jsonc:
# { "binding": "MY_KV", "id": "abc123...your-namespace-id" }
Add the Binding to wrangler.jsonc
{
"kv_namespaces": [
{
"binding": "MY_KV",
"id": "abc123...your-namespace-id"
}
]
}
Use KV in Your MCP Tool
// Inside your McpAgent's init() method:
this.server.tool(
"remember",
"Store a piece of information for later retrieval",
{
label: z.string().describe("A short label for what you're storing"),
content: z.string().describe("The information to remember"),
},
async ({ label, content }) => {
await this.env.MY_KV.put(label, content, {
expirationTtl: 86400, // expires after 24 hours (optional)
});
return {
content: [{ type: "text", text: `Stored "${label}" — I'll remember that.` }],
};
}
);
this.server.tool(
"recall",
"Retrieve previously stored information by label",
{
label: z.string().describe("The label of the information to recall"),
},
async ({ label }) => {
const value = await this.env.MY_KV.get(label);
return {
content: [
{
type: "text",
text: value
? `Recalled "${label}": ${value}`
: `Nothing stored under "${label}"`,
},
],
};
}
);
Deploy with Wrangler
Test Locally First
npx wrangler dev
This starts a local dev server at http://localhost:8787. Your MCP endpoint is available at http://localhost:8787/mcp. You can test it with any MCP client — or paste the URL into MCP Playground to inspect tools and run live requests without any client setup.
Deploy to Production
npx wrangler deploy
That's it. Wrangler builds your TypeScript, bundles it, and deploys to Cloudflare's global edge. The output shows your live URL:
✅ Deployed my-mcp-server (1.23 sec)
https://my-mcp-server.your-account.workers.dev
Your MCP endpoint is live at:
https://my-mcp-server.your-account.workers.dev/mcp
This URL is your permanent remote MCP server URL. Share it with teammates, add it to Claude Desktop, or connect any MCP-compatible client.
View Logs
# Stream real-time logs from your deployed Worker
npx wrangler tail my-mcp-server
Connect to Claude Desktop
Open your Claude Desktop config file at ~/.config/claude/claude_desktop_config.json (macOS/Linux) or %APPDATA%\Claude\claude_desktop_config.json (Windows) and add your Worker URL:
For McpAgent (Remote, Recommended)
{
"mcpServers": {
"my-mcp-server": {
"url": "https://my-mcp-server.your-account.workers.dev/mcp"
}
}
}
For workers-mcp (Local Proxy)
The npx workers-mcp setup command generates this config automatically. It looks like:
{
"mcpServers": {
"my-worker": {
"command": "node",
"args": [
"/path/to/your/project/node_modules/workers-mcp/dist/index.js",
"my-worker",
"https://my-worker.your-account.workers.dev"
]
}
}
}
Save the config file and restart Claude Desktop. You should see your server listed in the MCP connections panel — and your tools available when starting a new conversation.
Testing tip: Before adding to Claude Desktop, paste your /mcp URL into MCP Playground to verify your server responds correctly and your tools are visible. It takes 10 seconds and saves debugging time.
Pricing and Free Tier Limits
| Feature | Free Plan | Paid Plan ($5/month) |
|---|---|---|
| Requests | 100,000 / day | 10 million / month (~333k/day avg) |
| CPU time | 10ms per request | 30 million CPU-ms / month |
| KV reads | 100,000 / day | 10 million / month |
| KV writes | 1,000 / day | 1 million / month |
| Durable Objects | Not included | Included (required for McpAgent sessions) |
| Custom domains | workers.dev subdomain only | Custom domains supported |
Important: McpAgent uses Durable Objects for session state. Durable Objects are not available on the free plan — you need at minimum the $5/month Workers Paid plan to use the McpAgent approach in production. The free workers-mcp approach does not require Durable Objects and works on the free tier.
For most personal and team MCP servers, the paid plan at $5/month is more than sufficient. At 10M requests/month, you would need roughly 3–4 MCP tool calls per second continuously to hit the limit.
When NOT to Use Cloudflare Workers for MCP
Cloudflare Workers is excellent for most MCP use cases, but it has real constraints:
- CPU-intensive tools — Workers have a 30-second CPU limit (paid). Long-running ML inference, video processing, or heavy computation will hit this. Use a VPS or dedicated compute instead.
- Large binary data — Workers have a 128MB memory limit. If your MCP tools process large files, images, or datasets, you'll need a different runtime.
- Node.js-specific libraries — Workers run in V8 isolates, not Node.js. Most npm packages work, but packages that depend on native Node.js APIs (
child_process,fsin full form) do not. - Persistent long-running connections — Workers terminate after the request completes. If your tools need to maintain a persistent connection to an external system, Durable Objects can help but add complexity.
- Database-heavy MCP servers — KV is not a relational database. For complex queries, joins, or large datasets, pair your Worker with an external database (PlanetScale, Supabase, Neon) via their APIs.
For these cases, the Docker deployment guide or a traditional VPS is a better fit. Cloudflare Workers excels at lightweight, fast, API-wrapping MCP servers — which covers the majority of what developers actually build.
Complete wrangler.jsonc Reference
For reference, a fully configured wrangler.jsonc for an McpAgent server with KV storage:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-mcp-server",
"main": "src/index.ts",
"compatibility_date": "2025-11-01",
"compatibility_flags": ["nodejs_compat"],
// Durable Objects — required for McpAgent session state
"durable_objects": {
"bindings": [
{
"name": "MCP_OBJECT",
"class_name": "MyMcpServer"
}
]
},
// KV namespace — optional, for persistent storage
"kv_namespaces": [
{
"binding": "MY_KV",
"id": "YOUR_KV_NAMESPACE_ID",
"preview_id": "YOUR_PREVIEW_KV_ID" // for local dev
}
],
// Required when using Durable Objects
"migrations": [
{
"tag": "v1",
"new_classes": ["MyMcpServer"]
}
]
}
Frequently Asked Questions
Do I need a Cloudflare account with a paid plan?+
workers-mcp approach (local proxy) works on the free tier. The McpAgent approach requires Durable Objects, which require the Workers Paid plan at $5/month. For most production MCP servers, the $5/month plan is the right choice.Can I use environment variables and secrets in my MCP tools?+
wrangler secret put SECRET_NAME to store secrets securely. They're available in your Worker as this.env.SECRET_NAME. Never hardcode API keys in your Worker source — always use Wrangler secrets for anything sensitive.Can multiple MCP clients connect to the same Worker simultaneously?+
How do I add authentication so only my team can use the MCP server?+
Authorization header in your Worker's fetch handler before routing to McpAgent. Cloudflare Access can also protect your Worker URL without any code changes.How is this different from just building a REST API?+
Related Guides
- Build a Custom MCP Server Using Node.js (Simple Guide)
- Build Your First MCP Server with Python and FastMCP
- Deploy an MCP Server to Production with Docker
- Where to Host MCP Servers for Free: Cloudflare, Vercel, and More
- Wrap Existing APIs as MCP Tools (Simple Guide)
Resources:
- Cloudflare Agents — MCP Documentation — Official Agents SDK MCP reference
- Build a Remote MCP Server — Cloudflare Guide — Step-by-step official tutorial
- cloudflare/workers-mcp — GitHub — Official workers-mcp package and examples
- cloudflare/mcp-server-cloudflare — GitHub — Cloudflare's own MCP servers (Workers, KV, R2, D1)
- Cloudflare Blog: Remote MCP Servers — Announcement and deep dive
- Cloudflare Workers Pricing — Current free and paid tier limits
- MCP Playground Online — Test your deployed Cloudflare MCP server in the browser
Written by Nikhil Tiwari
15+ years in product development. AI enthusiast building developer tools that make complex technologies accessible to everyone.
Related Resources