# Jackspace Claudeskillz Cloudflare MCP Server

> Cloudflare MCP Server Skill

- Skill: `tomevault-io/jackspace-claudeskillz-cloudflare-mcp-server` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/jackspace-claudeskillz-cloudflare-mcp-server`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/jackspace-claudeskillz-cloudflare-mcp-server/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/jackspace-claudeskillz-cloudflare-mcp-server

---


# Cloudflare MCP Server Skill

Build and deploy **Model Context Protocol (MCP) servers** on Cloudflare Workers with TypeScript.

---

## What is This Skill?

This skill teaches you to build **remote MCP servers** on Cloudflare - the ONLY platform with official remote MCP support as of 2025.

**Use this skill when**:
- Building MCP servers with TypeScript (@modelcontextprotocol/sdk)
- Deploying remote MCP servers to Cloudflare Workers
- Implementing OAuth authentication (GitHub, Google, Azure, custom)
- Creating stateful MCP servers with Durable Objects
- Optimizing costs with WebSocket hibernation
- Supporting both SSE and Streamable HTTP transports
- Avoiding 15+ common MCP + Cloudflare errors

**You'll learn**:
1. McpAgent class patterns and tool definitions
2. OAuth integration (all 4 auth patterns)
3. Durable Objects for per-session state
4. WebSocket hibernation API
5. Dual transport configuration (SSE + HTTP)
6. Complete deployment workflow

---

## Quick Start (5 Minutes)

### Option 1: Deploy from Template

```bash
# Create new MCP server from Cloudflare template
npm create cloudflare@latest -- my-mcp-server \
  --template=cloudflare/ai/demos/remote-mcp-authless

cd my-mcp-server
npm install
npm run dev
```

Your MCP server is now running at `http://localhost:8788/sse`

### Option 2: Copy Templates from This Skill

```bash
# Copy basic MCP server template
cp ~/.claude/skills/cloudflare-mcp-server/templates/basic-mcp-server.ts src/index.ts
cp ~/.claude/skills/cloudflare-mcp-server/templates/wrangler-basic.jsonc wrangler.jsonc
cp ~/.claude/skills/cloudflare-mcp-server/templates/package.json package.json

# Install dependencies
npm install

# Start development server
npm run dev
```

### Test with MCP Inspector

```bash
# In a new terminal, start MCP Inspector
npx @modelcontextprotocol/inspector@latest

# Open http://localhost:5173
# Enter your MCP server URL: http://localhost:8788/sse
# Click "Connect" and test tools
```

### Deploy to Cloudflare

```bash
# Deploy to production
npx wrangler deploy

# Your MCP server is now live at:
# https://my-mcp-server.your-account.workers.dev/sse
```

---

## Core Concepts

### 1. McpAgent Class

The `McpAgent` base class from Cloudflare's Agents SDK provides:
- Automatic Durable Objects integration
- Built-in state management with SQL database
- Tool, resource, and prompt registration
- Transport handling (SSE + HTTP)

**Basic pattern**:
```typescript
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export class MyMCP extends McpAgent<Env> {
  server = new McpServer({
    name: "My MCP Server",
    version: "1.0.0"
  });

  async init() {
    // Register tools here
    this.server.tool(
      "tool_name",
      "Tool description",
      { param: z.string() },
      async ({ param }) => ({
        content: [{ type: "text", text: "Result" }]
      })
    );
  }
}
```

### 2. Tool Definitions

Tools are functions that MCP clients can invoke. Use Zod for parameter validation.

**Pattern**:
```typescript
this.server.tool(
  "tool_name",           // Tool identifier
  "Tool description",    // What it does (for LLM)
  {                      // Parameters (Zod schema)
    param1: z.string().describe("Parameter description"),
    param2: z.number().optional()
  },
  async ({ param1, param2 }) => {  // Handler
    // Your logic here
    return {
      content: [{ type: "text", text: "Result" }]
    };
  }
);
```

**Best practices**:
- **Detailed descriptions**: Help LLMs understand tool purpose
- **Parameter descriptions**: Explain expected values and constraints
- **Error handling**: Return `{ isError: true }` for failures
- **Few, focused tools**: Better than many granular ones

### 3. Transport Methods

MCP supports two transports:

**SSE (Server-Sent Events)** - Legacy, widely supported:
```typescript
MyMCP.serveSSE("/sse").fetch(request, env, ctx)
```

**Streamable HTTP** - 2025 standard, more efficient:
```typescript
MyMCP.serve("/mcp").fetch(request, env, ctx)
```

