chrome-bridge
chrome-bridge
Install as a Claude Code plugin
/plugin marketplace add bbauersjt/sjt-chrome-bridge
/plugin install chrome-bridge@sjt-chrome-bridgeThen two manual steps the plugin can't do for you: pip install -r requirements.txt,
and load the extension/ folder as an unpacked extension in Chrome
(chrome://extensions → Developer mode → Load unpacked). Details below.
A direct line from Claude Cowork into your authenticated Chrome session — no linked tab, no file relay, no tab-group sprawl. Cowork calls MCP verbs; a local relay forwards them over a localhost WebSocket to an MV3 extension, which acts inside the live page (reads it, injects JS, calls the backend with the page's own cookies/CSRF) and returns the result.
Cowork ──MCP stdio──► server.py (relay) ──localhost WS (token)──► extension ──► Axcess / Suralink pageThis is the generic transport. Site-specific JS (Axcess, Suralink) lives in the skill, not here.
Pieces
File | Role |
| MV3 extension. Holds the outbound WS, runs |
| Local stdio MCP server. Registers the |
| The WebSocket hub (background thread, request/response correlation). |
| Registers |
|
|
| Proves the server half with a mock extension — no Chrome/Claude needed. |
Verbs
chrome_bridge_status()— is the server up and the extension connected? Start here.chrome_list_tabs()— open tabs (id/url/title); use an id astarget.chrome_page_info(target="active")— url/title/readyState.chrome_fetch(url, method, headers, body, target="active")— runsfetch()inside the page, so it carries that page's cookies/auth/CSRF. The backend-call primitive.chrome_eval(code, target="active", world="MAIN")— arbitrary JS, JSON result. MAIN world is subject to the page's CSP (may block eval); preferchrome_fetchfor backend calls.
Setup (Windows)
Install deps —
pip install -r requirements.txtLoad the extension — Chrome →
chrome://extensions→ enable Developer mode → Load unpacked → select theextension/folder. The toolbar badge reads on once it connects to the server (which must be running — see step 4/5).Register the server —
powershell -ExecutionPolicy Bypass -File ".\register_server.ps1"Fully quit and reopen the Claude desktop app (not just close the window) so it loads the new MCP server and launches
server.py.Test — open an Axcess tab and log in, then in Cowork:
chrome_bridge_status→extension_connected: truechrome_list_tabs→ your tabschrome_page_info→ the Axcess page's url/titlechrome_fetchan Axcess backend endpoint → JSON, authenticated.
Verify the server logic anytime without Chrome: python selftest.py → SELFTEST OK.
Security model
Server binds 127.0.0.1 only and requires a shared token (
BRIDGE_TOKEN, must match inbridge_core.pyandextension/background.js).The relay is capability-by-omission: no filesystem/shell/code-exec verbs. Arbitrary JS (
chrome_eval) runs in the browser page, contained by Chrome's sandbox — never on the host.You stay logged in as yourself; the extension reuses your live session (correct for an attest tool).
Known limits / next
DEV token is hardcoded. Before sharing with coworkers, move it to an extension options page (per-user secret), not source.
Multiple instances self-organize. The desktop app and the Cowork runtime each spawn
server.py. The first to bind 8765 is the daemon (owns the extension); any other becomes a controller that proxies its calls through the daemon. So whichever process a call lands on, it reaches the one extension.chrome_bridge_statusshowsroleand (for the daemon) the controller count.MV3 keepalive. While connected, the worker pings every 20s so it isn't suspended (WebSocket activity resets Chrome's idle timer). A 30s alarm + on-demand connect cover cold starts, so a dropped socket self-heals within ~30s.
One extension at a time. The daemon tracks a single extension socket. If the extension is installed in multiple Chrome profiles, disable all but one (the daemon would otherwise bind to whichever connected last). Multi-profile routing is a future enhancement.
chrome_evalvs page CSP. If a page blocks eval, usechrome_fetchorworld="ISOLATED".Distribution. One-click handoff later = publish the extension (Web Store / enterprise CRX) + bundle
server.pyas a Cowork plugin (which registers the MCP server) + freeze Python to an exe so coworkers need no toolchain.Port is
8765(change in bothbridge_core.pyandbackground.jsif it collides).