Zum Inhalt springen
CASOON

MCP in der Praxis: Warum wir uns gegen Remote und für lokal entschieden haben

Was passiert, wenn ein MCP-Server auf Cloudflare Workers trifft – und warum stdio die bessere Antwort war

10 Minuten
MCP in der Praxis: Warum wir uns gegen Remote und für lokal entschieden haben
#MCP #Cloudflare Workers #npm #stdio
SerieMCP für Entwickler
Teil 7 von 8

Ausgangslage: Eine Registry für KI-Agenten

Webspire ist eine kuratierte Registry aus CSS-Effekten und UI-Patterns, die als Tailwind-kompatible Snippets bereitstehen. Jedes Snippet hat eine klare Struktur: HTML als Referenz, CSS Custom Properties für Konfiguration, automatisch generierte Astro- und Web-Component-Varianten. Kein Runtime-Overhead, keine Framework-Abhängigkeit.

Das Ziel: Webspire soll nicht nur für Menschen nutzbar sein, sondern auch für KI-Agenten. Über MCP sollen Tools wie Claude Code oder Cursor direkt auf die Registry zugreifen können – Snippets suchen, Empfehlungen bekommen, Code abrufen. Der Agent wird zum Komponentenberater.

Der MCP-Server stellt 7 Tools und 5 Resources bereit:

  • search_snippets – Volltextsuche mit Filtern nach Kategorie, Tags, Accessibility
  • recommend_snippet – Use Case beschreiben, passendes Snippet bekommen
  • get_snippet – Vollständigen CSS-Code samt Metadaten abrufen
  • list_categories – Alle Kategorien mit Anzahl
  • search_patterns / get_pattern – UI-Patterns nach Intent oder Family durchsuchen

Ein typischer Workflow: Du sagst deinem Agenten “Ich brauche einen glassmorphism Login-Card”, der Agent ruft recommend_snippet auf, bekommt glass/frosted zurück, holt den Code via get_snippet und passt ihn an deinen Kontext an. Getesteter Code statt halluziniertes CSS.

Der erste Ansatz: Cloudflare Worker

Die naheliegende Idee war, den MCP-Server als Cloudflare Worker zu deployen. Der Worker lief bereits als REST-API, also warum nicht einfach einen /mcp-Endpoint hinzufügen?

const server = new McpServer({
  name: 'webspire',
  version: '0.1.0',
});

const transport = new WebStandardStreamableHTTPServerTransport({
  sessionIdGenerator: undefined,
  enableJsonResponse: true,
});

await server.connect(transport);
return transport.handleRequest(request);

Stateless, jeder Request erstellt eine frische Server-Instanz, registriert Tools und Resources, verarbeitet die Anfrage. Sauber, funktional, deployed in Sekunden.

Und es hat funktioniert. Technisch.

Das Problem: Architektur-Mismatch

Was wir nicht bedacht hatten: MCP-Clients und Cloudflare Workers haben fundamental unterschiedliche Erwartungen.

MCP-Clients (wie mcp-remote, Claude Desktop, Cursor) erwarten persistente Verbindungen. Sie bauen eine Session auf und halten sie am Leben – mit regelmäßigen Health-Checks, Reconnect-Logik und Session-Management. Das ist das richtige Verhalten für ein Protokoll, das auf kontinuierliche Interaktion ausgelegt ist.

Cloudflare Workers sind von Haus aus stateless. Jeder Request ist isoliert, kein Session-State zwischen Requests. Das lässt sich ändern – mit Durable Objects, KV Storage oder D1 kann man Sessions persistent machen. Aber genau das wollten wir nicht: Der MCP-Server sollte bewusst stateless bleiben, weil die Daten read-only sind und kein Session-Management nötig ist. Nur passt dieses Modell nicht zu MCP-Clients, die persistente Verbindungen erwarten. Jeder Health-Check ist dadurch ein vollständiger Cold Start: Server erstellen, Tools registrieren, Transport aufbauen, Response senden.

Das Ergebnis: Ein einzelner MCP-Client generierte im Leerlauf 250.000 Requests pro Tag – für einen Client. Nicht weil er etwas tat – sondern weil er die Verbindung aufrechterhalten wollte, die es nicht gab.

Die Kostenrechnung

Cloudflare Workers sind großzügig: 100.000 Requests pro Tag kostenlos, danach 0,50 Dollar pro Million. Klingt nach wenig. Aber:

SzenarioRequests/TagKosten/Monat
1 Client idle250.000ca. 2,25 Dollar
10 Clients2.500.000ca. 37,50 Dollar
50 Clients12.500.000ca. 187 Dollar

Und das alles für: nichts. Kein einziger sinnvoller Request. Jeder dieser Requests erstellt einen MCP-Server, registriert 7 Tools und 5 Resources, baut einen Transport auf und antwortet mit “ja, ich lebe noch”. Das ist architektonisch falsch.

Alternativen auf Worker-Seite

Wir haben verschiedene Ansätze evaluiert:

AnsatzLöst das Problem?Trade-off
Rate LimitingTeilweiseClient pollt trotzdem, bekommt 429er
API Key / AuthNeinVerhindert Missbrauch, nicht das Polling
Durable ObjectsJaSession-State möglich, aber Extra-Kosten und Komplexität
SSE mit SessionsTeilweiseWorkers haben kein echtes Persistent-Streaming (30s Timeout)

