Files
nexus-browser-pilot-extension/mcp-bridge/README.md
T
coreandClaude Opus 5 f943261c3c feat: Gemini bridge, browser extension hardening, shared memory
Nexus gateway (measured 2026-08-07, not assumed):
- Gemini speaks Google-GenAI (/v1beta/models/{id}:streamGenerateContent, header
  api-key), NOT the Azure-OpenAI path — that returned 404 "no Route matched" and
  was the cause of the reported failures. New agent/gemini_bridge.py translates
  Bedrock Converse <-> Gemini in both directions.
- Only gemini-3.6-flash is subscribed; 2.5-flash/2.5-pro/3.1-flash-lite give 403,
  every other name 404. Catalog corrected.
- Four Gemini rules, each previously an HTTP 400, now covered by tests:
  thought signatures are mandatory, they belong to the TURN (not the individual
  call), functionResponse turns must be homogeneous, arrays need `items`.
- Prompt caching is NOT available: cachePoint is accepted and ignored.

System prompt:
- Was an f-string; a code sample containing braces broke build_system_prompt at
  request time (CLI and web both 500, import stayed green). Now a plain template
  with __TOKEN__ placeholders. Regression guards in tests/test_system_prompt.py.

CLI:
- `agent resume` now prints the stored transcript. The history was always loaded
  into the model context, only the terminal stayed empty.

Memory (new, all three surfaces):
- agent/memory.py stores notes about the user in one local file, written
  atomically; memory.cnull.net remains an optional mirror that can never fail a
  write. Tools memory_save/search/forget, injected into the prompt with a budget.
  HTTP surface /api/memory for the extension.

Browser extension (agent/extension, first commit of the source):
- driveMode 'direct' talks to Nexus without the Python broker: Claude via
  Bedrock converse, GPT via Azure-OpenAI, Gemini via Google-GenAI.
- browser_type no longer guesses the focused element — that wrote whole mails
  into Outlook's subject line. Read-back now reports where the text actually
  landed, so a mis-target is visible instead of silent.
- aria-labelledby is resolved across all ids (it is a list); contenteditable is
  interactive and marked editable. Without this, subject and message body look
  identical to the model.
- Hard block against sending mail, independent of riskMode.
- Runs survive the panel: events are buffered and replayed by sequence number.
- Image input (paste, file, drag&drop), on-page glow/spotlight, memory tools.

Cost: fixed tokens per round 11434 -> 6540 (-43%) by trimming tool schemas,
dropping gateway docs from the browser prompt and sending site knowledge only
where it applies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:31:26 +02:00

10 KiB

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

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>

  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 <TOKEN>
Header X-Mcp-Token: <TOKEN>
Query ?token=<TOKEN>

Ohne gueltiges Token: HTTP 401, der WebSocket-Upgrade wird abgelehnt.

Eigenes Token vorgeben:

# bash
NEXUS_MCP_TOKEN=$(uuidgen) node server.js
# 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=<TOKEN>

Der Pfad ist beliebig; ws://127.0.0.1:9224/ext/ws?token=<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:

curl "http://127.0.0.1:9224/health?token=<TOKEN>"
# {"status":"ok","extension":"verbunden","tools":31,...}

4. Einbindung in Claude Code

claude mcp add --transport http browser http://127.0.0.1:9224/mcp \
  --header "Authorization: Bearer <TOKEN>"

Pruefen:

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
{
  "mcpServers": {
    "browser": {
      "type": "http",
      "url": "http://127.0.0.1:9224/mcp",
      "headers": {
        "Authorization": "Bearer <TOKEN>"
      }
    }
  }
}

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:

{
  "mcpServers": {
    "browser": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://127.0.0.1:9224/mcp",
               "--header", "Authorization: Bearer <TOKEN>"]
    }
  }
}

Einbindung in Cursor

.cursor/mcp.json im Projekt oder ~/.cursor/mcp.json global:

{
  "mcpServers": {
    "browser": {
      "url": "http://127.0.0.1:9224/mcp",
      "headers": { "Authorization": "Bearer <TOKEN>" }
    }
  }
}

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:

{"content":[
  {"type":"image","data":"<base64 ohne data:-Praefix>","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:

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