Skip to main content
Glama

Chrome Bridge

By frsorrentino · npm chrome-bridge-mcp · not affiliated with other projects named "chrome-bridge".

License: MIT Node 18+ Chrome 135+ Tests Chrome Web Store Chrome Bridge MCP on mcprush

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.

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.

  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

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:

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 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 get the same from plugin.json + mcp.json.

The same form filled in 3 turns instead of 15 — 5× fewer turns on the form, 3.9-6.1× lower cost on form, 1,500-row table and debugging (paired runs, 17/10/2026, n=5)

Related MCP server: neuron-inspector

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, 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.

Claude checks its own work: console errors, pixel diffs, network mocking and audits

Using it

The skill in 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.

Some jobs never touch the model: the CLI lane runs the same tools at zero tokens

CLI — batch operations, piped through grep or jq before anything reaches the context:

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:

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.

69 tools in nine groups, from clicking a button to tracing a page

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.

How it works

It drives the Chrome you are logged into, over a local WebSocket bridge

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

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

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. 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 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.

  • 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

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.

Documentation

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).

  • agent-webbridge — drives several Chrome profiles at once, with parallel tabs

  • awb-recipes — community-maintained, dated per-site recipes for automating logged-in websites

License

MIT

Available Tools

69 tools
animationsA

