Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Chrome DevTools MCP Connector

Control and inspect Chrome from Claude Code via the Model Context Protocol: capture screenshots, navigate and interact with pages, read console output, and evaluate scripts. Perfect for visual theme verification, UI regression testing, design review, and end-to-end browser automation - Claude sees and drives exactly what your users see.

┌──────────────┐  WebSocket   ┌──────────────┐  stdio/MCP   ┌──────────────┐
│    Chrome    │─────────────▶│  MCP Server  │◀────────────▶│  Claude Code │
│  Extension   │  localhost   │  (Node.js)   │              │              │
│ (MV3, s.w.)  │   :8770      └──────────────┘              └──────────────┘

What's included

Component Path Purpose
Chrome extension (Manifest V3) extension/ Captures screenshots and drives the active tab on demand
MCP Server server/ Bridges extension → Claude Code
Screenshots screenshots/ Saved PNGs + diff images

Setup

1. Install the MCP server

cd server
npm install

2. Load the Chrome extension

  1. Open Chrome and go to chrome://extensions
  2. Enable Developer mode (top-right toggle)
  3. Click Load unpacked
  4. Select the extension/ folder
  5. The camera icon appears in your toolbar

The extension's background logic runs as a service worker, so it can be unloaded by Chrome when idle and woken back up automatically (via an alarm and on-reconnect logic) - there's nothing to keep open or pin.

3. Register with Claude Code

Run this from the project root:

claude mcp add chrome-devtools \
  --scope user \
  -- node /ABSOLUTE/PATH/TO/chrome-devtools-mcp/server/index.js

Replace /ABSOLUTE/PATH/TO/ with the real path on your machine.

Or add it manually to ~/.claude.json:

{
  "mcpServers": {
    "chrome-devtools": {
      "command": "node",
      "args": ["/ABSOLUTE/PATH/TO/chrome-devtools-mcp/server/index.js"],
      "env": {
        "CHROME_DEVTOOLS_WS_PORT": "8770"
      }
    }
  }
}

4. Verify

Open a new Claude Code session and run:

/mcp

You should see chrome-devtools listed with 26 tools.

Tools available in Claude Code

Tool Tier Description
capture_screenshot read Capture the visible viewport of the active tab
capture_full_page read Capture the full scrollable page (scroll-and-stitch)
list_screenshots List all saved screenshots with timestamps
get_screenshot Retrieve a saved screenshot by filename
compare_screenshots Pixel-diff two screenshots, outputs a diff image
navigate write Load a URL in the active tab
go_back / go_forward write Move through tab history
reload_page write Reload the active tab, optionally bypassing cache
get_current_url read Return {url, title} of the active tab
get_element_text read Get an element's textContent
get_element_attribute read Get an element's attribute value
query_elements read Find elements matching a selector, with summary info — good for exploring a page
click_element write Click an element
fill_input write Fill an input/textarea; dispatches input/change so React/Vue notice
fill_form write Fill multiple inputs in one call, optionally clicking a submit element after
select_option write Select a <select> option by value or visible label
set_checkbox write Set a checkbox/radio's checked state
hover_element write Dispatch mouseover/mouseenter on an element
focus_element write Focus an element
press_key write Dispatch a keydown/keyup pair (e.g. Enter, Tab, Escape)
wait_for_selector write Poll until an element is visible/attached/hidden, or time out
read_console_logs read Read buffered console output from the active tab (since it last navigated)
clear_console_logs read Clear the active tab's console buffer
get_page_errors read Return uncaught errors and unhandled promise rejections since page load
evaluate_script execute Run JavaScript in the page's own MAIN-world context - full access to page globals

navigate/go_back/go_forward/reload_page/get_current_url/capture_* and all the interaction/inspection/console tools act on the active Chrome tab and are gated by the site permission allowlist (see Permissions below). list_screenshots, get_screenshot, and compare_screenshots work on locally saved files and aren't tab-scoped.

Console capture only ever runs on tabs the allowlist already grants read access to - the extension doesn't monkey-patch console on every site you browse, only ones you've explicitly allowed. If a page's Content-Security-Policy blocks the capture script, other tools on that page keep working; read_console_logs will just come back empty.

Element-targeting tools accept a selector_type of css (default), xpath, or text (case-insensitive substring match against leaf elements), plus an optional nth to pick among multiple matches.

evaluate_script is the most powerful - and most dangerous - tool here: it runs arbitrary JavaScript in the page's own MAIN-world context, with full access to page globals, not just the DOM. Code is wrapped in an async function body (return produces a value, await works directly). It requires the execute tier, which no site gets by default - see Permissions below. Every invocation logs a warning to the extension's service worker console and the MCP server's stderr.

Usage examples

Once connected, just talk naturally in Claude Code:

> Take a screenshot of the page I have open and check if the dark
> mode theme matches our design tokens.

> Capture screenshots before and after my CSS change, then compare
> them to see what shifted.

> List all the screenshots we've taken and show me the latest one.

> Compare screenshot_1721900000.png with screenshot_1721901000.png
> using a threshold of 15 - I want to catch subtle color changes.

How it works

  1. Claude Code calls an MCP tool (e.g. capture_screenshot)
  2. The MCP server sends a WebSocket message to the Chrome extension's service worker
  3. The extension uses chrome.tabs.captureVisibleTab() to grab a PNG
  4. The PNG (base64) is sent back through WebSocket → MCP server → Claude Code
  5. Claude Code sees the actual screenshot and can analyze it visually

