building-mcp-server-on-cloudflare

Build and deploy remote MCP servers on Cloudflare Workers with tools and OAuth authentication. Define tools using Zod-validated parameters and expose them via MCP protocol; supports text responses, external API calls, and database bindings Two deployment modes: public servers (no auth) and OAuth-protected servers (GitHub, Google, Auth0, and other providers) Test locally with MCP Inspector, deploy with Wrangler CLI, and connect clients via Claude Desktop or other MCP-compatible applications Access Cloudflare bindings (D1, KV, Durable Objects) directly within tool handlers for data persistence and state management

INSTALLATION
npx skills add https://github.com/cloudflare/skills --skill building-mcp-server-on-cloudflare
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

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

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card