effidevFlutter · Cloudflare edge · Cloud cost optimization
English

Building Model Context Protocol (MCP) Servers on Cloudflare Workers: Hands-on Edge AI Agent Tools

Building Model Context Protocol (MCP) Servers on Cloudflare Workers

Anthropic’s Model Context Protocol (MCP) has rapidly emerged as the open standard for connecting AI agent host environments (such as Claude Desktop, Cursor, and Zed) with external tools and data sources. However, most MCP tutorials focus on local Node.js/Python processes using stdio transport, which hits scalability barriers when attempting to share agent tools across a engineering team or deploy production agent services.

This article details how to deploy Server-Sent Events (SSE) transport-based MCP servers to Cloudflare Workers edge infrastructure. We cover everything from leveraging Cloudflare’s 300+ global datacenters for sub-10ms latency tools with zero cold starts to connecting D1 databases and securing endpoints with OAuth/Bearer tokens.

Key Takeaways

  • stdio vs. SSE/HTTP: While stdio is convenient for local CLI testing, HTTP-based Server-Sent Events (SSE) transport is mandatory for multi-user collaboration and production deployments.
  • Cloudflare Workers Edge Advantage: V8 isolate architecture eliminates cold starts and delivers MCP tool execution in 10-30ms globally.
  • D1 & KV Integration: Access Cloudflare D1 (SQLite) and KV key-value stores directly inside Worker handlers so AI agents can query edge databases on demand.
  • Edge Security Patterns: Internet-accessible MCP servers must use Bearer token middleware and Cloudflare Access to prevent unauthorized tool invocation.

MCP Transport Architecture: From stdio to SSE

The MCP specification utilizes JSON-RPC 2.0 for message exchanges between client hosts and tool servers.

Feature stdio Transport SSE / HTTP Transport
Target Environment Local Development (Client spawns child process) Production / Edge Serverless
Communication Standard Input / Output streams HTTP GET (SSE) + POST (JSON-RPC)
Network Scope Restricted to local machine Accessible globally over HTTPS
Scalability Single user Multi-user with auth controls
Ideal Backend Node.js, Python CLI Cloudflare Workers, AWS Lambda

On Cloudflare Workers, running persistent child processes is impossible, making HTTP SSE (Server-Sent Events) the ideal transport. Clients issue a GET request to /sse to maintain a real-time event stream and receive a session endpoint URL (e.g. /message?sessionId=...) for submitting JSON-RPC requests via POST.


Implementing an Edge MCP Server on Cloudflare Workers

The official @modelcontextprotocol/sdk fully supports Web Standard APIs (Request, Response, ReadableStream), allowing it to run seamlessly on Cloudflare Workers.

1. Project Setup

npm init cloudflare@latest mcp-edge-worker -- --type=workers-types-typescript
cd mcp-edge-worker
npm install @modelcontextprotocol/sdk hono

Add the D1 database binding in wrangler.toml:

name = "mcp-edge-worker"
main = "src/index.ts"
compatibility_date = "2026-07-01"

[[d1_databases]]
binding = "DB"
database_name = "production-db"
database_id = "your-d1-database-id"

2. Writing SSE Transport and MCP Handlers

Here is a full TypeScript implementation using Hono and @modelcontextprotocol/sdk:

import { Hono } from "hono";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";

type Bindings = {
  DB: D1Database;
  AUTH_TOKEN: string;
};

const app = new Hono<{ Bindings: Bindings }>();
const activeTransports = new Map<string, SSEServerTransport>();

function createMcpServer(db: D1Database) {
  const server = new Server(
    { name: "effidev-edge-mcp", version: "1.0.0" },
    { capabilities: { tools: {} } }
  );

  server.setRequestHandler(ListToolsRequestSchema, async () => {
    return {
      tools: [
        {
          name: "query_user_stats",
          description: "Query user payment and activity statistics from Cloudflare D1 edge DB.",
          inputSchema: {
            type: "object",
            properties: {
              startDate: { type: "string", description: "YYYY-MM-DD" },
              limit: { type: "number", description: "Max rows to return (default 10)" },
            },
            required: ["startDate"],
          },
        },
      ],
    };
  });

  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    if (request.params.name === "query_user_stats") {
      const startDate = String(request.params.arguments?.startDate);
      const limit = Number(request.params.arguments?.limit ?? 10);

      const { results } = await db
        .prepare("SELECT user_id, status, amount FROM transactions WHERE created_at >= ? LIMIT ?")
        .bind(startDate, limit)
        .all();

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify({ success: true, count: results.length, data: results }),
          },
        ],
      };
    }

    throw new Error(`Unknown tool: ${request.params.name}`);
  });

  return server;
}

// Bearer token authentication middleware
app.use("*", async (c, next) => {
  const authHeader = c.req.header("Authorization");
  if (!authHeader || authHeader !== `Bearer ${c.env.AUTH_TOKEN}`) {
    return c.text("Unauthorized MCP Request", 401);
  }
  await next();
});

// SSE connection endpoint (GET /sse)
app.get("/sse", async (c) => {
  const sessionId = crypto.randomUUID();
  const { readable, writable } = new TransformStream();
  const writer = writable.getWriter();

  const transport = new SSEServerTransport(`/message?sessionId=${sessionId}`, {
    write: async (message) => {
      const data = `data: ${JSON.stringify(message)}\n\n`;
      await writer.write(new TextEncoder().encode(data));
    },
    close: async () => {
      await writer.close();
      activeTransports.delete(sessionId);
    },
  });

  activeTransports.set(sessionId, transport);
  const server = createMcpServer(c.env.DB);
  server.connect(transport);

  return new Response(readable, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      "Connection": "keep-alive",
    },
  });
});

// JSON-RPC message endpoint (POST /message)
app.post("/message", async (c) => {
  const sessionId = c.req.query("sessionId");
  if (!sessionId || !activeTransports.has(sessionId)) {
    return c.text("Session not found", 404);
  }

  const transport = activeTransports.get(sessionId)!;
  const body = await c.req.json();
  await transport.handlePostMessage(c.req.raw, c.res.raw, body);
  return c.text("Accepted", 202);
});

export default app;

Client Integration: Claude Desktop & Cursor

Assuming your Worker URL is https://mcp.effidev.workers.dev:

{
  "mcpServers": {
    "effidev-edge-tools": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/inspector",
        "--sse",
        "https://mcp.effidev.workers.dev/sse",
        "--header",
        "Authorization: Bearer YOUR_SECURE_TOKEN"
      ]
    }
  }
}

Performance & Cost Metrics

Benchmark Node.js (AWS EC2 t4g.small) Cloudflare Workers Paid
Monthly Fixed Cost $14.60 $5.00 (Includes 10M requests)
TTFB (Latency) 120ms - 280ms 12ms - 35ms (Global Edge)
Cold Start None 0ms (V8 Isolate)
Max Concurrent SSE Memory bound (~2,000) Uncapped Edge Scaling

Conclusion

Deploying MCP servers to Cloudflare Workers elevates AI agent tooling from isolated local scripts into production-ready, globally distributed enterprise infrastructure. Combine Cloudflare D1, KV, and Vectorize with the MCP standard for ultra-fast, serverless agent tool execution.