For capture_full_page, the extension scrolls the page in viewport-sized increments, capturing a PNG at each step (throttled, since Chrome itself rate-limits captureVisibleTab), and the server stitches the slices into one image with sharp, positioned at each slice's real (post-clamp) scroll offset. This is a genuine full-page capture, not a viewport fallback - the one known artifact is that fixed/sticky headers or footers will appear once per slice, since each slice is an independent screenshot of whatever was in the viewport at that scroll position.

For compare_screenshots, the server uses sharp to do a pixel-level diff and generates a highlighted diff image where changed pixels appear in red.

Configuration

Env var Default Description
CHROME_DEVTOOLS_WS_PORT 8770 WebSocket port the extension connects to

The extension's background.js hardcodes the matching port (ws://localhost:8770); if you change the env var, update WS_PORT at the top of extension/background.js to match and reload the extension.

Theme verification workflow

A typical visual QA loop looks like this:

  1. Open your app in Chrome with the light theme → ask Claude to capture
  2. Toggle to dark theme → capture again
  3. Ask Claude to compare the two and verify contrast ratios, color tokens, etc.
  4. Claude reports exactly which regions changed and whether the theme looks correct

Troubleshooting

Extension shows "Disconnected" → Make sure the MCP server is running. Claude Code starts it automatically when you use a tool, but you can also run cd server && node index.js manually. If it was running and still shows disconnected, click Reconnect in the popup - Chrome may have put the service worker to sleep and it can take a moment to reconnect on wake.

NO_ACTIVE_TAB → Either no tab is focused, or the focused tab is a privileged browser page (chrome:*, chrome-extension:*, edge:*, about:*, devtools:*) that the extension can never automate, regardless of the allowlist. Focus a regular webpage tab and retry.

TAB_CHANGED → The active tab switched to a different tab between the permission check and the action actually running (you alt-tabbed, or clicked a different tab, while a tool call was in flight). The extension re-verifies the active tab immediately before every action - this is a real permission check catching a real race, not a bug - and aborts rather than risk acting on a page it never checked. Just retry once you're back on the intended tab. (If the tab merely navigated to a different URL rather than losing focus, you'll see PERMISSION_DENIED instead if the new page isn't allowlisted.)

Screenshots are blank, black, or full-page capture looks off → Some pages block captureVisibleTab (e.g. DRM-protected video). For full-page capture, infinite-scroll pages are capped at 40 slices, and lazy-loaded content that hasn't rendered yet by the time a slice is captured may be missing - try capturing again after the page has fully settled.

ELEMENT_NOT_FOUND / ELEMENT_NOT_INTERACTABLE → The selector matched zero elements (or nth was out of range), or it matched an element that's disabled or has no visible layout box (display:none, zero size, etc.). Try query_elements first to see what's actually on the page.

"Site not allowlisted" / PERMISSION_DENIED → Every action is denied by default. If auto-prompt is on (the default), a native notification appears and the popup shows an inline "Allow this site?" panel - click the toolbar icon and hit Allow. Otherwise, click ⚙ Manage Permissions and add the site's URL pattern (e.g. http://localhost:*/*) with the tier the action needs.

RATE_LIMITED → More than rate_limit_per_second actions (default 20) hit the same tab within one second - usually a runaway loop. Wait a moment and retry, or raise the limit in ⚙ Manage Permissions → Settings.

SCRIPT_ERROR / INJECTION_BLOCKEDevaluate_script: SCRIPT_ERROR means your code threw - the message is the page's own error. INJECTION_BLOCKED means the page's Content-Security-Policy stopped the Function constructor from running; nothing can be done short of the page relaxing its CSP.

Permissions

Deny by default: Claude Code can only act on a tab whose URL matches an entry in the extension's allowlist. Manage the allowlist, settings, and audit log from the popup's ⚙ Manage Permissions link, which opens the options page (Sites / Settings / Audit Log tabs).

  • read - capture_screenshot, capture_full_page, get_current_url, get_element_text, get_element_attribute, query_elements, read_console_logs, clear_console_logs, get_page_errors
  • write - everything in read, plus navigate, go_back, go_forward, reload_page, click_element, fill_input, fill_form, select_option, set_checkbox, hover_element, focus_element, press_key, wait_for_selector
  • execute - everything in write, plus evaluate_script - arbitrary JS in the page's MAIN-world context. The Sites tab shows a red warning before you can grant it; it's off by default and worth granting only to sites you fully trust and control.

Patterns follow a match-pattern syntax (https://example.com/*, *://*.example.com/*, http://localhost:*/*). <all_urls> is rejected - it's too permissive for automation.

Settings tab: auto_prompt_new_sites (native notification + inline popup panel on first denied attempt), log_all_actions (audit log on/off), max_console_buffer (per-tab entry cap), rate_limit_per_second (per-tab action cap). Danger zone: clear the allowlist or reset all extension data - both irreversible.

Audit log tab: every action attempt (allowed or denied), filterable by site/action/result, with a clear button.

Manifest V3 notes

This extension targets Manifest V3 (minimum_chrome_version: 116):

  • The background page is a non-persistent service worker. It reconnects its WebSocket on load, on chrome.runtime.onStartup/onInstalled, and via a chrome.alarms heartbeat (every 60s) that also pings the socket to keep it from going idle.
  • DOM interaction and script evaluation use chrome.scripting.executeScript with inline, self-contained functions (no persistent injected content scripts beyond the one document_start console-capture script) - world: "MAIN" is used for evaluate_script and the console/error patcher, world: "ISOLATED" (the default) for DOM queries/clicks/fills.
  • No chrome.debugger usage anywhere, so you'll never see Chrome's "This extension is debugging this browser" banner.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages