# Nexus MCP-Bruecke Verbindet externe KI-Clients (**Claude Code**, **Claude Desktop**, **Cursor**) mit der Chrome/Brave-Extension. Der Client sieht ganz normale MCP-Tools, die Extension fuehrt sie im echten Browser aus — inklusive echter Maus- und Tastatureingaben ueber CDP. ``` Claude Code / Desktop / Cursor │ MCP (Streamable HTTP, POST /mcp) ▼ mcp-bridge/server.js ← 127.0.0.1, Token-Pflicht │ WebSocket (Protokoll aus src/shared/protocol.ts) ▼ Browser-Extension → Chrome/Brave ``` Keine Abhaengigkeiten, kein Build. Node 18+, ESM, WebSocket selbst implementiert. --- ## 1. Starten ```bash cd extension/mcp-bridge npm start # oder: node server.js ``` Beim Start erscheint: ``` ──────────────────────────────────────────────────────────────────────── Nexus Browser Pilot — MCP-Bruecke 1.0.0 ──────────────────────────────────────────────────────────────────────── MCP-Endpunkt http://127.0.0.1:9224/mcp (nur POST) Health http://127.0.0.1:9224/health Extension-WS ws://127.0.0.1:9224/?token= Token 0cc23cd9-ad03-4993-b96e-de650663259a (neu erzeugt, gespeichert in …/mcp-bridge/.token) Tools 31 aus C:\Users\…\MB\tools\schema … ``` Der Server lauscht **ausschliesslich auf 127.0.0.1**. Er ist von aussen nicht erreichbar, auch nicht aus dem lokalen Netz. --- ## 2. Token Ein Token ist **Pflicht** — es gibt keinen Modus ohne Authentifizierung. Reihenfolge beim Start: 1. `NEXUS_MCP_TOKEN` aus der Umgebung, falls gesetzt. 2. Sonst der Inhalt von `mcp-bridge/.token`, falls vorhanden (Neustarts machen damit eingerichtete Clients nicht ungueltig). 3. Sonst wird ein neues per `crypto.randomUUID()` erzeugt, nach `.token` geschrieben (Rechte 0600) und auf der Konsole ausgegeben. Jeder HTTP-Request **und** der WebSocket-Upgrade muessen es tragen: | Weg | Beispiel | | --- | --- | | Header | `Authorization: Bearer ` | | Header | `X-Mcp-Token: ` | | Query | `?token=` | Ohne gueltiges Token: **HTTP 401**, der WebSocket-Upgrade wird abgelehnt. Eigenes Token vorgeben: ```bash # bash NEXUS_MCP_TOKEN=$(uuidgen) node server.js ``` ```powershell # PowerShell $env:NEXUS_MCP_TOKEN = [guid]::NewGuid().ToString(); node server.js ``` > `.token` gehoert nicht ins Repository. Falls das Verzeichnis versioniert wird, bitte > `mcp-bridge/.token` in die `.gitignore` aufnehmen. --- ## 3. Extension einrichten In der Extension unter **Einstellungen** muss die **brokerUrl** auf diese Bruecke zeigen, also auf **`ws://127.0.0.1:9224`** — und weil auch der WebSocket ein Token verlangt, mit angehaengtem Token: ``` ws://127.0.0.1:9224/?token= ``` Der Pfad ist beliebig; `ws://127.0.0.1:9224/ext/ws?token=` funktioniert genauso. Entscheidend sind Host, Port und das Token. Die exakte Zeile zum Kopieren steht beim Serverstart in der Konsole. Ausserdem sollte `driveMode` auf **`broker`** stehen (Standard). Danach verbindet sich die Extension selbstaendig und schickt ihr `hello`; im Log der Bruecke erscheint: ``` [07:20:26] Extension-Socket verbunden (Origin: chrome-extension://…). Warte auf hello … [07:20:26] hello von Extension v1.0.0 (Session-Secret vorhanden). Bereit. ``` Pruefen laesst sich das jederzeit ueber: ```bash curl "http://127.0.0.1:9224/health?token=" # {"status":"ok","extension":"verbunden","tools":31,...} ``` --- ## 4. Einbindung in Claude Code ```bash claude mcp add --transport http browser http://127.0.0.1:9224/mcp \ --header "Authorization: Bearer " ``` Pruefen: ```bash claude mcp list ``` Danach stehen in der Sitzung die Tools `browser_navigate`, `browser_screenshot`, `browser_read_page`, `browser_computer` usw. zur Verfuegung. Nur fuer das aktuelle Projekt statt global: `--scope project` ergaenzen. Entfernen: `claude mcp remove browser`. --- ## 5. Einbindung in Claude Desktop Konfigurationsdatei: * Windows: `%APPDATA%\Claude\claude_desktop_config.json` * macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` ```json { "mcpServers": { "browser": { "type": "http", "url": "http://127.0.0.1:9224/mcp", "headers": { "Authorization": "Bearer " } } } } ``` Claude Desktop danach vollstaendig beenden und neu starten (Tray-Icon schliessen, nicht nur das Fenster). Sollte die verwendete Version keine HTTP-Server unterstuetzen, hilft die Bruecke ueber `mcp-remote` weiter: ```json { "mcpServers": { "browser": { "command": "npx", "args": ["-y", "mcp-remote", "http://127.0.0.1:9224/mcp", "--header", "Authorization: Bearer "] } } } ``` ## Einbindung in Cursor `.cursor/mcp.json` im Projekt oder `~/.cursor/mcp.json` global: ```json { "mcpServers": { "browser": { "url": "http://127.0.0.1:9224/mcp", "headers": { "Authorization": "Bearer " } } } } ``` --- ## 6. Umgebungsvariablen | Variable | Standard | Bedeutung | | --- | --- | --- | | `MCP_PORT` | `9224` | Port auf 127.0.0.1. | | `NEXUS_MCP_TOKEN` | — | Token vorgeben. Sonst `.token` bzw. neu erzeugt. | | `MCP_TOOL_TIMEOUT_MS` | `60000` | Wartezeit auf ein `tool_result` der Extension. | | `MCP_SCHEMA_DIR` | `../../tools/schema` | Verzeichnis mit den `browser_*.json`-Schemata. | | `MCP_ALLOWED_ORIGIN` | — | Kommagetrennte Liste. Nur dann gibt es ueberhaupt CORS-Header. | | `MCP_STRICT_SESSION` | `0` | Bei `1` muss jeder Folgeaufruf eine bekannte `Mcp-Session-Id` mitschicken. | --- ## 7. Tools Die Tool-Liste ist **nicht** im Server hartkodiert. Bei jedem `tools/list` werden alle `*.json` aus `tools/schema/` gelesen und ueber `name`, `description` und `inputSchema` auf MCP-Tools abgebildet (`requires_confirmation: true` wird zu `annotations.destructiveHint`). Ein neues Schema in diesem Verzeichnis steht also ohne Neustart zur Verfuegung; der Pfad laesst sich mit `MCP_SCHEMA_DIR` umbiegen. **Screenshots** kommen als echter MCP-Bildinhalt zurueck, nicht als Text: ```json {"content":[ {"type":"image","data":"","mimeType":"image/jpeg"}, {"type":"text","text":"{ \"image\": \"[Bild #1 als image-Content angehaengt, 118 KB]\", \"width\": 1400, … }"} ]} ``` Das gilt auch fuer Bilder tief in einem `browser_batch`-Ergebnis. Der base64-Block landet nie im Text und nie im Log. --- ## 8. HTTP-Endpunkte | Methode | Pfad | Verhalten | | --- | --- | --- | | `POST` | `/mcp` | MCP ueber Streamable HTTP: `initialize`, `notifications/initialized`, `tools/list`, `tools/call`, `ping`. | | `GET` | `/mcp` | **405** mit Erklaerung. Bewusst kein SSE-Stream, der offen bleibt und nie antwortet. | | `DELETE` | `/mcp` | Beendet die Session aus dem Header `Mcp-Session-Id`. | | `GET` | `/health` | Status, Extension-Verbindung, Tool-Anzahl. | | `GET` | `/` (Upgrade) | WebSocket fuer die Extension. | `initialize` erzeugt eine Session-Id, liefert sie im Antwort-Header `Mcp-Session-Id` und prueft sie bei Folgeaufrufen: eine unbekannte oder abgelaufene Id ergibt **404**, worauf konforme Clients neu initialisieren. --- ## 9. Sicherheit Diese Bruecke gibt einem externen Prozess die Kontrolle ueber den angemeldeten Browser des Nutzers — mit allen Sessions, Cookies und gespeicherten Passwoertern. Entsprechend eng ist sie zugeschnitten: * **Nur 127.0.0.1.** Kein Schalter, um auf `0.0.0.0` zu binden. * **Token ist Pflicht**, fuer HTTP *und* fuer den WebSocket-Upgrade. Verglichen wird in konstanter Zeit ueber SHA-256-Digests. * **Kein CORS-Wildcard.** `Access-Control-Allow-Origin` wird nur gesetzt, wenn `MCP_ALLOWED_ORIGIN` ausdruecklich einen passenden Origin nennt. Ohne die Variable gibt es gar keinen CORS-Header, und Preflights werden mit 403 beantwortet. * **WS-Upgrade nur mit passendem Origin:** entweder gar keiner (lokale Prozesse) oder `chrome-extension://…`. Eine besuchte Webseite sendet immer ihren eigenen Origin und kann ihn nicht faelschen — damit ist der Weg "boesartige Seite oeffnet `ws://127.0.0.1:9224`" versperrt. * **Genau eine Extension-Verbindung.** Meldet sich eine zweite, wird die bestehende *sauber* mit Close-Code 1012 geschlossen und protokolliert, offene Aufrufe werden mit klarer Meldung abgebrochen — nichts wird stillschweigend ersetzt. * **Kein `hello`, keine Tools.** Eine Verbindung, die sich nicht innerhalb von 15 Sekunden mit `hello` meldet, wird getrennt und bekommt nie einen `tool_call` zu sehen. * **Keine Geheimnisse im Log.** Weder das Session-Secret der Extension noch base64-Bilddaten werden ausgegeben. > Der Vorlage `agent_workspace/browser-pilot/server/index.js` fehlte genau das: `/execute` > war ungeschuetzt und `Access-Control-Allow-Origin: *` erlaubte jeder besuchten Webseite, > den Browser per `fetch()` fernzusteuern. Diese Kombination ist hier bewusst nicht > nachgebaut — es gibt kein `/execute` und kein Wildcard. Das Token ist ein Passwort. Es gehoert nicht in ein Repository, nicht in ein Ticket und nicht in eine Bildschirmaufnahme. --- ## 10. Fehlersuche | Symptom | Ursache und Abhilfe | | --- | --- | | `401 unauthorized` | Token fehlt oder ist falsch. Aktuelles Token: Konsole beim Start oder `mcp-bridge/.token`. | | `Keine Extension verbunden` | Browser offen? Extension aktiv? brokerUrl inkl. `?token=` gesetzt? `driveMode` auf `broker`? | | `Port 9224 ist bereits belegt` | Die Bruecke laeuft schon, oder ein alter Prozess haengt. Anderen Port ueber `MCP_PORT`. | | `Zeitueberschreitung nach 60000 ms` | Die Extension antwortet nicht — offene Risiko-Rueckfrage im Side Panel, blockierender Browser-Dialog oder kein aktiver Tab. Notfalls `MCP_TOOL_TIMEOUT_MS` erhoehen. | | `Unbekannte oder abgelaufene Mcp-Session-Id` | Der Client haelt eine Session von einem frueheren Serverlauf. Client neu verbinden lassen. | | `tools/list` liefert 0 Tools | `tools/schema/` nicht gefunden. `MCP_SCHEMA_DIR` setzen; `/health` zeigt den benutzten Pfad. | | `GET /mcp` gibt 405 | So gewollt. MCP laeuft ueber `POST /mcp`. | Schnelltest ohne Client: ```bash TOKEN=$(cat .token) curl -s -X POST http://127.0.0.1:9224/mcp \ -H "content-type: application/json" \ -H "authorization: Bearer $TOKEN" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | head -c 400 ```