**Support both** for maximum compatibility:
```typescript
export default {
  fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const { pathname } = new URL(request.url);

    if (pathname.startsWith("/sse")) {
      return MyMCP.serveSSE("/sse").fetch(request, env, ctx);
    }
    if (pathname.startsWith("/mcp")) {
      return MyMCP.serve("/mcp").fetch(request, env, ctx);
    }

    return new Response("Not Found", { status: 404 });
  }
};
```

---

## Authentication Patterns

Cloudflare MCP servers support **4 authentication patterns**:

### Pattern 1: No Authentication

**Use case**: Internal tools, development, public APIs

**Template**: `templates/basic-mcp-server.ts`

**Setup**: None required

**Security**: ⚠️ Anyone can access your MCP server

---

### Pattern 2: Token Validation (JWTVerifier)

**Use case**: Pre-authenticated clients, custom auth systems

**How it works**: Client sends Bearer token, server validates

**Template**: Create custom JWTVerifier middleware

**Setup**:
```typescript
import { JWTVerifier } from "agents/mcp";

const verifier = new JWTVerifier({
  secret: env.JWT_SECRET,
  issuer: "your-auth-server"
});

// Validate token before serving MCP requests
```

**Security**: ✅ Secure if tokens are properly managed

---

### Pattern 3: OAuth Proxy (workers-oauth-provider)

**Use case**: GitHub, Google, Azure OAuth integration

**How it works**: Cloudflare Worker proxies OAuth to third-party provider

**Template**: `templates/mcp-oauth-proxy.ts`

**Setup**:
```typescript
import { OAuthProvider, GitHubHandler } from "@cloudflare/workers-oauth-provider";

export default new OAuthProvider({
  authorizeEndpoint: "/authorize",
  tokenEndpoint: "/token",
  clientRegistrationEndpoint: "/register",

  defaultHandler: new GitHubHandler({
    clientId: (env) => env.GITHUB_CLIENT_ID,
    clientSecret: (env) => env.GITHUB_CLIENT_SECRET,
    scopes: ["repo", "user:email"],

    context: async (accessToken) => {
      // Fetch user info from GitHub
      const octokit = new Octokit({ auth: accessToken });
      const { data: user } = await octokit.rest.users.getAuthenticated();

      return {
        login: user.login,
        email: user.email,
        accessToken
      };
    }
  }),

  kv: (env) => env.OAUTH_KV,
  apiHandlers: {
    "/sse": MyMCP.serveSSE("/sse"),
    "/mcp": MyMCP.serve("/mcp")
  },

  allowConsentScreen: true,
  allowDynamicClientRegistration: true
});
```

**Required bindings**:
```jsonc
{
  "kv_namespaces": [
    { "binding": "OAUTH_KV", "id": "YOUR_KV_ID" }
  ]
}
```

**Security**: ✅✅ Secure, production-ready

---

### Pattern 4: Remote OAuth with DCR

**Use case**: Full OAuth provider, custom consent screens

**How it works**: Your Worker is the OAuth provider

**Template**: See Cloudflare's `remote-mcp-authkit` demo

**Setup**: Complex, requires full OAuth 2.1 implementation

**Security**: ✅✅✅ Most secure, full control

---

## Stateful MCP Servers with Durable Objects

Use **Durable Objects** when your MCP server needs:
- Per-session persistent state
- Conversation history
- Game state (chess, tic-tac-toe)
- Cached API responses
- User preferences

### Storage API Pattern

**Template**: `templates/mcp-stateful-do.ts`

**Store values**:
```typescript
await this.state.storage.put("key", "value");
await this.state.storage.put("user_prefs", { theme: "dark" });
```

**Retrieve values**:
```typescript
const value = await this.state.storage.get<string>("key");
const prefs = await this.state.storage.get<object>("user_prefs");
```

**List keys**:
```typescript
const allKeys = await this.state.storage.list();
```

**Delete keys**:
```typescript
await this.state.storage.delete("key");
```

### Configuration

**wrangler.jsonc**:
```jsonc
{
  "durable_objects": {
    "bindings": [
      {
        "name": "MY_MCP",
        "class_name": "MyMCP",
        "script_name": "my-mcp-server"
      }
    ]
  },

  "migrations": [
    { "tag": "v1", "new_classes": ["MyMCP"] }
  ]
}
```

**IMPORTANT**: Migrations are required on first deployment!

---

## WebSocket Hibernation for Cost Optimization

**Problem**: Long-lived WebSocket connections cost CPU time

**Solution**: WebSocket Hibernation API suspends connections when idle

### Pattern

**Serialize metadata** (preserves data during hibernation):
```typescript
webSocket.serializeAttachment({
  userId: "123",
  sessionId: "abc",
  connectedAt: Date.now()
});
```

**Retrieve on wake**:
```typescript
const metadata = webSocket.deserializeAttachment();
console.log(metadata.userId); // "123"
```

**Storage for persistent state**:
```typescript
// ❌ DON'T: In-memory state lost on hibernation
this.userId = "123";

// ✅ DO: Use storage API
await this.state.storage.put("userId", "123");
```

### Cost Savings

Without hibernation:
- 1000 concurrent WebSockets × 10ms CPU/sec = 10 CPU-sec/sec
- **Cost: ~$0.50/day**

With hibernation:
- CPU only on messages (99% idle time suspended)
- **Cost: ~$0.01/day** (50x reduction!)

---

## Worker & Durable Objects Basics

*Self-contained section for standalone use*

### Worker Export Pattern

**Workers must export a `fetch` handler**:
```typescript
export default {
  fetch(request: Request, env: Env, ctx: ExecutionContext): Response | Promise<Response> {
    // Handle request
    return new Response("Hello");
  }
};
```

### Durable Objects Class Structure

**DOs extend McpAgent** (for MCP servers):
```typescript
export class MyMCP extends McpAgent<Env> {
  constructor(state: DurableObjectState, env: Env) {
    super(state, env);
  }

  // Your methods here
}
```

### Bindings Configuration

**Environment bindings** give Workers access to resources:
```jsonc
{
  "kv_namespaces": [{ "binding": "MY_KV", "id": "..." }],
  "durable_objects": {
    "bindings": [{ "name": "MY_DO", "class_name": "MyDO" }]
  },
  "r2_buckets": [{ "binding": "MY_BUCKET", "bucket_name": "..." }]
}
```

**Access in code**:
```typescript
env.MY_KV.get("key");
env.MY_DO.idFromName("session-123").getStub(env);
env.MY_BUCKET.get("file.txt");
```

---

## Deployment & Testing

### Local Development

```bash
# Start dev server (uses Miniflare for local DOs)
npm run dev

# Start dev server with remote Durable Objects (more accurate)
npx wrangler dev --remote
```

**Access at**: `http://localhost:8788/sse`

### Test with MCP Inspector

```bash
npx @modelcontextprotocol/inspector@latest
```

1. Open `http://localhost:5173`
2. Enter MCP server URL
3. Click "Connect"
4. Use "List Tools" to see available tools
5. Test tool calls with parameters

### Deploy to Cloudflare

```bash
# First time: Login
npx wrangler login

# Deploy
npx wrangler deploy

# Check deployment
npx wrangler tail
```

**Your server is live at**:
```
https://my-mcp-server.YOUR_ACCOUNT.workers.dev/sse
```

### Connect Claude Desktop

**~/.config/claude/claude_desktop_config.json** (Linux/Mac):
```json
{
  "mcpServers": {
    "my-mcp": {
      "url": "https://my-mcp-server.your-account.workers.dev/sse"
    }
  }
}
```

**%APPDATA%/Claude/claude_desktop_config.json** (Windows)

**With OAuth**:
```json
{
  "mcpServers": {
    "my-mcp": {
      "url": "https://my-mcp-oauth.your-account.workers.dev/sse",
      "auth": {
        "type": "oauth",
        "authorizationUrl": "https://my-mcp-oauth.your-account.workers.dev/authorize",
        "tokenUrl": "https://my-mcp-oauth.your-account.workers.dev/token"
      }
    }
  }
}
```

Restart Claude Desktop after config changes.

---

## Common Patterns

### API Proxy MCP Server

**Use case**: Wrap external API with MCP tools

**Pattern**:
```typescript
this.server.tool(
  "search_wikipedia",
  "Search Wikipedia for a topic",
  { query: z.string() },
  async ({ query }) => {
    const response = await fetch(
      `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(query)}`
    );
    const data = await response.json();

    return {
      content: [{
        type: "text",
        text: data.extract
      }]
    };
  }
);
```

### Database-Backed Tools

**Use case**: Query D1, KV, or external databases

**Pattern**:
```typescript
this.server.tool(
  "get_user",
  "Get user details from database",
  { userId: z.string() },
  async ({ userId }) => {
    // Query Durable Objects storage
    const user = await this.state.storage.get<User>(`user:${userId}`);

    // Or query D1 database
    const result = await env.DB.prepare(
      "SELECT * FROM users WHERE id = ?"
    ).bind(userId).first();

    return {
      content: [{
        type: "text",
        text: JSON.stringify(user || result, null, 2)
      }]
    };
  }
);
```

