Builds remote MCP (Model Context Protocol) servers on Cloudflare Workers with tools, OAuth authentication, and production deployment. Generates server code,…
Building MCP Servers on Cloudflare
Your knowledge of the MCP SDK and Cloudflare Workers integration may be outdated. Prefer retrieval over pre-training for any MCP server task.
Retrieval Sources
Source
How to retrieve
Use for
MCP docs
https://developers.cloudflare.com/agents/mcp/
Server setup, auth, deployment
MCP spec
https://modelcontextprotocol.io/
Protocol spec, tool/resource definitions
Workers docs
Search tool or https://developers.cloudflare.com/workers/
Runtime APIs, bindings, config
When to Use
User wants to build a remote MCP server
User needs to expose tools via MCP
User asks about MCP authentication or OAuth
User wants to deploy MCP to Cloudflare Workers
Prerequisites
Cloudflare account with Workers enabled
Node.js 18+ and npm/pnpm/yarn
Wrangler CLI (npm install -g wrangler)
Quick Start
Option 1: Public Server (No Auth)
npm create cloudflare@latest -- my-mcp-server \
--template=cloudflare/ai/demos/remote-mcp-authless
cd my-mcp-server
npm start
Server runs at http://localhost:8788/mcp
Option 2: Authenticated Server (OAuth)
npm create cloudflare@latest -- my-mcp-server \
--template=cloudflare/ai/demos/remote-mcp-github-oauth
cd my-mcp-server
Requires OAuth app setup. See references/oauth-setup.md.
Core Workflow
Step 1: Define Tools
Tools are functions MCP clients can call. Define them using server.tool():
import { McpAgent } from "agents/mcp";
import { z } from "zod";
export class MyMCP extends McpAgent {
server = new Server({ name: "my-mcp", version: "1.0.0" });
async init() {
// Simple tool with parameters
this.server.tool(
"add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
})
);
// Tool that calls external API
this.server.tool(
"get_weather",
{ city: z.string() },
async ({ city }) => {
const response = await fetch(`https://api.weather.com/${city}`);
const data = await response.json();
return {
content: [{ type: "text", text: JSON.stringify(data) }],
};
}
);
}
}
Step 2: Configure Entry Point
Public server (src/index.ts):
import { MyMCP } from "./mcp";
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(request.url);
if (url.pathname === "/mcp") {
return MyMCP.serveSSE("/mcp").fetch(request, env, ctx);
}
return new Response("MCP Server", { status: 200 });
},
};
export { MyMCP };
Authenticated server — See references/oauth-setup.md.
Step 3: Test Locally
# Start server
npm start
# In another terminal, test with MCP Inspector
npx @modelcontextprotocol/inspector@latest
# Open http://localhost:5173, enter http://localhost:8788/mcp
Step 4: Deploy
npx wrangler deploy
Server accessible at https://[worker-name].[account].workers.dev/mcp
Step 5: Connect Clients
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"my-server": {
"command": "npx",
"args": ["mcp-remote", "https://my-mcp.workers.dev/mcp"]
}
}
}
Restart Claude Desktop after updating config.
Tool Patterns
Return Types
// Text response
return { content: [{ type: "text", text: "result" }] };
// Multiple content items
return {
content: [
{ type: "text", text: "Here's the data:" },
{ type: "text", text: JSON.stringify(data, null, 2) },
],
};
Input Validation with Zod
this.server.tool(
"create_user",
{
email: z.string().email(),
name: z.string().min(1).max(100),
role: z.enum(["admin", "user", "guest"]),
age: z.number().int().min(0).optional(),
},
async (params) => {
// params are fully typed and validated
}
);
Accessing Environment/Bindings
export class MyMCP extends McpAgent<Env> {
async init() {
this.server.tool("query_db", { sql: z.string() }, async ({ sql }) => {
// Access D1 binding
const result = await this.env.DB.prepare(sql).all();
return { content: [{ type: "text", text: JSON.stringify(result) }] };
});
}
}
Authentication
For OAuth-protected servers, see references/oauth-setup.md.
Supported providers:
GitHub
Google
Auth0
Stytch
WorkOS
Any OAuth 2.0 compliant provider
Wrangler Configuration
Minimal wrangler.toml:
name = "my-mcp-server"
main = "src/index.ts"
compatibility_date = "2024-12-01"
[durable_objects]
bindings = [{ name = "MCP", class_name = "MyMCP" }]
[[migrations]]
tag = "v1"
new_classes = ["MyMCP"]
With bindings (D1, KV, etc.):
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxx"
[[kv_namespaces]]
binding = "KV"
id = "xxx"
Common Issues
"Tool not found" in Client
Verify tool name matches exactly (case-sensitive)
Ensure init() registers tools before connections
Check server logs: wrangler tail
Connection Fails
Confirm endpoint path is /mcp
Check CORS if browser-based client
Verify Worker is deployed: wrangler deployments list
OAuth Redirect Errors
Callback URL must match OAuth app config exactly
Check GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET are set
For local dev, use http://localhost:8788/callback
References
references/examples.md — Official templates and production examples
references/oauth-setup.md — OAuth provider configuration
references/tool-patterns.md — Advanced tool examples
references/troubleshooting.md — Error codes and fixesdon't have the plugin yet? install it then click "run inline in claude" again.