Drive any browser tab from your AI coding agent โ through the browser you already have open. Your logins, your cookies, your extensions. No fresh Playwright instance. No CDP debug port. No re-auth dance. Just the browser already on your desk.
# 1. clone + build $ git clone https://github.com/arikw/rx-browser-bridge-mcp $ cd rx-browser-bridge-mcp && npm install && npm -w mcp run build # 2. boot the relay (npm, docker compose, or podman compose) $ npm -w relay run dev # 3. load extension/ unpacked into Chrome / Edge / Brave # 4. register the MCP user-scope (so it loads in every CC session) $ claude mcp add browser-bridge -s user -- \ node --env-file-if-exists=$PWD/.env $PWD/mcp/dist/server.js
Snippet shows the shape โ see the full install guide for secrets, extension config, and registering with other MCP clients.
Three pieces. Self-hosted relay, MCP server, MV3 extension.
A small Node HTTP+WS server. The MCP POSTs commands to it; the browser extension pulls them off via a persistent WebSocket. Two static bearer tokens โ POSTER (writes) and PULLER (reads) โ so a leak of one doesn't grant the other. Sqlite audit log on every action.
A standard stdio MCP server. Loaded by any MCP-aware coding agent โ Claude Code via .mcp.json, Cursor / Cline / Zed / Continue / Goose / Windsurf via their own config. Exposes list_browsers, list_tabs, screenshot, navigate, new_tab, click, fill, query, evaluate.
Manifest v3 extension that maintains a persistent WS to the relay. Each command runs against the active tab via chrome.scripting.executeScript + chrome.tabs.captureVisibleTab. Destructive actions trigger a Chrome notification with Allow / Deny โ default-deny after 10s.
The extension pulls โ it doesn't open a port. Nothing in your browser is reachable from outside. The relay can stay bound to 127.0.0.1 and still drive a browser that's behind any NAT.
When a fresh headless browser isn't what you want.
It's the browser you're already logged into. GitHub, AWS console, internal dashboards, Gmail, anything behind SSO. Your agent works with the same sessions you do โ no re-auth, no cookie copy, no service-account workaround.
Nothing in your browser needs to listen on a port. The extension pulls work from the relay over an outbound WS. Works behind NAT, works on machines you'd rather not poke holes in, works without restarting Chrome with debug flags.
Office Chrome, home Brave, a VM's Edge โ all register against the same relay. Address them by id (target: "office") or by tag (tag: "reddit", first match wins). One agent session, many surfaces.
Pragmatic surface. The full pipe wired end-to-end, nothing you don't need.
list_browsers, list_tabs, screenshot, navigate, new_tab, click, fill, query, evaluate. Enough to inspect, interact, and report โ the agent takes it from there. screenshot takes fullPage:true to scroll-and-stitch the whole page; evaluate runs arbitrary JS (via the userScripts world, so it works even on CSP-locked sites like GitHub); pass tabId from list_tabs to drive a specific tab across multiple windows.
POSTER for the MCP (writes), PULLER for the extension (reads). A leak of one doesn't grant the other direction. Static bearer tokens โ simple, auditable, rotatable from a single env file.
Click selectors that look like submit / post / send, or fill on password fields, trigger a Chrome notification with Allow / Deny buttons. Default-deny after 10s. No double-checking your auth-token field after the fact.
The toolbar icon flashes red while the bridge is handling a request (โฅ5s, held while requests keep coming) โ an out-of-band cue that something is driving your browser right now. A separate badge tracks the relay link: green connected, amber connecting, orange retrying, red disconnected, grey disabled.
Every register / cmd / result lands in relay/data/relay.sqlite with a timestamp. Grep, join, replay โ your call. No external service, no telemetry leaving the box.
Toolbar icon โ status (connected? which relay?), the last 50 audit entries, and a single big disconnect button. The extension stops accepting work the moment you flip it.
Puppeteer boots the relay, launches Chromium with the unpacked extension, seeds storage via the SW worker target, exercises the full pipe โ screenshot, navigate, query โ in under five seconds.
Relay ships with a Dockerfile + compose file that works under both docker compose and podman compose. Single shared .env at repo root drives the container, the host-mode relay, and the MCP โ same config, same secrets.
Each browser self-registers with an id + tags on connect, and pings a heartbeat every 20s. list_browsers tells the agent who's online; target / tag picks where a command runs.
Relay, MCP, and docker-compose all read the same .env at repo root via --env-file-if-exists. Edit once, applies everywhere โ no parallel copies of POSTER_TOKEN to keep in sync.
Other MCP browser-automation servers spawn their own browser. That's the right call for clean-room scraping. It's the wrong call when the browser is the workspace โ the already-logged-in tabs you've got open right now.
| Capability |
rx-browser-bridge
(this project)
|
chrome-devtools-mcp | playwright-mcp | puppeteer-mcp |
|---|---|---|---|---|
| Drives your already-open browser | yes โ via MV3 extension | only if launched with --remote-debugging-port | no โ spawns its own | no โ spawns its own |
| Reuses your real session cookies / SSO | yes (it is your profile) | yes if you point at your profile dir | requires storageState export | requires manual cookie injection |
| Browser exposes no inbound port | yes โ outbound WS only | no โ CDP debug port required | n/a (own process) | n/a (own process) |
| Multi-browser, tag-routed targeting | yes (id + tag, first-match-online) | single attach | single instance | single instance |
| Built-in destructive-action confirmation | heuristic + chrome.notifications | no | no | no |
| Audit log of every action | sqlite, on by default | no | no | no |
| Cross-machine deploy (browser elsewhere) | extension pulls from any reachable relay | requires CDP port reachable from CC | in-process only | in-process only |
| Full DevTools surface (network, perf, console) | no โ extension APIs only | yes | yes | yes |
| Accessibility-tree based selectors | no โ CSS selectors only | yes | yes | partial |
Use Playwright / Puppeteer MCP when you want a clean-room browser per task โ scraping, screenshot regression, no shared state. Use chrome-devtools-mcp when you need the full DevTools surface (network panel, performance traces, console). Use rx-browser-bridge when the value is in this exact browser, this exact session: dashboards behind SSO, a CI tab you already opened, an admin UI that took a security key to log into.
They compose. Nothing stops you from loading all three MCPs โ the right tool surface for the job is the one the agent picks per call.
No, not for the single-machine default. The relay binds to 127.0.0.1; the MCP talks to it over loopback; the extension also connects to 127.0.0.1 if Chrome is on the same box. Cross-machine deploys โ driving an office browser from a home laptop, say โ need the relay reachable by both ends, which you'd front with TLS via Caddy, Cloudflare Tunnel, or Tailscale Funnel.
Two layers. (1) A heuristic in the extension flags clicks against submit/post/send selectors and fills against password fields as destructive โ those trigger a Chrome notification with Allow / Deny buttons that default-deny after 10s. (2) The kill-switch in the popup disconnects the extension entirely. Neither is a substitute for not running this against a session that matters. The current defaults favor speed of iteration over hardened policy; per-domain trust toggles are on the roadmap.
CDP needs Chrome started with --remote-debugging-port, an open TCP port on your machine, and locks the browser into a mode where any local process can drive it. An extension is just a long-running script the user explicitly loaded. It pulls work over an outbound WS โ no port, no flag, no full-takeover surface. The trade-off is the extension API is narrower than CDP โ no network interception, no performance traces.
Not yet โ every command runs against the active tab in the active window. URL-matching and window-selector targeting is on the roadmap; the registration protocol already passes current_url so the routing layer has what it needs.
A Firefox MV3 manifest variant is on the roadmap. The bulk of extension/background.js uses standard chrome.* APIs Firefox supports via its browser.* namespace, so the diff should be small.
It's a vanilla @modelcontextprotocol/sdk stdio server, so any MCP-aware client works. Claude Code reads .mcp.json (shipped in the repo). Cursor uses ~/.cursor/mcp.json. Cline uses its settings panel. Zed, Continue, Goose, Windsurf โ all support stdio MCPs. Point yours at node mcp/dist/server.js with POSTER_TOKEN + RELAY_URL in the env. A mcp/.claude-plugin/plugin.json is also in the tree for Claude Code marketplace packaging.
The relay is plain HTTP + WS. For loopback that's fine. For cross-network deploys, front it with a TLS-terminating reverse proxy โ Caddy, Cloudflare Tunnel, Tailscale Funnel. A first-party TLS sidecar service in the compose file is on the roadmap.