### Multi-Tool Coordination

**Use case**: Tools that call other tools

**Pattern**:
```typescript
// Store result from first tool
await this.state.storage.put("last_search", result);

// Second tool reads it
const lastSearch = await this.state.storage.get("last_search");
```

### Caching Strategy

**Use case**: Cache expensive API calls

**Pattern**:
```typescript
this.server.tool(
  "get_weather",
  "Get weather (cached 5 minutes)",
  { city: z.string() },
  async ({ city }) => {
    const cacheKey = `weather:${city}`;
    const cached = await this.state.storage.get<CachedWeather>(cacheKey);

    // Check cache freshness
    if (cached && Date.now() - cached.timestamp < 5 * 60 * 1000) {
      return {
        content: [{ type: "text", text: cached.data }]
      };
    }

    // Fetch fresh data
    const weather = await fetchWeatherAPI(city);

    // Cache it
    await this.state.storage.put(cacheKey, {
      data: weather,
      timestamp: Date.now()
    });

    return {
      content: [{ type: "text", text: weather }]
    };
  }
);
```

### Rate Limiting with Durable Objects

**Use case**: Prevent abuse, respect upstream rate limits

**Pattern**:
```typescript
async rateLimit(key: string, maxRequests: number, windowMs: number): Promise<boolean> {
  const now = Date.now();
  const requests = await this.state.storage.get<number[]>(`ratelimit:${key}`) || [];

  // Remove old requests outside window
  const recentRequests = requests.filter(ts => now - ts < windowMs);

  if (recentRequests.length >= maxRequests) {
    return false; // Rate limited
  }

  // Add this request
  recentRequests.push(now);
  await this.state.storage.put(`ratelimit:${key}`, recentRequests);

  return true; // Allowed
}

// Use in tool
if (!await this.rateLimit(userId, 10, 60 * 1000)) {
  return {
    content: [{ type: "text", text: "Rate limit exceeded (10 requests/minute)" }],
    isError: true
  };
}
```

---

## 15 Known Errors (With Solutions)

### 1. McpAgent Class Not Exported

**Error**: `TypeError: Cannot read properties of undefined (reading 'serve')`

**Cause**: Forgot to export McpAgent class

**Solution**:
```typescript
export class MyMCP extends McpAgent { ... }  // ✅ Must export
export default { fetch() { ... } }
```

---

### 2. Transport Mismatch

**Error**: `Connection failed: Unexpected response format`

**Cause**: Client expects `/sse` but server only serves `/mcp`

**Solution**: Serve both transports (see Transport Methods section)

---

### 3. OAuth Redirect URI Mismatch

**Error**: `OAuth error: redirect_uri does not match`

**Cause**: Client configured with localhost, but deployed to workers.dev

**Solution**: Update claude_desktop_config.json after deployment

---

### 4. WebSocket Hibernation State Loss

**Error**: Tool calls fail after reconnect with "state not found"

**Cause**: In-memory state cleared on hibernation

**Solution**: Use `this.state.storage` instead of instance properties

---

### 5. Durable Objects Binding Missing

**Error**: `Error: Cannot read properties of undefined (reading 'idFromName')`

**Cause**: Forgot DO binding in wrangler.jsonc

**Solution**: Add binding (see Stateful MCP Servers section)

---

### 6. Migration Not Defined

**Error**: `Error: Durable Object class MyMCP has no migration defined`

**Cause**: First DO deployment requires migration

**Solution**:
```jsonc
{
  "migrations": [
    { "tag": "v1", "new_classes": ["MyMCP"] }
  ]
}
```

---

### 7. CORS Errors on Remote MCP

**Error**: `Access to fetch at '...' blocked by CORS policy`

**Cause**: MCP server doesn't return CORS headers

**Solution**: Use OAuthProvider (handles CORS) or add headers manually

---

### 8. Client Configuration Format Error

**Error**: Claude Desktop doesn't recognize server

**Cause**: Wrong JSON format in claude_desktop_config.json

**Solution**: See "Connect Claude Desktop" section for correct format

---

### 9. serializeAttachment() Not Used

**Error**: WebSocket metadata lost on hibernation wake

**Cause**: Not using serializeAttachment()

**Solution**: See WebSocket Hibernation section

---

### 10. OAuth Consent Screen Disabled

**Security risk**: Users don't see permissions

**Cause**: `allowConsentScreen: false` in production

**Solution**: Always set `allowConsentScreen: true` in production

---

### 11. JWT Signing Key Missing

