home / skills / cloudflare / skills / building-mcp-server-on-cloudflare
This skill helps you build production-ready MCP servers on Cloudflare Workers with tools, OAuth authentication, and deployment.
npx playbooks add skill cloudflare/skills --skill building-mcp-server-on-cloudflareReview the files below or copy the command above to add this skill to your agents.
---
name: building-mcp-server-on-cloudflare
description: |
Builds remote MCP (Model Context Protocol) servers on Cloudflare Workers
with tools, OAuth authentication, and production deployment. Generates
server code, configures auth providers, and deploys to Workers.
Use when: user wants to "build MCP server", "create MCP tools", "remote
MCP", "deploy MCP", add "OAuth to MCP", or mentions Model Context Protocol
on Cloudflare. Also triggers on "MCP authentication" or "MCP deployment".
---
# Building MCP Servers on Cloudflare
Creates production-ready Model Context Protocol servers on Cloudflare Workers with tools, authentication, and deployment.
## 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)
```bash
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)
```bash
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](references/oauth-setup.md).
## Core Workflow
### Step 1: Define Tools
Tools are functions MCP clients can call. Define them using `server.tool()`:
```typescript
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`):
```typescript
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](references/oauth-setup.md).
### Step 3: Test Locally
```bash
# 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
```bash
npx wrangler deploy
```
Server accessible at `https://[worker-name].[account].workers.dev/mcp`
### Step 5: Connect Clients
**Claude Desktop** (`claude_desktop_config.json`):
```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
```typescript
// 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
```typescript
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
```typescript
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](references/oauth-setup.md).
Supported providers:
- GitHub
- Google
- Auth0
- Stytch
- WorkOS
- Any OAuth 2.0 compliant provider
## Wrangler Configuration
Minimal `wrangler.toml`:
```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.):
```toml
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxx"
[[kv_namespaces]]
binding = "KV"
id = "xxx"
```
## Common Issues
### "Tool not found" in Client
1. Verify tool name matches exactly (case-sensitive)
2. Ensure `init()` registers tools before connections
3. Check server logs: `wrangler tail`
### Connection Fails
1. Confirm endpoint path is `/mcp`
2. Check CORS if browser-based client
3. Verify Worker is deployed: `wrangler deployments list`
### OAuth Redirect Errors
1. Callback URL must match OAuth app config exactly
2. Check `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` are set
3. For local dev, use `http://localhost:8788/callback`
## References
- [references/examples.md](references/examples.md) — Official templates and production examples
- [references/oauth-setup.md](references/oauth-setup.md) — OAuth provider configuration
- [references/tool-patterns.md](references/tool-patterns.md) — Advanced tool examples
- [references/troubleshooting.md](references/troubleshooting.md) — Error codes and fixes
This skill builds production-ready Model Context Protocol (MCP) servers on Cloudflare Workers, including tool wiring, OAuth authentication, and deployment. It generates server code, configures auth providers, and deploys the Worker so clients can connect to a remote MCP endpoint.
The skill scaffolds a Cloudflare Worker project (public or OAuth-protected), defines MCP tools as typed handlers, and exposes an SSE MCP endpoint at /mcp. It integrates optional environment bindings (D1, KV, Durable Objects) and configures OAuth providers for secure access. Finally it runs local testing with the MCP Inspector and deploys via Wrangler.
How do I test my MCP server locally?
Run npm start to launch the Worker locally, then open the MCP Inspector (npx @modelcontextprotocol/inspector) and point it at http://localhost:8788/mcp.
Which OAuth providers are supported?
Out of the box the templates support GitHub, Google, Auth0, Stytch, WorkOS, and any OAuth2-compliant provider with proper callback configuration.
Why do clients report 'Tool not found'?
Confirm the tool name matches exactly (case-sensitive), ensure init() registers tools before connections, and check Worker logs with wrangler tail.