Chrome Bridge
Chrome Bridge is a local MCP server that lets an AI agent drive the user's already-logged-in Chrome, inspect/debug pages, and automate browser workflows.
Manage tabs and windows: navigate, create/close/reload/move/duplicate/discard/mute tabs, and tile/save window layouts.
Interact with pages: click, type, fill forms, press keys, hover, drag-and-drop, upload files, handle dialogs/overlays, clipboard, and hand off 2FA/login to the user.
Inspect and extract content: read pages as text/markdown/HTML/accessibility tree, list interactive elements with refs, query DOM/CSS, find text, list frames, and extract tables or repeated items with server-side filtering.
Execute JavaScript, modify DOM, inject CSS, emulate media/geolocation, and resize viewports/windows.
Capture visuals: viewport/element/full-page screenshots, visual regression baselines and diffs, screencast video, and MHTML page saves.
Debug: read console logs, monitor network/HAR/WebSocket traffic, mock/block/redirect/stub/replay network rules, send authenticated HTTP requests, and handle HTTP auth.
Work with storage and files: read/write localStorage, sessionStorage, cookies, save/restore session fixtures, capture/manage downloads, and save response bytes.
Measure performance: record perf traces, inspect animations and frames/CLS/INP, run Lighthouse and heap snapshots in launch mode, and audit accessibility, SEO, security, links, vitals, CSS, resources, and cache.
Audit cookies/consent banners and identify trackers contacted before consent.
Use WebMCP: list and call tools declared by the page.
Automate and verify: wait for conditions, assert page state, watch pages for changes, record/replay sessions, and export Playwright tests.
Check bridge status and enable optional tool caps at runtime; launch a dedicated Chromium/Edge/Brave instance for isolated or headless/CI use.
Chrome Bridge
By frsorrentino · npm
chrome-bridge-mcp· not affiliated with other projects named "chrome-bridge".
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
In a Claude Code session:
/plugin install chrome-bridge --marketplace frsorrentino/chrome-bridgeFrom a shell, the same:
claude plugin install chrome-bridge --marketplace frsorrentino/chrome-bridge.Add the extension from the Chrome Web Store.
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 |
|
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 |
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.shThen 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.

Related MCP server: neuron-inspector
Why it takes fewer turns
Refs, not screenshots.
navigatereturns 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_formfills N fields and submits in the same call.Filtering on the server.
extract_tablewithwhereandcolumnsreturns the matching rows of a 1,500-row table, not the table.Debugging starts with the answer.
navigatereports the first console error mapped to its source line and the failed requests with their status;get_css_stylesnames 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 ( | No | No | No |
Audits (a11y/SEO/sec) | Yes (one call, report on disk) | No | Lighthouse | No |
Performance trace | Yes ( | No | Yes | Playwright trace only |
Screen recording | Yes ( | 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
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:
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.jsonlLaunch mode — a dedicated Chromium instance with an ephemeral profile, for isolated sessions or CI:
node server/index.js --launch --headlessIt 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, |
Interaction | 11 |
|
DOM & Inspection | 12 |
|
Debugging & Network | 8 |
|
WebMCP | 2 |
|
Visual & Responsive | 5 |
|
Audits | 2 |
|
State, Storage & Files | 10 | storage, fixtures, MHTML, |
Motion & Performance | 6 |
|
Every tool, with the notes that matter: 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
Variable | Default | Notes |
|
| |
|
|
|
| unset | Required on both |
|
|
|
| 22 most used tools | Tools kept in context when the client loads the server whole; the others carry |
| unset | No arbitrary JavaScript in the page: |
| unset | Where |
| unset | Launch mode only: the browser binary, e.g. |
| unset |
|
| unset | Every path the model chooses ( |
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
lighthouseandheap_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_keywithtrusted,emulate_media via: "debugger",perf_traceandscreencastattachchrome.debuggerto 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-jsis not a sandbox:inject_cssstays, and CSS attribute selectors withurl()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 senddoes the same on demand;list,markandaddread 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 --securityprepares 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), orCHROME_BRIDGE_OBSERVE=offfor the server. Keep the log but stop the offers:{"propose": false}.
Troubleshooting
Symptom | Cause / fix |
| 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 |
| Something else owns the port. Free it or change |
| Enable Allow user scripts in |
| The page was opened before the extension, "Capture console & metrics" is off, or the page isn't injectable ( |
Screenshot times out, or | A minimized or fully covered window stops producing frames; captures fail after 10 s. Bring the window forward. |
| Same cause: Chrome does not render a hidden page and slows its timers (after a few minutes, to one wake-up per minute). |
| 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 |
| The field rejected the value. Events from an extension have |
| Written by the MCP client, not by the bridge: too many images in one request. Use |
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 |
| Expected with the plugin: since Claude Code 2.1.285 it hides them for stdio servers that plugins provide (variable names stay). |
Popup says «In attesa: un altro browser è collegato», | 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. |
Documentation
docs/TOOLS.md — all 69 tools, by group
docs/CAPABILITIES.md — what the bridge gets past and what it does not, one dated state per wall
docs/EFFICIENCY.md — the benchmark and the design behind it
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 — latency per tool on the real path,
npm run bench:latencybench/RESULTS.md — raw runs and inclusion rule
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 — 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 toolsanimationsA
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.
assertARead-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.
auditARead-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.
clipboardAIdempotent
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.
cookie_auditAIdempotent
Cookie and consent-banner audit: clears this site's cookies, reloads, records cookies and third-party requests BEFORE consent, accepts the banner, records again; the findings name the trackers contacted before consent. Logs you out of the audited site.
Inputs: tab_id, settle_ms, accept_selector.
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_overlaysAIdempotent
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_screenshotARead-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_mediaAIdempotent
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_jsADestructive
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.
extractARead-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_tableARead-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_settingAIdempotent
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_textARead-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_screenshotARead-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_stylesARead-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_framesARead-onlyIdempotent
List frames (main + iframes) with frameId, parent, URL — for the frame_id parameter of DOM tools.
Inputs: tab_id.
get_interactivesARead-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_infoARead-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_statusAIdempotent
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_storageARead-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_tabsARead-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_dialogsAIdempotent
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.
handoffAIdempotent
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_snapshotADestructive
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.
hoverAIdempotent
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_authAIdempotent
Set/clear credentials for HTTP Basic/Digest auth dialogs (browser-wide, in-memory only).
Inputs: action (required), password, username.
http_requestADestructive
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_cssAIdempotent
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.
lighthouseADestructive
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_spacingARead-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_domAIdempotent
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_networkARead-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_tabAIdempotent
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_rulesAIdempotent
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_traceADestructive
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_domARead-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_consoleADestructive
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_formARead-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_pageARead-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_pageADestructiveIdempotent
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.
screencastADestructive
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.
screenshotARead-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_diffAIdempotent
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_fixtureADestructiveIdempotent
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_geolocationAIdempotent
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_storageADestructiveIdempotent
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_actionADestructive
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_windowsAIdempotent
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_eventsARead-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_textAIdempotent
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_resizeAIdempotent
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_forARead-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.
watchAIdempotent
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_domADestructive
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_callADestructive
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_toolsARead-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_layoutADestructiveIdempotent
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
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.
Maintenance
Related MCP Connectors
Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.
Browser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.
Live browser debugging for AI assistants — DOM, console, network via MCP.
Headless-browser-as-JSON with memorymarket cache economics. Real Chromium, crypto settlement.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceMCP 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.1Apache 2.0
- AlicenseBqualityBmaintenanceTurns 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.92459 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables 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 npmMIT
- AlicenseNot gradedqualityAmaintenanceA 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 npmISC