Builds production-ready remote MCP servers on Cloudflare Workers with typed tools, OAuth authentication, and Wrangler deployment. Use when: "build MCP server on Cloudflare", "remote MCP", "deploy MCP to Workers", "MCP OAuth authentication", "expose tools via MCP".
Scaffolds and deploys production-ready Model Context Protocol servers on Cloudflare Workers.
Creates remote MCP servers using the McpAgent Durable Object pattern on Cloudflare Workers. Covers tool definition with Zod validation, public vs. OAuth-protected modes, local testing with MCP Inspector, Wrangler deployment, and client connection. Distinct from mcp-builder (which covers general MCP server concepts) and mcp-server-builder (which scaffolds from OpenAPI specs) — this skill is Cloudflare-specific and deployment-complete.
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.
OAuth-protected server:
npm create cloudflare@latest -- my-mcp-server \
--template=cloudflare/ai/demos/remote-mcp-github-oauth
cd my-mcp-server
Tools are functions MCP clients can call. Define them in McpAgent.init():
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() {
this.server.tool(
"add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
})
);
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) }] };
}
);
}
}
// 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 };
npm start
# In another terminal:
npx @modelcontextprotocol/inspector@latest
# Open http://localhost:5173, connect to http://localhost:8788/mcp
npx wrangler deploy
# Server accessible at https://[worker-name].[account].workers.dev/mcp
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.
// 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) },
],
};
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 */ }
);
export class MyMCP extends McpAgent<Env> {
async init() {
this.server.tool("query_db", { sql: z.string() }, async ({ sql }) => {
const result = await this.env.DB.prepare(sql).all();
return { content: [{ type: "text", text: JSON.stringify(result) }] };
});
}
}
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"]
# Optional: D1, KV bindings
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxx"
Supported providers: GitHub, Google, Auth0, Stytch, WorkOS, any OAuth 2.0 provider.
Set secrets via Wrangler:
wrangler secret put GITHUB_CLIENT_ID
wrangler secret put GITHUB_CLIENT_SECRET
Callback URL must match OAuth app config exactly. For local dev: http://localhost:8788/callback.
| Issue | Cause | Fix |
|---|---|---|
| "Tool not found" | Name mismatch | Verify exact case; ensure init() registers before connections |
| Connection fails | Wrong path | Confirm endpoint is /mcp; check CORS for browser clients |
| OAuth redirect error | Callback URL mismatch | Match OAuth app config; check secrets are set |
A Cloudflare Workers project directory containing:
src/index.ts — entry point with SSE routingsrc/mcp.ts — McpAgent class with tool definitionswrangler.toml — deployment configurationpackage.json — dependencies including agents, zod, @modelcontextprotocol/sdk