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>
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:
NEXUS_MCP_TOKENaus der Umgebung, falls gesetzt.- Sonst der Inhalt von
mcp-bridge/.token, falls vorhanden (Neustarts machen damit eingerichtete Clients nicht ungueltig). - Sonst wird ein neues per
crypto.randomUUID()erzeugt, nach.tokengeschrieben (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
.tokengehoert nicht ins Repository. Falls das Verzeichnis versioniert wird, bittemcp-bridge/.tokenin die.gitignoreaufnehmen.
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.0zu 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-Originwird nur gesetzt, wennMCP_ALLOWED_ORIGINausdruecklich 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 oeffnetws://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 mithellomeldet, wird getrennt und bekommt nie einentool_callzu sehen. - Keine Geheimnisse im Log. Weder das Session-Secret der Extension noch base64-Bilddaten werden ausgegeben.
Der Vorlage
agent_workspace/browser-pilot/server/index.jsfehlte genau das:/executewar ungeschuetzt undAccess-Control-Allow-Origin: *erlaubte jeder besuchten Webseite, den Browser perfetch()fernzusteuern. Diese Kombination ist hier bewusst nicht nachgebaut — es gibt kein/executeund 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