Chrome Bridge
# Chrome Bridge
> By [frsorrentino](https://github.com/frsorrentino) · npm `chrome-bridge-mcp` · not affiliated with other projects named "chrome-bridge".
    [](https://chromewebstore.google.com/detail/chrome-bridge-for-claude/bioknpaeahidbelaljjohjofiloeodmb) [](https://mcprush.com/to/chrome-bridge-mcp?ref=badge)
**Claude Code drives the Chrome you are already using — signed in, with your
extensions and your cookies. 69 tools: navigation, forms, DOM inspection,
network mocking, visual regression, audits, performance traces and the
WebMCP tools a page declares. Self-hosted, local-only, no paid plan, works on
ChromeOS.**
Measured against the official Claude in Chrome extension on the same tasks
(paired runs, 17/10/2026, Claude Code 2.1.302, n=5 per task, medians):
| Task | Chrome Bridge | Claude in Chrome | Fewer turns | Lower cost |
|---|---|---|---:|---:|
| Fill a form | 3 turns · $0.043 | 15 turns · $0.236 | **5.0×** | **5.5×** |
| Find one row in a 1,500-row table | 3 turns · $0.039 | 6 turns · $0.154 | **2.0×** | **3.9×** |
| Debug a broken page | 6 turns · $0.091 | 31 turns · $0.559 | **5.2×** | **6.1×** |
15 correct answers out of 15, against 14. Method, every run and the caveats in
[docs/EFFICIENCY.md](docs/EFFICIENCY.md).
## Quickstart
1. In a Claude Code session:
```
/plugin install chrome-bridge --marketplace frsorrentino/chrome-bridge
```
From a shell, the same: `claude plugin install chrome-bridge --marketplace frsorrentino/chrome-bridge`.
2. Add the extension from the [Chrome Web
Store](https://chromewebstore.google.com/detail/chrome-bridge-for-claude/bioknpaeahidbelaljjohjofiloeodmb).
3. Restart Claude Code.
Then ask for something like *"open localhost:3000, run an accessibility audit
and find the Sign Up button"*: Claude Code calls `navigate`, `audit` and
`find_text`. Because `navigate` already returns element refs,
`click(ref="n1")` follows with no discovery turn in between.
The plugin registers the MCP server from npm together with the recipes skill.
| Requirement | For | Notes |
|---|---|---|
| Node.js 18+ | the MCP server | |
| Chrome 135+ | the extension | `execute_js` needs **Allow user scripts** in `chrome://extensions` → Chrome Bridge → Details; on Chrome 135-137, Developer Mode instead |
| Claude Code 2.1.275+ in a session, 2.1.292+ from a shell | the one-command install | Older versions: see below |
| Python 3.8+ (optional) | the plugin's error log hooks | As `python3`, `python` or `py` (the Microsoft Store `python3` alias is skipped). Without it they stay silent and the tools work the same |
<details>
<summary>Older Claude Code, from source, Windows, one path only</summary>
**Older Claude Code.** Add the marketplace first:
`/plugin marketplace add frsorrentino/chrome-bridge`, then
`/plugin install chrome-bridge@chrome-bridge` (from a shell:
`claude plugin marketplace add frsorrentino/chrome-bridge`, then
`claude plugin install chrome-bridge@chrome-bridge`).
**From source:**
```bash
git clone https://github.com/frsorrentino/chrome-bridge.git
cd chrome-bridge && ./install.sh
```
Then open `chrome://extensions`, enable **Developer mode**, click **Load
unpacked**, select the `extension/` folder, and restart Claude Code.
`install.sh` registers the MCP server with `--scope user`; by hand:
`claude mcp add --scope user chrome-bridge node /path/to/server/index.js`.
On ChromeOS/Crostini use the Web Store extension instead: an unpacked one is
dropped on every reboot, because the container isn't mounted when Chrome
starts.
**Windows.** `install.sh` does not run from PowerShell or cmd: use the plugin
with the Web Store extension, or run `install.sh` from Git Bash. The plugin's
error log hooks run in Git Bash, as Claude Code runs every hook. For launch
mode see `CHROME_BRIDGE_BROWSER` below: Google Chrome cannot load the extension
there, Microsoft Edge can.
**One path only.** The plugin and `install.sh` would register the same server
twice, and so would a plugin added from the [Anthropic
directory](https://claude.ai/directory) on claude.ai (it reaches Claude Code as
`chrome-bridge@synced`) next to a marketplace install. In claude.ai chat the
plugin loads only the recipes skill: the tools need Claude Code, or Cowork
running on your computer, where the local MCP server can start. Clients that
read [Agent Plugins 1.0](https://agent-plugins.org) get the same from
`plugin.json` + `mcp.json`.
</details>

## Why it takes fewer turns
- **Refs, not screenshots.** `navigate` returns the page's interactive
elements as short refs (`n1`, `n2`…), so the next call is the click, not a
screenshot and a coordinate guess.
- **One call for a whole form.** `fill_form` fills N fields and submits in the
same call.
- **Filtering on the server.** `extract_table` with `where` and `columns`
returns the matching rows of a 1,500-row table, not the table.
- **Debugging starts with the answer.** `navigate` reports the first console
error mapped to its source line and the failed requests with their status;
`get_css_styles` names the rule behind a style and flags undefined CSS
variables.
- **Deferred loading.** 22 stay in context; the rest of the 46 in the
default set wait behind Claude Code's tool search (2.1.285+), and loading
one mid-session does not invalidate the prompt cache. The other 23, in seven
optional groups, switch on mid-session with `get_status({enable: [...]})`,
no restart.
## Your browser, your login
No blank profile, no debugging port, no cloud browser. The extension runs in
your Chrome, so every site you are signed into is already open to the agent:
webmail, the hosting panel, a back office, a staging site behind basic auth.
Tabs the agent opens are marked as its own and closed when the session ends;
your tabs stay yours. Several Claude Code sessions share the same browser: the
second one becomes a relay on the first one's bridge.
**WebMCP.** When a page declares its own tools through
[WebMCP](https://github.com/webmachinelearning/webmcp), `navigate` and
`get_interactives` say so in one line, `webmcp_tools` lists them and
`webmcp_call` runs one, with a timeout. Needs the WebMCP flag or origin trial
in Chrome; `get_status` reports whether it is on.
**Limits you can set.** `upload_file` refuses keys and credentials
(`~/.ssh`, `.env*`, `*.pem`, …) and `--read-root` fences it to one directory;
`--write-root` fences every path the model writes; `--no-js` removes arbitrary
JavaScript. The agent never types credentials: for a login, a 2FA code or a
choice, `handoff` shows a banner in the page and waits for you.
## Compared
| | Chrome Bridge | Claude in Chrome | Chrome DevTools MCP | Playwright MCP |
|---|---|---|---|---|
| **ChromeOS / Crostini** | **Yes** (real host) | No | Container only | Container only |
| **Tools** | **69** (46 default, 22 always loaded) | 22 | 29 default (56 with flags) | 24 core (71 total) |
| **Requires paid plan** | **No** | Yes (Pro+) | No | No |
| **WebMCP (tools the page declares)** | **Yes** | No | Yes | Yes |
| **Network mocking** | **Yes** (stub/headers) | No | No | Yes |
| **Visual regression** | **Yes** (`screenshot_diff`) | No | No | No |
| **Audits (a11y/SEO/sec)** | **Yes** (one call, report on disk) | No | Lighthouse | No |
| **Performance trace** | **Yes** (`perf_trace`, `lighthouse`) | No | Yes | Playwright trace only |
| **Screen recording** | **Yes** (`screencast`, mp4/webm) | Yes (GIF) | Partial | Yes (video) |
| **Heap snapshot** | **Yes** (launch mode) | No | Yes | No |
| **Breakpoints** | No | No | **Yes** | No |
| **Headless / CI** | **Yes** | No | Yes | Yes |
Codex for Chrome (OpenAI, May 2026) sits in the Claude in Chrome column: an
official extension with the `debugger` permission, macOS and Windows only,
the ChatGPT app required. Claude in Chrome documents Linux desktop since
September 2026; ChromeOS and WSL stay out. Competitor tool counts measured on
2026-09-01 and 2026-09-11 (`docs/analisi-2026-09-11-concorrenti.md`); WebMCP, recording and
tracing rows checked in their documentation on 2026-10-28.

## Using it
The **skill** in [`skills/chrome-bridge/SKILL.md`](skills/chrome-bridge/SKILL.md)
is what makes the tools discoverable: recipes with the phrase that triggers
each one ("verify the email arrives", "test the checkout with a test card",
"which plugin slows the page", "what fires before consent"), the tool sequence,
and the zero-token CLI commands the model would otherwise never see.
`install.sh` copies it to `~/.claude/skills/chrome-bridge`; do the same by
hand for other clients.
Beyond the MCP tools, two lanes keep work away from the model entirely.

**CLI** — batch operations, piped through `grep` or `jq` before anything reaches
the context:
```bash
chrome-bridge navigate --url https://example.com
chrome-bridge read_console --level error | head -20
chrome-bridge assert --selector "#success" --text "Done"
chrome-bridge replay --file ./recordings/login.jsonl
```
**Launch mode** — a dedicated Chromium instance with an ephemeral profile, for
isolated sessions or CI:
```bash
node server/index.js --launch --headless
```
It looks for Chromium, Microsoft Edge, Brave and last Google Chrome in their
standard paths on Linux, macOS and Windows; `CHROME_BRIDGE_BROWSER` picks
another binary. Google Chrome 137+ ignores `--load-extension`, so the
extension never connects in it: use Chromium, Edge, Brave or Chrome for
Testing.
Pair it with `session_record` + `replay` for smoke tests with no model in the
loop. In launch mode `execute_js` falls back to `new Function` when the
user-script toggle isn't available.
## Tools
69 in total. `core` (46 tools: every tool used in 101 real sessions, plus the
two WebMCP tools) loads by default; the other 23 sit in seven optional caps,
switched on with `get_status({enable: ["visual"]})` or at startup with `--caps`.

| Group | N | What's in it |
|---|---|---|
| Core & Navigation | 13 | tabs, windows, `navigate`, `screenshot`, `tile_windows` |
| Interaction | 11 | `click`, `fill_form`, `upload_file`, dialogs, clipboard |
| DOM & Inspection | 12 | `read_page`, `extract`, `extract_table`, `query_dom`, `get_css_styles`, `watch_dom` |
| Debugging & Network | 8 | `execute_js`, console, network log, mocking, `track_events` |
| WebMCP | 2 | `webmcp_tools`, `webmcp_call`: the tools a page declares |
| Visual & Responsive | 5 | `screenshot_diff`, viewport and zoom, media emulation |
| Audits | 2 | `audit` (a11y, keyboard, SEO, security, links, vitals, css, resources, cache in one call), `cookie_audit` |
| State, Storage & Files | 10 | storage, fixtures, MHTML, `capture_downloads` (exports saved with no "Save as" dialog), recording, `assert` |
| Motion & Performance | 6 | `animations`, `frames` (long frames, CLS, INP), `perf_trace`, `screencast`, `lighthouse` and `heap_snapshot` (launch mode) |
Every tool, with the notes that matter: [docs/TOOLS.md](docs/TOOLS.md).
## How it works

```
Claude Code <--stdio--> MCP Server <--WebSocket :8765--> Chrome Extension
(server/) (extension/, MV3)
```
The Node.js server handles the protocol and tool logic; the MV3 extension
executes commands through Chrome APIs. User scripts (`execute_js`) run via
`chrome.userScripts.execute()`.
## Configuration and security
<details>
<summary>Environment variables, each with a matching CLI flag</summary>
| Variable | Default | Notes |
|---|---|---|
| `CHROME_BRIDGE_PORT` | `8765` | |
| `CHROME_BRIDGE_HOST` / `--host` | `127.0.0.1` | `0.0.0.0` **only** where the browser lives outside the container (ChromeOS/Crostini port-forward) — and only with a token |
| `CHROME_BRIDGE_TOKEN` | unset | Required on both `ext_init` and `relay_init`. Strongly recommended whenever the bind isn't loopback |
| `CHROME_BRIDGE_CAPS` / `--caps` | `core` | `core`, `audits`, `visual`, `network`, `storage`, `dom`, `files`, `all`. Optional caps can also be switched on at runtime with `get_status({enable})` |
| `CHROME_BRIDGE_ALWAYS_LOAD` / `--always-load` | 22 most used tools | Tools kept in context when the client loads the server whole; the others carry `_meta['anthropic/alwaysLoad'] = false` and wait behind tool search (Claude Code 2.1.285+). `all` loads every tool; a comma list picks them |
| `CHROME_BRIDGE_NO_JS` / `--no-js` | unset | No arbitrary JavaScript in the page: `execute_js` and `modify_dom` leave the schema, `wait_for(condition=function)` and `javascript:`/`data:` URLs are refused. `get_status` reports `js_evaluation` |
| `CHROME_BRIDGE_READ_ROOT` / `--read-root` | unset | Where `upload_file` may read from. Unset: any file except keys and credentials (`~/.ssh`, `~/.aws`, `~/.gnupg`, `.env*`, `id_*`, `*.pem`, `*.key`, …), symlinks resolved first. Set: only files under this directory, whatever their name — the way to upload a certificate key on purpose |
| `CHROME_BRIDGE_BROWSER` | unset | Launch mode only: the browser binary, e.g. `C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe`. Unset: the first of Chromium, Edge, Brave, Google Chrome found in the standard paths (on Linux Chromium, then Google Chrome) |
| `CHROME_BRIDGE_OBSERVE` | unset | `off` stops the local error log below. The plugin sets `hook`: its hooks keep the log and the server stays out of it |
| `CHROME_BRIDGE_WRITE_ROOT` / `--write-root` | unset | Every path the model chooses (`save_to`, `output_path`, exports) must be under this directory, checked before the browser does any work; the server's own state under `~/.config/chrome-bridge` stays writable. The CLI is your shell and is not restricted |
</details>
The bridge binds loopback, accepts extension connections only from a
`chrome-extension://` origin, and — when a token is set — requires it on both
handshakes. Without one, any local process could act as a relay and reach
`execute_js` inside your authenticated browser session. Secondary MCP instances
connect via loopback and are acknowledged with `relay_init_ok`, so a foreign
process holding the port fails fast instead of timing out per command.
**What is *not* protected:** page content reaches the model unfiltered, so a
hostile page's text is untrusted input. `get_storage`, `session_fixture`, HAR
exports and screenshots are **not** redacted and may carry cookies, tokens or
personal data. Don't point the automation at pages holding secrets you wouldn't
paste into a chat.
### Threat model
chrome-bridge gives an AI agent your real, logged-in browser. Scanners that
rate MCP servers by capability flag it, correctly, for code execution
(`execute_js`), requests that carry your cookies (`http_request`) and reading
private page data (`get_storage`, `get_tabs`). Those are the product, not bugs.
What matters is who can reach them and what a hostile page can make the agent
do with them.
- **Launch mode also opens a DevTools port** on 127.0.0.1 (chosen by the
system), for `lighthouse` and `heap_snapshot`. CDP has no authentication:
any local process can drive that dedicated browser while it runs. Its profile
is temporary and holds nothing of yours; your own Chrome never gets a port.
- **The debugger bar is the signal.** `click`/`press_key` with `trusted`,
`emulate_media via: "debugger"`, `perf_trace` and `screencast` attach
`chrome.debugger` to one tab only for that work; Chrome shows its "started
debugging this browser" bar meanwhile, and cancelling it ends the work.
- **The real risk is prompt injection.** A page can ask the agent to run
JavaScript, send a request with your cookies, submit a form or upload a file.
Treat every page you automate as untrusted input, and review what the agent
proposes on sites that hold money or credentials.
- **Reduce the surface:** `--caps core` (the default) exposes the smallest set,
and see [Limits you can set](#your-browser-your-login). For sessions that
matter, automate in a separate Chrome profile.
- **`--no-js` is not a sandbox:** `inject_css` stays, and CSS attribute
selectors with `url()` can leak attribute values to a remote host.
- **What leaves your machine** is what the browser sends on the agent's behalf.
chrome-bridge itself has no telemetry; the error log below stays local unless
you approve an issue.
### Local error log
When one of its own tools fails, chrome-bridge adds a line to a **local** file,
`${XDG_STATE_HOME:-~/.local/state}/claude-observe/chrome-bridge.jsonl` (mode
0600, in a 0700 folder), in the [claude-observe](https://github.com/frsorrentino/claude-observe)
format (the plugin's copy is in `observe/`). A `PostToolUseFailure` hook writes
it, or the server when used without the plugin. A repeated error is one line
with a count.
- **Kept:** the tool name, the error text after scrubbing (your home folder
becomes `~`; emails, URL queries, secrets and typed values are removed), the
names of the parameters and the length of text values, the chrome-bridge
version, the OS, the name of the working folder and of the Claude Code config
folder. With the plugin, also the Claude Code and model versions, the
session id and the names of the last three tools called.
- **Never kept:** parameter values — no URLs, selectors, typed text or page
content.
- **Nothing leaves your computer** unless you say yes. With the plugin, once
a few errors have piled up (or one has waited a few days, or one is marked
as a defect), a line at the end of a turn tells you so, and Claude may offer
to send them as one GitHub issue: it shows the anonymized text first and
sends only what you choose. `/chrome-bridge:observe send` does the same on
demand; `list`, `mark` and `add` read and triage the log. The server alone
never sends or offers anything.
- **Security observations** (a read or write outside the perimeter, a secret
exposed, unwanted code execution, data leaving the computer) never enter a
public issue: `/chrome-bridge:observe send --security` prepares a private
vulnerability report for the maintainers, see [SECURITY.md](SECURITY.md).
- **Turn it off:** `{"enabled": false}` in `~/.config/claude-observe/config.json`
(every plugin that uses claude-observe), or `CHROME_BRIDGE_OBSERVE=off` for
the server. Keep the log but stop the offers: `{"propose": false}`.
## Troubleshooting
<details>
<summary>Symptoms and fixes</summary>
| Symptom | Cause / fix |
|---|---|
| `Chrome extension not connected` | Extension disabled, or its port differs from the server's. The error names the actual host/port; check them in the popup (⚙). |
| Port 8765 already in use | Expected: a second MCP session becomes a **relay** and shares the one bridge. Set `CHROME_BRIDGE_PORT` for a separate one. |
| `Port N is held by a process that is not chrome-bridge` | Something else owns the port. Free it or change `CHROME_BRIDGE_PORT`. |
| `execute_js` fails | Enable **Allow user scripts** in `chrome://extensions` → Chrome Bridge → Details (Chrome 138+; on 135-137 enable Developer Mode). |
| `read_console` returns `note=Instrumentation not loaded` | The page was opened before the extension, "Capture console & metrics" is off, or the page isn't injectable (`chrome://`). Reload it. |
| Screenshot times out, or `image readback failed` | A minimized or fully covered window stops producing frames; captures fail after 10 s. Bring the window forward. |
| `wait_for`/`scroll` return `page_hidden: true`, pages stop updating | Same cause: Chrome does not render a hidden page and slows its timers (after a few minutes, to one wake-up per minute). `get_page_info` reports `visibility`. Bring the window on screen, or `create_tab` with `new_window` and bounds. |
| `screenshot presets` says `NOT APPLIED` | Presets resize the window; they do not emulate a phone (no device pixel ratio, UA or touch). The window manager enforces a minimum width, and no window exceeds the screen (e.g. at most a 1536×686 viewport on a 1536×864 ChromeOS screen). For phone emulation use `emulate_media` with `device` (via debugger), or a headless browser for larger viewports. |
| `type_text` returns `mismatch: true` | The field rejected the value. Events from an extension have `isTrusted: false`, and some widgets (date pickers, search boxes with tokenizers) discard them. Try `mode: 'keys'`, then the site's own controls (the calendar buttons), or `handoff`. |
| `[media removed: request limit]` instead of a screenshot | Written by the MCP client, not by the bridge: too many images in one request. Use `save_to`, or `element_screenshot` with `region` and a small `scale`. |
| Commands work, then stop | The MV3 service worker restarted and in-memory state (network log, diff baselines, HTTP auth) was reset. Re-run the monitoring call. |
| Extension dropped on every ChromeOS reboot | Install from the Web Store instead of Load unpacked. |
| Tool missing from the list | It's in an opt-in group. Check `get_status` → `caps_available`, then set `CHROME_BRIDGE_CAPS=all`. |
| `claude mcp get` shows no command, arguments or environment | Expected with the plugin: since Claude Code 2.1.285 it hides them for stdio servers that plugins provide (variable names stay). `get_status` reports the server and extension versions, mode, host, port and caps; `/mcp` shows the connection. |
| Popup says «In attesa: un altro browser è collegato», `get_status` lists `refused_browsers` | Two browsers with the extension reach the same port. The one already connected keeps the bridge; the other retries every 15 s and takes over when the first disconnects. `get_status` → `browser` names the connected one. Before this, the last one to connect took over silently. |
</details>
## Documentation
- [docs/TOOLS.md](docs/TOOLS.md) — all 69 tools, by group
- [docs/CAPABILITIES.md](docs/CAPABILITIES.md) — what the bridge gets past and what it does not, one dated state per wall
- [docs/EFFICIENCY.md](docs/EFFICIENCY.md) — the benchmark and the design behind it
- [docs/misure/2026-10-token-cache-vs-claude-in-chrome.md](docs/misure/2026-10-token-cache-vs-claude-in-chrome.md) — the 17/10 paired set and the prompt-cache breakdown (Italian)
- [docs/PERFORMANCE.md](docs/PERFORMANCE.md) — latency per tool on the real path, `npm run bench:latency`
- [bench/RESULTS.md](bench/RESULTS.md) — raw runs and inclusion rule
- [CHANGELOG.md](CHANGELOG.md)
## Tests
`npm test` (Chrome-free, ~22s) · `npm run test:e2e` (needs Chrome and a
connected extension; with a bridge already on 8765:
`CHROME_BRIDGE_PORT=8799 node test/test-devtools.js --launch`, which opens
its own Chromium with `extension/`) · `npm run measure` (schema cost) ·
`npm run bench:latency` (milliseconds per tool, launches its own Chromium,
writes `docs/PERFORMANCE.md`).
## Related projects
- [agent-webbridge](https://github.com/jeet-dhandha/agent-webbridge) — drives several Chrome profiles at once, with parallel tabs
- [awb-recipes](https://github.com/jeet-dhandha/awb-recipes) — community-maintained, dated per-site recipes for automating logged-in websites
## License
MIT
TDQS
Scored across 69 tools
The descriptions are exhaustively cross-referenced, which helps, but the 69-tool set still has genuinely crowded areas: four screenshot variants, four read paths (read_page, extract, extract_table, query_dom, find_text), and monitor_network vs network_rules vs track_events vs read_console all need careful reading to separate. An agent can usually pick right thanks to the 'use X instead' notes, but the boundaries are not self-evident from names alone.
Most tools follow snake_case verb_noun (get_tabs, create_tab, set_storage, full_page_screenshot), but there is a large minority of bare verbs and noun-only names (navigate, extract, click, hover, scroll, assert, watch, audit, handoff, clipboard, animations, frames, lighthouse). The convention is readable but only loosely followed.
69 tools is far beyond a comfortable set for an agent, and the existence of an opt-in 'caps' mechanism (audits, visual, perf, network, storage, dom, files) confirms the surface was split because it is too large. Splitting into cap groups is the right mitigation, but as a flat tool list it is heavy.
Coverage is exceptionally thorough for a browser bridge: navigation, tabs/windows, DOM read/write, forms, dialogs, storage, network interception, performance/audit, screenshots, downloads, WebMCP, and human handoff. Almost no plausible browser-automation operation is missing.