effidevFlutter · Cloudflare-Edge · Cloud-Kostenoptimierung
Deutsch

Model Context Protocol (MCP) Server auf Cloudflare Workers erstellen: Edge AI Agent Tools Praxisguide

MCP Server auf Cloudflare Workers

Das Model Context Protocol (MCP) von Anthropic hat sich rasch als offener Standard für die Verbindung von KI-Agenten-Hosts (wie Claude Desktop, Cursor oder Zed) mit externen Tools und Datenquellen etabliert.

In diesem Artikel zeigen wir, wie Sie SSE-basierte MCP-Server auf Cloudflare Workers bereitstellen, um weltweite Latenzen unter 10ms ohne Cold Starts zu erreichen.

Wichtige Erkenntnisse

  • stdio vs. SSE/HTTP: Für lokale Tests ist stdio praktisch, für Mehrbenutzerumgebungen und Produktion ist SSE (Server-Sent Events) erforderlich.
  • Edge-Vorteile: V8 Isolates verhindern Cold Starts und antworten weltweit in 10-30ms.
  • D1 & KV Integration: Ermöglicht KI-Agenten die direkte Abfrage von Cloudflare D1 (SQLite) Datenbanken am Edge.

MCP Server auf Cloudflare Workers implementieren

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 () => ({
    tools: [
      {
        name: "query_user_stats",
        description: "Fragt Nutzungsstatistiken aus der D1 Edge-Datenbank ab.",
        inputSchema: {
          type: "object",
          properties: { startDate: { type: "string" }, limit: { type: "number" } },
          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, data: results }) }]
      };
    }
    throw new Error(`Unbekanntes Tool: ${request.params.name}`);
  });

  return server;
}

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) => {
      await writer.write(new TextEncoder().encode(`data: ${JSON.stringify(message)}\n\n`));
    },
    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" }
  });
});

app.post("/message", async (c) => {
  const sessionId = c.req.query("sessionId");
  if (!sessionId || !activeTransports.has(sessionId)) return c.text("Sitzung nicht gefunden", 404);
  const transport = activeTransports.get(sessionId)!;
  await transport.handlePostMessage(c.req.raw, c.res.raw, await c.req.json());
  return c.text("Accepted", 202);
});

export default app;

Fazit

Die Bereitstellung von MCP auf Cloudflare Workers verwandelt lokale Skripte in skalierbare, hochverfügbare KI-Infrastruktur für Unternehmen.