**Error**: `Error: JWT_SIGNING_KEY environment variable not set`

**Cause**: OAuth Provider requires signing key

**Solution**:
```bash
openssl rand -base64 32
# Add to wrangler.jsonc vars
```

---

### 12. Environment Variables Not Configured

**Error**: `env.MY_VAR is undefined`

**Cause**: Variables in `.dev.vars` but not in wrangler.jsonc

**Solution**: Add to `"vars"` section in wrangler.jsonc

---

### 13. Tool Schema Validation Error

**Error**: `ZodError: Invalid input type`

**Cause**: Client sends string, schema expects number

**Solution**: Use Zod transforms:
```typescript
z.string().transform(val => parseInt(val, 10))
```

---

### 14. Multiple Transport Endpoints Conflicting

**Error**: `/sse` returns 404 after adding `/mcp`

**Cause**: Incorrect path matching

**Solution**: Use `startsWith()` or exact matches

---

### 15. Local Testing with Miniflare Limitations

**Error**: OAuth flow fails in local dev

**Cause**: Miniflare doesn't support all DO features

**Solution**: Use `npx wrangler dev --remote` for full DO support

---

## Configuration Reference

### Complete wrangler.jsonc (All Features)

```jsonc
{
  "name": "my-mcp-server",
  "main": "src/index.ts",
  "compatibility_date": "2025-01-01",
  "compatibility_flags": ["nodejs_compat"],
  "account_id": "YOUR_ACCOUNT_ID",

  "vars": {
    "ENVIRONMENT": "production",
    "GITHUB_CLIENT_ID": "optional-pre-configured-id"
  },

  "kv_namespaces": [
    {
      "binding": "OAUTH_KV",
      "id": "YOUR_KV_ID",
      "preview_id": "YOUR_PREVIEW_KV_ID"
    }
  ],

  "durable_objects": {
    "bindings": [
      {
        "name": "MY_MCP",
        "class_name": "MyMCP",
        "script_name": "my-mcp-server"
      }
    ]
  },

  "migrations": [
    { "tag": "v1", "new_classes": ["MyMCP"] }
  ],

  "node_compat": true
}
```

### Complete package.json

See `templates/package.json`

### Complete claude_desktop_config.json

See `templates/claude_desktop_config.json`

---

## Additional Resources

### Official Documentation
- **Cloudflare Agents**: https://developers.cloudflare.com/agents/
- **MCP Specification**: https://modelcontextprotocol.io/
- **workers-oauth-provider**: https://github.com/cloudflare/workers-oauth-provider
- **Durable Objects**: https://developers.cloudflare.com/durable-objects/

### Official Examples
- **Cloudflare AI Demos**: https://github.com/cloudflare/ai/tree/main/demos
- **13 MCP Servers**: https://blog.cloudflare.com/thirteen-new-mcp-servers-from-cloudflare/

### Tools
- **MCP Inspector**: https://github.com/modelcontextprotocol/inspector
- **Wrangler CLI**: https://developers.cloudflare.com/workers/wrangler/

---

## When NOT to Use This Skill

**Don't use this skill when**:
- Building Python MCP servers (use `fastmcp` skill instead)
- Building local-only MCP servers (use `typescript-mcp` skill)
- You need non-Cloudflare hosting (AWS Lambda, GCP, etc.)
- You're working with Claude.ai web interface skills (different from MCP)

**Use this skill specifically for**: TypeScript + Cloudflare Workers + Remote MCP

---

## Version Information

- **@modelcontextprotocol/sdk**: 1.21.0
- **@cloudflare/workers-oauth-provider**: 0.0.13
- **agents (Cloudflare Agents SDK)**: 0.2.20
- **Last Verified**: 2025-11-04

**Production tested**: Based on Cloudflare's official MCP servers (mcp-server-cloudflare, workers-mcp)

---

## Token Efficiency

**Without this skill**:
- Research scattered docs: ~10k tokens
- Debug 15 errors: ~30k tokens
- **Total: ~40k tokens**

**With this skill**:
- Read skill: ~4k tokens
- Copy templates: ~1k tokens
- **Total: ~5k tokens**

**Savings: ~87%** (40k → 5k tokens)

**Errors prevented**: 15 (100% prevention rate)

---

**Questions? Check**:
- `references/authentication.md` - Auth patterns comparison
- `references/transport.md` - SSE vs HTTP technical details
- `references/oauth-providers.md` - GitHub, Google, Azure setup
- `references/common-issues.md` - Error troubleshooting deep-dives
- `references/official-examples.md` - Curated links to Cloudflare examples

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/jackspace) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-11 -->