Durable Objects wären die technisch sauberste Lösung gewesen – ein Durable Object pro Client, das den Session-State hält und Health-Checks ohne neuen Server-Aufbau beantwortet. Aber für ein Projekt mit statischen, read-only Daten ist das Over-Engineering.

Die eigentliche Frage

Am Ende war die Frage einfach: Braucht Webspire überhaupt einen Remote-MCP?

Die Antwort: Nein.

  • Die Registry ist statisch – sie ändert sich nur beim Deploy
  • Alle Daten sind read-only – kein User-State, keine Mutationen
  • Es gibt keine Echtzeit-Anforderung – Snippets ändern sich nicht zwischen Requests
  • Die Registry ist klein – rund 600 Zeilen JSON, kein Problem für ein lokales Paket

Die Lösung: npm-Paket mit stdio

Also haben wir @casoon/webspire-mcp als npm-Paket gebaut:

npx @casoon/webspire-mcp

Das Paket bundled die komplette Registry bei Build-Zeit. Kein Netzwerk-Call, kein Server, keine Kosten. Der MCP-Server läuft lokal über stdio – der natürliche Transport für Desktop-Tools wie Claude Code oder Cursor.

Konfiguration in Claude Code

{
  "mcpServers": {
    "webspire": {
      "command": "npx",
      "args": ["@casoon/webspire-mcp"]
    }
  }
}

Drei Zeilen. Danach hat der Agent Zugriff auf die komplette Registry.

Der Vergleich

Cloudflare Workernpm-Paket
TransportHTTP (stateless)stdio (persistent)
SessionKeine (Reconnect-Loop)Natürlich (Prozess lebt)
Kosten0,50 Dollar/Mio Requests0
Latenzca. 50ms (Netzwerk)ca. 1ms (lokal)
RegistryFetch + CacheBundled, instant
OfflineNeinJa
SetupURL konfigurierennpx ausführen

Die stdio-Verbindung löst das Session-Problem elegant: Der Prozess startet einmal, bleibt am Leben, und die Kommunikation läuft über stdin/stdout. Kein Polling, kein Reconnect, kein Overhead.

Wann Remote-MCP trotzdem richtig ist

Cloudflare Workers sind nicht das Problem. Die REST-API läuft weiterhin dort – für JSON-Endpoints und maschinelle Konsumenten, die klassische HTTP-Requests machen.

Das Problem war spezifisch das Zusammenspiel von MCP-Protokoll und stateless Workers. MCP ist für persistente Verbindungen designed, Workers für kurzlebige Request-Response-Zyklen. Beides hat seinen Platz – nur nicht zusammen, wenn der Client Health-Checks in Dauerschleife sendet.

Remote-MCP ist die richtige Wahl bei:

  • Dynamischen Daten – wenn sich der Inhalt zwischen Requests ändert
  • Multi-Tenant – wenn verschiedene Nutzer unterschiedliche Daten sehen
  • Authentifizierung – wenn der Zugriff kontrolliert werden muss
  • Zentrale Updates – wenn alle Clients sofort die neueste Version brauchen

Für eine statische Snippet-Registry trifft nichts davon zu.

Projektstruktur Webspire MCP

@casoon/webspire-mcp/ ├── src/ │ ├── index.ts # MCP Server Entry │ ├── tools/ │ │ ├── search.ts # search_snippets, recommend_snippet │ │ ├── get.ts # get_snippet, get_pattern │ │ └── list.ts # list_categories, list_pattern_families │ └── resources/ │ ├── categories.ts # webspire://categories │ └── patterns.ts # webspire://patterns ├── data/ │ └── registry.json # Bundled bei Build-Zeit └── package.json

Die Entscheidungsmatrix

Für alle, die vor einer ähnlichen Entscheidung stehen:

KriteriumLokal (stdio/npm)Remote (HTTP/SSE)
Daten ändern sich seltenBesserUnnötig
Daten sind nutzerspezifischUngeeignetBesser
Offline-Fähigkeit wichtigJaNein
Kosten minimierenJaSkaliert mit Nutzung
Einfaches Setupnpx reichtURL + Auth konfigurieren
Zentrale KontrolleNeinJa

Die Faustregel: Wenn deine Daten in ein npm-Paket passen und sich nicht zwischen Requests ändern, ist stdio fast immer die bessere Wahl. Remote-MCP löst Probleme, die du bei statischen Daten nicht hast.

Was ich daraus gelernt habe

  1. Die einfachste Lösung ist oft die richtige. Nicht jedes Problem braucht einen Server.
  2. stdio ist der native MCP-Transport – designed für genau diesen Use Case.
  3. Architektur-Entscheidungen kosten Geld. 250.000 sinnlose Requests pro Tag sind ein Warnsignal, kein Feature.
  4. Remote und lokal schließen sich nicht aus. Die REST-API bleibt, der MCP-Server wandert lokal. Jede Schnittstelle bekommt den Transport, der zu ihr passt.

Der MCP-Server läuft jetzt dort, wo er hingehört: auf deinem Rechner, neben deinem Editor, ohne Umweg über die Cloud.

Die vollständige Registry mit CSS-Snippets, UI-Patterns und Token-System gibt es auf der Webspire Landingpage.