List the page animations: CSS animations, transitions, Web Animations and View Transitions, each with selector and ref, animated properties, duration, delay, easing (linear() curves included), iterations, play state, timeline (document, scroll, view). composited_estimate guesses whether only transform/opacity/filter move (an estimate, not the compositor's answer); fade_only marks opacity-only fades, what prefers-reduced-motion should leave. Without duration_ms: what runs now. With duration_ms: records every animation that starts in the window, optionally after an action (hover, click, scroll, key), since a 150 ms transition is over before a second call. Refuses on a hidden page. Pair with emulate_media reducedMotion to check the reduced version; how smoothly they run is frames.

Inputs: limit, scope, action, tab_id, +2 more.

assertA
Read-onlyIdempotent

Assert a page condition, polling until timeout: element exists/visible (optionally with count or containing text), text on page, tab url/title. Patterns: substring, or "/…/" for regex. Recorded flows replay it as a test. Unlike wait_for, which returns found: false, a failure is an error naming what did not match; success returns passed and waited_ms.

Inputs: url, text, count, state, +4 more.

auditA
Read-onlyIdempotent

One-call page audit, pick the kinds: accessibility, keyboard (tab order and focus issues), SEO, security headers, broken links, Core Web Vitals, unused CSS, resources (which plugin/theme/module/host slows the page), cache (is the CDN serving the new version). A summary line per kind; with save_to the full Markdown report with every finding. Read-only. For one deep look at load speed use perf_trace, for Lighthouse scores lighthouse.

Inputs: kinds, scope, tab_id, save_to, +1 more.

capture_downloadsA

Save files the page builds itself, with no browser download and no "Save as" dialog: blob and data URL links (a[download]), window.showSaveFilePicker writes, and downloads the page starts (cancelled at once, bytes fetched again with the browser cookies). arm with save_dir, then click Export/Download, then wait: it returns the saved paths. A file with a URL: http_request save_to instead. Names are cleaned, existing files never overwritten, size capped by max_mb.

Inputs: action (required), max_mb, tab_id, timeout, +1 more.

clickA

Click an element by CSS selector or by a ref (n1, n2…) from get_interactives or navigate. A real pointer sequence: it can submit, open a dialog or navigate — use wait_after. Not idempotent; a native confirm() blocks the bridge: handle_dialogs first. Returns page_changed (url, title and DOM deltas: nodes, text, open, expanded, checked, dialogs, focus): the effect, without a screenshot; when the click opens or reveals something, the refs of the visible interactives follow, no get_interactives needed.

Inputs: ref, count, force, button, +5 more.

clipboardA
Idempotent

Read or write the system clipboard (text). Activates the tab and focuses its window first. write overwrites whatever the user had copied, which is not recoverable. To fill a field use type_text instead of write and paste; read checks what a Copy button copied, and an empty clipboard returns text "".

Inputs: action (required), text, tab_id.

create_tabA

Open a new tab, optionally at a URL, and make it the implicit target of later commands in this session. Each call creates another tab: reuse a tab_id or navigate() to move an existing one instead of piling up tabs.

Inputs: top, url, left, width, +3 more.

dismiss_overlaysA
Idempotent

Dismiss cookie banners/modal overlays: OneTrust, Cookiebot, Usercentrics, then generic heuristic. Idempotent.

Inputs: tab_id.

drag_and_dropA

Drag an element onto another. html5 = DragEvent+DataTransfer; pointer = pointer/mouse events (sortable libraries). It scrolls the source into view and returns once the events are sent, without knowing whether the app accepted the drop (check with assert or query_dom); a missing element is an error.

Inputs: source_selector (required), target_selector (required), mode, tab_id, +1 more.

element_screenshotA
Read-onlyIdempotent

Screenshot cropped to one element or to a viewport region (PNG), optionally enlarged. Read-only. The cheapest image in the set, because it carries only the box you asked for — and with scale the way to read fine print: crop the box, zoom it, verify.

Inputs: scale, region, tab_id, selector.

emulate_mediaA
Idempotent

Make the page believe it runs elsewhere, until reset or reload: prefers-color-scheme, prefers-reduced-motion, print mode, navigator.userAgent/platform. user_agent changes only what page JS reads — the request header is network_rules modify_header. via "page" (default for these) only patches matchMedia and, for reduce, zeroes every animation: the site's own @media rules do not apply. via "debugger" emulates like DevTools, so the site's @media (prefers-reduced-motion) rules really apply — use it to check reduced motion with animations — and adds contrast, CPU throttling, network profiles, device viewport with DPR, touch (these imply via debugger). via debugger keeps Chrome's "started debugging this browser" bar until reset: true; in launch mode nobody sees it. Pair with viewport_resize to emulate a device in page mode.

Inputs: via, reset, touch, device, +8 more.

execute_jsA
Destructive

Run JavaScript in the page (MAIN world). Requires the extension's "Allow user scripts" toggle; errors explain setup if disabled. Use it when no dedicated tool fits: extract or extract_table read data, query_dom inspects elements, inject_css restyles, and none of them needs the toggle. A promise is awaited up to timeout. A cut result stays valid JSON and says truncated; save_to writes the whole result to disk.

Inputs: code (required), tab_id, save_to, timeout, +2 more.

extractA
Read-onlyIdempotent

Read repeated non-tabular structures — product cards, list items, search results — as one record per item: item_selector matches the repeating block, fields map output names to selectors relative to it. Read-only, deterministic, parsed server-side from the main-document HTML. Default output: total and shown, then one tab-separated row per item under the field names; a field with no match is empty (null in json).

Inputs: fields (required), item_selector (required), format, tab_id, +3 more.

extract_tableA
Read-onlyIdempotent

Read a real as JSON: thead cells become keys, each row an object. Only for tabular markup — where filters rows server-side, so one matching row out of 1500 costs a few hundred bytes instead of the whole document. row_count is the table total, match_count how many rows passed the filter. No table at index is an error saying how many the selector found; for cards and lists use extract.

Inputs: index, where, offset, tab_id, +4 more.

fill_formA

Batch fill form fields with React-compatible events. Handles input, select, checkbox, radio, and textarea. Fields and submit take a CSS selector or a ref (n1, n2…) from navigate/get_interactives. With submit_selector it also submits, so a repeated call submits twice — not safe to retry blindly; after a submit, after_submit carries url, title and the text the page gained (the confirmation or the errors). Each field reports value_after and mismatch; page_changed carries the DOM delta, and the refs of what appeared.

Inputs: fields (required), tab_id, frame_id, submit_ref, +2 more.

find_settingA
Idempotent

Where is a setting in an unknown admin panel: follows the panel's menu links (same origin) until a page contains the keyword. Returns the URL, the menu path (Settings › Media) and the matching text; when nothing matches, how many pages it tried. Navigates the tab and leaves it on the last page visited, at most max_pages.

Inputs: keyword (required), tab_id, max_pages, menu_selector.

find_textA
Read-onlyIdempotent

Find text on the page: parent selector, context, visibility, position per match. Attaches nearby interactive elements (with refs for click/type_text/hover) for the first visible match. Use it when you know the words on screen but not the selector (query_dom needs one); no match returns count 0.

Inputs: text (required), tab_id, max_results, case_sensitive.

framesA

Record the page for duration_ms and report how smooth it ran: main-thread frame rate and dropped frames, Long Animation Frames (duration, blocking time, render and style/layout start, the scripts responsible), layout shifts with the elements that moved (CLS for the window only), and the slowest interaction (INP for the window, split into input delay, processing, presentation; needs trusted input, synthetic events have no interaction id). Optionally runs an action (click, hover, scroll, key) right after recording starts. Animations that started are counted too. Compositor-only animations can stay smooth while the main thread drops frames. Refuses on a hidden page: rAF and observers stop there. For load speed use perf_trace; for the settings of each animation, animations.

Inputs: limit, action, tab_id, frame_id, +2 more.

full_page_screenshotA
Read-onlyIdempotent

Full-page capture by scrolling: stitched segments of ~2 viewports (≤1568px), or one image per viewport with stitch=false. Expensive: ~2.7k image tokens per segment, only max_segments returned, the rest via segment_offset. Reading the text you need is cheaper by an order of magnitude — prefer read_page, extract or find_text unless layout is the question.

Inputs: delay, stitch, tab_id, max_scrolls, +2 more.

get_css_stylesA
Read-onlyIdempotent

Why an element looks the way it does: for each CSS property the winning declaration — selector, stylesheet, rule position, !important, @layer, @media — and the ones it overrides, like the Styles panel of DevTools; query_dom gives the computed value, this gives who set it. Read-only, from the CSSOM: stylesheets of another origin without CORS are opaque and listed by href; user-agent styles, @container and @scope rules are not evaluated. properties narrows the output.

Inputs: selector (required), tab_id, frame_id, properties, +1 more.

get_framesA
Read-onlyIdempotent

List frames (main + iframes) with frameId, parent, URL — for the frame_id parameter of DOM tools.

Inputs: tab_id.

get_interactivesA
Read-onlyIdempotent

List actionable elements (buttons, links, inputs, [role], [onclick]) with ready-to-use CSS selector, label, position, flags. Prefer this over dumping HTML to discover selectors. Document order, cut at limit (count equal to limit: there may be more), each with a ref (n1, n2…) for click, type_text and hover. The full list also names the WebMCP tools the page declares, if any.

Inputs: limit, scope, since, format, +3 more.

get_page_infoA
Read-onlyIdempotent

Page metadata: title, url, visibility, doctype, charset, meta tags, scripts, stylesheets, links and forms. visibility=hidden means the window is minimized, covered or in the background: the page does not render (screenshots fail) and its timers slow down. dev.server names a dev server (vite, webpack-dev-server, next, nuxt) and dev.overlay carries the compiler message when an error overlay is open: read it before treating the page as valid. For the text use read_page, for specific elements query_dom, for the bridge itself get_status.

Inputs: tab_id, frame_id.

get_statusA
Idempotent

Status of the bridge itself, not of a page: call it when a tool you expect is missing or browser commands fail (connected=false: the extension is not reachable, extension_install gives its Chrome Web Store link). Also reports mode (primary/relay), host, port, server and extension versions, js_evaluation (false under --no-js), write_root, read_root, session_tab_id (the implicit tab), owned_tabs (created by this session), uptime_sec, webmcp (whether WebMCP is active in the session tab, and how to enable it). caps_active and caps_available: a missing tool lives in an inactive cap. enable is the only change it makes: it switches caps on for this session, no restart, and lists the tools added. Optional caps: audits (cookie_audit); visual (inject_css, measure_spacing, emulate_media, viewport_resize); perf (animations, frames, perf_trace, screencast, lighthouse, heap_snapshot); network (http_auth, set_geolocation, track_events); storage (get_storage, set_storage, session_fixture); dom (modify_dom, watch_dom, drag_and_drop); files (save_page, manage_downloads, session_record).

Inputs: enable.

get_storageA
Read-onlyIdempotent

Read localStorage, sessionStorage or cookies of the current origin (type=all for every one). Read-only. Cookies come with domain, path, expiry and the httpOnly/secure flags, so this also answers whether a session cookie is present — write them back with set_storage, or snapshot a logged-in state with session_fixture.

Inputs: type, tab_id.

get_tabsA
Read-onlyIdempotent

List every open tab with id, url, title, active flag and mine (created by this session). Find a tab_id when the implicit target is not the one you mean. include_windows adds position, size, state and type of each window — needed before moving or tiling. Session tabs gone since the last call come back once in closed_session_tabs with the reason (closed, window_closed, replaced, closed_by_bridge).

Inputs: include_windows.

handle_dialogsA
Idempotent

Auto-accept/dismiss future JS dialogs (alert/confirm/prompt), logging them. reset restores native dialogs and returns the log.

Inputs: action, tab_id, prompt_text.

handoffA
Idempotent

Hand the browser to the user for what only a person can do — 2FA, CAPTCHA, login, a choice — and wait: a banner in the page shows your message with Done/Cancel, the call returns on click or timeout, redirects included. pick_element: the user clicks an element and you get its selector. Never type credentials yourself. ask: the user types a reply in the banner ("which of the three?") and you get it as answer. pick_max: with pick_element, up to N elements; the user presses Done when finished.

Inputs: message (required), ask, tab_id, timeout, +3 more.

heap_snapshotA
Destructive

Take a JS heap snapshot of the tab (after a garbage collection) and summarize it: object count, total size, detached DOM nodes, the classes that take most memory. With compare_to (an earlier snapshot file): which classes grew, to find a leak — snapshot, repeat the suspect action a few times, snapshot again. The .heapsnapshot file opens in DevTools > Memory. Works only in launch mode (chrome-bridge --launch [--headless]): it needs the DevTools port of the dedicated browser, which the extension in your own Chrome cannot provide: Chrome refuses the HeapProfiler domain to extensions.

Inputs: top, tab_id, save_to, compare_to.

hoverA
Idempotent

Hover over an element, by CSS selector or ref. Default: synthetic mouseenter/mouseover/mousemove, which fire JS hover handlers but not CSS :hover. trusted moves the real mouse pointer (chrome.debugger), so CSS :hover rules and their transitions apply.

Inputs: ref, tab_id, trusted, frame_id, +1 more.

http_authA
Idempotent

Set/clear credentials for HTTP Basic/Digest auth dialogs (browser-wide, in-memory only).

Inputs: action (required), password, username.

http_requestA
Destructive

HTTP request sent from the browser, with the logged-in user's cookies: fetches what a server-side request gets a login page for (invoices, authenticated JSON, exports). Text bodies inline (capped by max_length); save_to writes the bytes — the way to read a PDF, since read_page returns nothing on a PDF tab, and the way to save any file that has a URL: no click on Download, no browser download, no "Save as" dialog.

Inputs: url (required), body, method, headers, +2 more.

inject_cssA
Idempotent

Inject a CSS rule into the page. Stays until the next navigation or reload, and re-injecting the same id replaces it rather than stacking. Affects only what is rendered — the stylesheet of the site is untouched.

Inputs: css (required), tab_id.

lighthouseA
Destructive

Run a full Lighthouse audit (performance included, unlike chrome-devtools-mcp, plus accessibility, best practices, SEO) and return scores, the five lab metrics and the failing audits that weigh most; the full report goes to .report.json and .report.html files. Works only in launch mode (chrome-bridge --launch [--headless]): it needs the DevTools port of the dedicated browser, which the extension in your own Chrome cannot provide. Loads the URL in a new tab of that browser; takes 20-60 s; the first call downloads Lighthouse through npx.

Inputs: url, tab_id, save_to, categories, +1 more.

manage_downloadsA

List downloads, start one, or wait for the newest to finish. Files land in the browser Downloads folder, not on the server. download reports the real state: with Chrome set to ask where to save, it comes back waiting_for_user — bytes fetched, nothing moves until someone picks a destination. To save a file that has a URL use http_request with save_to instead: browser cookies, bytes on disk, no dialog; a file the page builds goes through capture_downloads.

Inputs: action (required), url, limit, timeout, +1 more.

measure_spacingA
Read-onlyIdempotent

Measure the gap, overlap and distance in CSS pixels between two elements, with their margins and paddings. Read-only. Values come from the current layout, so zoom and viewport size change them: viewport_resize({zoom:1}) and a fixed viewport_resize make results comparable across runs.

Inputs: selector1 (required), selector2 (required), tab_id.

modify_domA
Idempotent

Change an element in the live DOM: setAttribute, removeAttribute, addClass, removeClass, setStyle, setTextContent. Nothing is persisted — the next reload restores the page as the server sends it. For styling many elements at once inject_css is one call instead of N.

Inputs: action (required), selector (required), name, value, +3 more.

monitor_networkA
Read-onlyIdempotent

Monitor network requests. source=page: XHR/fetch hook (installed on first call); source=browser: all requests incl. static assets; source=websocket: connections and messages. format=har exports HAR 1.2. It only observes: to block, stub or rewrite requests use network_rules, to send one yourself http_request.

Inputs: clear, limit, since, format, +2 more.

move_tabA
Idempotent

Move an existing tab into another window (chrome.tabs.move), keeping id, history and page state; works on chrome-untrusted:// tabs too. The destination must be a normal window: out of an app/popup window is fine, into one is refused. A ChromeOS Terminal tab can be pulled out (new_window, window_type popup) but never merged back: extensions cannot create app windows.

Inputs: tab_id (required), top, left, index, +5 more.

network_rulesA
Idempotent

Network interception, browser-wide, survives reloads until cleared: block, redirect, set/remove headers, stub a synthetic body, or record real API responses and replay them with forced errors/latency (local helper; from HTTPS pages the stub host must be trusted). It changes traffic, while monitor_network only observes it and http_request sends one request; each rule comes back as added, list shows them, clear removes them all.

Inputs: action (required), body, name, header, +8 more.

perf_traceA
Destructive

Record a DevTools performance trace and return its conclusions, never the trace: LCP with its phases (TTFB, resource load delay and duration, render delay), FCP, CLS (session windows), INP, the document request, render-blocking resources, long tasks with the function responsible, long animation frames. The raw trace goes to a JSON file (path and size in the result) that DevTools > Performance can load. action record (default) = start, reload unless reload: false, optional action, wait duration_ms, stop. start/stop bracket anything else done meanwhile. Uses chrome.debugger: Chrome shows its debugging bar while tracing; in launch mode nobody sees it. Refuses on a hidden page. For smoothness during an interaction use frames, for scores lighthouse.

Inputs: action, reload, tab_id, save_to, +2 more.

press_keyA

Send a key to the focused element (or to selector, focusing it first) as a real keydown/keypress/keyup sequence, so framework handlers fire. For typing a value use type_text; this is for Enter, Tab, Escape, arrows and shortcuts. Not idempotent: two calls send the key twice. Returns page_changed like click; when the key opens or reveals something, the refs of the visible interactives follow.

Inputs: key (required), alt, ctrl, meta, +5 more.

query_domA
Read-onlyIdempotent

Query DOM elements by CSS selector, returning structure, attributes, bounding rect and, with properties, computed styles. Use it when you already have a selector (no match: count 0); to find one, get_interactives or find_text; for the rule that set a style, get_css_styles.

Inputs: selector (required), limit, tab_id, frame_id, +1 more.

read_consoleA
Destructive

Read console messages captured since page load, incl. uncaught errors and unhandled rejections. clear deletes the messages it returns, hence not read-only; since=cursor reads only newer ones without deleting, and failed requests are in monitor_network.

Inputs: clear, level, limit, since, +3 more.

read_formA
Read-onlyIdempotent

Read a form as the user filled it — label, type, value, checked/selected, required-but-empty, browser validity — to check it against the project's documents before an irreversible Submit. Password and card values come back [redacted]. Read-only, on request; to change values use fill_form.

Inputs: tab_id, selector.

read_pageA
Read-onlyIdempotent

Read the page as text (default), markdown, raw HTML, or accessibility tree. Read-only. markdown keeps headings, links and tables at a fraction of the HTML cost. Expensive on big pages: HTML on a large table costs tens of thousands of tokens for data you filter anyway — prefer extract_table/extract for repeated content, get_interactives for click targets.

Inputs: mode, tab_id, save_to, frame_id, +1 more.

save_pageA
DestructiveIdempotent

Save the full page (DOM, styles, images) as an MHTML archive file on the server filesystem. An offline copy Chrome reopens as it looked, for evidence or a later comparison (for an image use screenshot, for the text read_page); it overwrites output_path and returns path and size.

Inputs: output_path (required), tab_id.

screencastA
Destructive

Record the tab as a video (.mp4 or .webm) to watch an animation or interaction frame by frame; returns the file path and size, never the frames. action record (default) = start, optional interaction, wait duration_ms, stop. start/stop bracket anything else done meanwhile (max 120 s). Chrome sends a frame only when the page changes; the video keeps the real timing. Needs the tab active and its window visible, and ffmpeg on the server machine (without it: the JPEG folder and the ffmpeg command). Uses chrome.debugger: Chrome shows its debugging bar while recording; in launch mode nobody sees it.

Inputs: fps, action, format, tab_id, +5 more.

screenshotA
Read-onlyIdempotent

Screenshot of the visible viewport only (PNG), at the current scroll position, or one per viewport preset with presets. Read-only. Activates the tab in the background without stealing window focus, then restores the previous tab. Downscaled to ≤1568px: for fine print, element_screenshot with scale.

Inputs: tab_id, presets, save_to, if_changed.

screenshot_diffA
Idempotent

Visual regression: save a named baseline (viewport, element, or a PNG such as the design mockup) and compare later — changed-pixel % and a red-highlighted diff; or compare_urls: production vs staging in this tab, pixels plus text diff, logged-in pages included. Baselines are in-memory (lost on service worker restart).

Inputs: action (required), mask, name, url_a, +5 more.

scrollA

Scroll. action=to: once to element/coordinates; action=until: repeatedly until element visible, network idle, or no new content (infinite scroll). It moves the tab's scroll position and returns it (to) or stopped_reason and steps (until); full_page_screenshot scrolls by itself, wait_for waits without scrolling.

Inputs: x, y, until, action, +9 more.

session_fixtureA
DestructiveIdempotent

Snapshot localStorage, sessionStorage and cookies of the current origin into a named fixture (a logged-in state, usually), restore one, or list them. save overwrites silently; restore writes on top without clearing and refuses on a different origin — cookies would attach to the wrong site.

Inputs: action (required), name, tab_id.

session_recordA

Record the commands of this session as a replayable jsonl (chrome-bridge replay --file ); observe what the user does in a tab ("watch how I do it") into the same format plus a readable procedure, never recording sensitive values; export a recording as a Playwright test for CI. Replays target the tab they navigate. Use it for a flow to repeat or a procedure to hand over; for one check now, assert.

Inputs: action (required), name, tab_id, values, +1 more.

set_geolocationA
Idempotent

Override navigator.geolocation with fixed coordinates (page-level patch). reset restores native. For location-based pages (store locators, delivery zones): it answers without a permission prompt, and a reload or navigation drops it, so call it after navigate.

Inputs: reset, tab_id, accuracy, latitude, +1 more.

set_storageA
DestructiveIdempotent

Write, delete or clear a localStorage/sessionStorage key, or a cookie with its path, domain and expiry. action=clear wipes every entry of that type for the origin and cannot be undone — on a site the user is logged into, clearing cookies logs them out. Read the current values first with get_storage.

Inputs: type (required), action (required), key, path, +7 more.

tab_actionA
Destructive

Tab lifecycle: close, activate, reload, back, forward, discard, mute, duplicate, close_session (only tabs this session created). close drops unsaved work and cannot be undone — it may be the user's tab. reload drops injected CSS, emulations and hooks. duplicate works where create_tab is forbidden (chrome-untrusted://) and lands in the source window, app windows included. discard replaces the tab id.

Inputs: action (required), tab_id, bypass_cache.

tile_windowsA
Idempotent

Tile Chrome windows over one monitor in equal parts with no gap. Only Chrome windows. The monitor is the one of a window already on it, whose work area is read from a page there: at least one target needs a scriptable tab (not chrome://). Maximized windows are restored first, since maximized ignores bounds. To restore a saved arrangement use window_layout; to move one tab into another window, move_tab.

Inputs: area, layout, padding, window_ids, +2 more.

track_eventsA
Read-onlyIdempotent

Decode the tracking beacons the page fired (GA4, Meta Pixel, Google Ads, TikTok, LinkedIn, Pinterest, Microsoft Ads, GTM, Hotjar, Clarity) from the browser network log: one line per event with its key params. Read-only; POST-body params are flagged, not decoded.

Inputs: clear, tab_id, wait_ms.

type_textA
Idempotent

Put text into an input, textarea or contenteditable, by selector or ref. Replaces the whole value through the native setter (React/Vue controlled inputs register it) and fires input and change. mode=keys emits keydown/input/keyup per character for autocomplete and masked fields: slower, use it only when mode=set leaves the field empty. Returns value_after; mismatch=true means the field did not keep the value: retry with mode=keys.

Inputs: text (required), ref, mode, tab_id, +3 more.

upload_fileA

Set a file on input[type=file] from the server filesystem via DataTransfer (max 200 MB; over 6 MB it travels in pieces). Keys and credentials (~/.ssh, ~/.aws, ~/.gnupg, .env*, *.pem, .key, id_…) are refused, and a --read-root on the server limits the readable tree: the call fails with an error, nothing is read.

Inputs: path (required), selector (required), tab_id, mime_type.

viewport_resizeA
Idempotent

Resize the Chrome window to a preset (mobile 375x812, tablet 768x1024, desktop 1440x900) or explicit dimensions; the viewport is smaller by the browser chrome, action=get reports the real one. width/height override half a preset. A maximized window on ChromeOS ignores the request.

Inputs: top, left, zoom, state, +5 more.

wait_forA
Read-onlyIdempotent

Block until a condition holds: element, text, function (JS expression; needs the "Allow user scripts" toggle), navigation (mode=spa for client-side routes), network_idle. Polls until timeout (10s element/function, 15s the rest) then returns found: false with a reason instead of raising: check it, or a failed wait reads as success. Read-only.

Inputs: condition (required), mode, text, tab_id, +7 more.

watchA
Idempotent

Keep watching a page in the background — "tell me when the pipeline is green / the Approve button appears / this number changes" — checked every interval_s by the extension. Events are collected, not pushed: action=poll, or a script blocking on "chrome-bridge watch --wait ". Survives extension idling, not a browser restart. poll returns one line per event (time, name, kind, url, value, previous); a change due within seconds is wait_for.

Inputs: name, text, since, until, +7 more.

watch_domA
Destructive

Watch DOM mutations (MutationObserver) under selector, to see what an action changed. First call installs the watcher in the page; later calls return the latest mutations (type, tag, attribute, nodes added/removed), clear deletes the ones returned, stop disconnects and empties the buffer. To wait for one element or text use wait_for.

Inputs: stop, clear, limit, tab_id, +5 more.

webmcp_callA
Destructive

Run a WebMCP tool the page declares (see webmcp_tools or the webmcp line of navigate): one call instead of the clicks and fields it replaces. Returns status (ok, error, timeout, not_found, inactive, needs_confirmation), the tool result and ms; navigated_to when the call led to another page. A sensitive tool (buy, pay, send, delete, a POST form) is not run without confirm:true: on needs_confirmation ask the user, then repeat with confirm.

Inputs: name (required), input, tab_id, confirm, +2 more.

webmcp_toolsA
Read-onlyIdempotent

List the WebMCP tools the page declares for agents: name, description, input_schema, origin, kind (imperative = registered with document.modelContext.registerTool; declarative = a ), sensitive (why webmcp_call wants confirm) and how many times each was called. active=false: WebMCP is off in this Chrome and only form tools are visible, as plain forms. calls adds the call log of the page, any caller (chrome-bridge or the browser agent): input, output, status, ms.

Inputs: calls, tab_id, frame_id.

window_layoutA
DestructiveIdempotent

Save the current window arrangement under a name, restore, list or delete. save overwrites silently. Window ids do not survive a browser restart: restore matches windows by the overlap of their tab URLs and reports the ones it cannot recognise instead of guessing.

Inputs: action (required), name.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 69 tools

    Disambiguation3/5

    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.

    Naming Consistency3/5

    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.

    Tool Count2/5

    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.

    Completeness5/5

    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.

    Maintenance

    ActivityActive
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      MCP server that lets agents drive your real Chrome browser with existing logins and sessions via an outbound-only WebSocket extension. It exposes Playwright-compatible browser tools for navigation, clicking, typing, and snapshots.
      1
      Apache 2.0
    • A
      license
      B
      quality
      B
      maintenance
      Turns Chrome into a local MCP server, giving AI agents 51 browser tools to inspect DOM, automate clicks, run JavaScript, audit security and accessibility, mock APIs, extract data, record demos, and more from any MCP client.
      92
      459 npm
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI agents to drive a real Chrome instance over the Chrome DevTools Protocol — opening and navigating tabs, reading compact YAML page outlines with refs, clicking, typing, scrolling, screenshotting, and evaluating scripts — with no browser extension or installed dependencies. It also audits pages for horizontal overflow and WCAG AA contrast, available both as an MCP tool and as a CI-friendly CLI.
      10 npm
      MIT
    • A
      license
      Not graded
      quality
      A
      maintenance
      A local MCP server that lets AI coding tools drive the user's real, already-logged-in Chrome — reading pages, clicking, typing, uploading files, taking screenshots, and inspecting tabs, cookies, console, and network traffic — via 25 browser automation tools that pass bot detection without a debugging port. Requires a companion Chrome extension and stays entirely on the user's machine.
      1 npm
      ISC