Skip to main content
Glama
chrome-dev-mcp

chrome-dev-mcp

README.md
# chrome-dev-mcp

[![npm chrome-dev-mcp package](https://img.shields.io/npm/v/chrome-dev-mcp.svg)](https://npmjs.org/package/chrome-dev-mcp) [![chrome-dev-mcp MCP server – quality and maintenance score on Glama](https://glama.ai/mcp/servers/congzhou09/chrome-dev-mcp/badges/score.svg)](https://glama.ai/mcp/servers/congzhou09/chrome-dev-mcp)

●An MCP server that attaches to an already-running Chrome tab for real runtime debugging: breakpoints, stepping, and scope variables — plus JS/CSS inspection, console logs, and network capture. Built for web frontend development.

●It talks plain CDP through chrome-remote-interface — no DevTools SDK, no bundled browser — so it debugs the tab you already have open, in your own Chrome, instead of launching an isolated instance.

## Demo video

### Debugging js

[![Debugging js](https://github.com/user-attachments/assets/9f1a8066-4e0c-42cb-a95a-1bb58d5bbade)](https://github.com/user-attachments/assets/9d4de615-9b96-4780-ac72-c4097083bf8c)

### Inspecting html and css

[![Inspecting html and css](https://github.com/user-attachments/assets/d0a53c59-d3c8-4671-9387-9330cab952aa)](https://github.com/user-attachments/assets/2b15035b-c144-474a-91e3-71bbb607bf37)

## Why This Exists

●Currently, [chrome-devtools-mcp](https://www.npmjs.com/package/chrome-devtools-mcp) is still focused more on browser automation and inspection than full runtime debugging, though it is clearly moving toward exposing more DevTools capabilities, as described in this [Let your Coding Agent debug your browser session with Chrome DevTools MCP](https://developer.chrome.com/blog/chrome-devtools-mcp-debug-your-browser-session?utm_source=chatgpt.com).

●Meanwhile, the underlying debugging capabilities are already available through tools such as chrome-remote-interface and @jridgewell/trace-mapping.

●This project exists as a faster, independent implementation focused specifically on making Chrome runtime debugging usable for AI agents before similar functionality is officially available in chrome-devtools-mcp.

### Architectural difference from chrome-devtools-mcp

●chrome-devtools-mcp runs DevTools SDK models (`TargetManager`, `DebuggerModel`, `NetworkManager`, etc.) directly in Node.js via `chrome-devtools-frontend`'s `/mcp/mcp.js` entrypoint, backed by a Puppeteer CDP connection — capabilities that go beyond what the raw Chrome DevTools Protocol exposes directly.

●That approach comes with trade-offs: `chrome-devtools-frontend` is a very large package (it mirrors the entire Chrome DevTools frontend codebase), and the approach relies on the internal structure of the DevTools page remaining stable across Chrome versions.

●This project takes the opposite approach: plain CDP via `chrome-remote-interface`, no DevTools SDK, minimal dependencies. The result is a lightweight server that is easy to install, audit, and extend.

## Limitations

■ **Does not track Chrome's active tab automatically.** CDP does not expose a tab-switch event, so switching tabs in Chrome does not change the MCP connection — use `switch_tab` to explicitly reconnect to the tab you want.

■ **Iframes, workers, and service workers are not supported at present.**

■ **WebSocket frames are not captured.**

■ **Not designed to run alongside chrome-devtools-mcp**. Both register overlapping tool names and maintain independent debugger state against the same Chrome target, which causes confusion for the AI and potential state conflicts.

■ **Breakpoints are not shared with DevTools.** Chrome keeps breakpoints per CDP connection: those set through `set_breakpoint` never appear in DevTools' Breakpoints pane and cannot be removed there (use `remove_breakpoint`; Chrome also drops them when this server disconnects or switches tabs), and those set in DevTools never appear in `list_breakpoints`. Pausing is shared, though — DevTools pauses on this server's breakpoints too, and resuming or stepping from either side applies to both.

■ **DevTools' "Deactivate breakpoints" (Ctrl+F8) has no effect while this server is debugging the tab.** Chrome turns breakpoints off only once every debugger connected to the tab has turned them off, and this server never does — so the page still pauses on DevTools' breakpoints, on debugger; statements, and on this server's breakpoints. This server starts debugging a tab with its first call to `get_console_logs`, `navigate_tab`, `reload_tab`, `switch_tab` or a debugger tool such as `set_breakpoint`, and stops when it disconnects or switches to another tab.

## Prerequisites

- Node.js 22+
- Google Chrome

## Usage

### Chrome

▲Launch Chrome with remote debugging enabled.

```
chrome.exe --remote-debugging-port=9222 --user-data-dir=C:\chrome-debug-profile
# --user-data-dir can be any empty directory; it keeps the debug session isolated from your normal Chrome profile.
```

▲Verify remote debugging is active by opening http://localhost:9222/json in a browser — it should return a JSON list of debuggable targets.

▲Open the page you want to debug. The MCP server connects to the active tab at startup. To switch to a different tab later, ask the AI to switch — it will use `list_tabs` and `switch_tab` as needed.

### Claude Code configuration

#### Through npm package

##### With a fixed version

▲Install npm package globally.

```
npm install -g chrome-dev-mcp
```

▲Add the server to Claude Code's MCP.

```
claude mcp add --transport stdio chrome-dev -- chrome-dev-mcp
```

▲Claude Code's config(`~/.claude.json`) will look like this:

```json
"mcpServers": {
  "chrome-dev": {
    "type": "stdio",
    "command": "chrome-dev-mcp",
    "args": [],
    "env": {}
  }
},
```

##### Always use the latest version

▲Add the server to Claude Code's MCP.

```
claude mcp add --transport stdio chrome-dev -- npx -y chrome-dev-mcp@latest
# '-y' is not supportted at 20260522. We may change the config below directory.
```

▲Claude Code's config(`~/.claude.json`) will look like this:

```json
"mcpServers": {
  "chrome-dev": {
    "type": "stdio",
    "command": "npx",
    "args": [
      "-y",
      "chrome-dev-mcp@latest"
    ],
    "env": {}
  }
},
```

#### Through local project

▲Clone this project to local.

▲Add the server to Claude Code's MCP.

```
claude mcp add --transport stdio chrome-dev -- node "path/to/chrome-dev-mcp/dist/index.js"
```

▲Claude Code's config(`~/.claude.json`) will look like this:

```json
"mcpServers": {
  "chrome-dev": {
    "type": "stdio",
    "command": "node",
    "args": ["path/to/chrome-dev-mcp/dist/index.js"],
    "env": {}
  }
}
```

### Validation

●run `claude mcp list`, and it will print `chrome-dev: xxxxx - ✓ Connected`.

## MCP Tools

☆25 tools in seven groups — six domains plus one cross-cutting tool:

| Group           | Tools | Covers                                                                                                                 |
| --------------- | ----- | ---------------------------------------------------------------------------------------------------------------------- |
| Tab management  | 2     | Discovering tabs, and choosing which one this server is attached to                                                    |
| Page inspection | 6     | The live page: URL and title, HTML, computed CSS, screenshots, the DevTools-selected element, and arbitrary evaluation |
| Navigation      | 2     | Loading a URL or reloading, and waiting until the new page has loaded or paused                                        |
| Console         | 1     | Console messages and uncaught exceptions, including output from before this server connected                           |
| Debugger        | 11    | Breakpoints, stepping, call stack, scopes, frame-scoped evaluation                                                     |
| Network         | 2     | Requests captured from connect time onward, plus response bodies fetched on demand                                     |
| Capture buffers | 1     | `clear_captures` — cross-cutting: resets the console and network buffers above                                         |

☆All 25 serve one workflow. Two things come first, in either order: the agent learns what goes wrong, and the page reaches the state where it does — by hand, or through the agent's own `navigate_tab`, `reload_tab` and breakpoints. From there the agent reads whatever explains it — a console error, a network response, the DOM and its computed CSS, or a paused call stack and its scopes. Once a fix is in, the same tools confirm it: `reload_tab`, then read again.

☆Several pairs below look mergeable and are deliberately not — [docs/tool-boundaries.md](docs/tool-boundaries.md) records which, and why.

### Tab management

| Tool         | Description                                                                                                                                                                                                                              |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list_tabs`  | List all open Chrome page tabs as `[{ targetId, title, url, active?, connected }]` — `active` marks a tab showing on screen (one per visible window; absent if the tab did not answer), `connected` the tab this server acts on          |
| `switch_tab` | Connect this server to another tab by `targetId` (from `list_tabs`), without bringing it to the front. A tab that Chrome discarded to free memory is reloaded. Breakpoints and the console and network buffers start over on the new tab |

### Page inspection

| Tool                    | Description                                                                                                                                                                                                                                                                                                                                                                                                       |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get_page_info`         | Current page URL and title as `{ url, title }` — answers even while the page is not responding                                                                                                                                                                                                                                                                                                                    |
| `get_html`              | Full page HTML (capped at 20,000 chars)                                                                                                                                                                                                                                                                                                                                                                           |
| `evaluate_js`           | Run arbitrary JavaScript in global scope, with DevTools console semantics: top-level `await` works, and an expression that merely returns a promise stays pending rather than being awaited for you. Returns the real value when it serialises; DOM nodes, Errors, Maps and class instances come back as a preview instead — class name plus a first level of properties, readable but not parseable as the value |
| `get_computed_style`    | Computed CSS values for the given properties on the first element matching a CSS selector                                                                                                                                                                                                                                                                                                                         |
| `screenshot`            | PNG screenshot of the current viewport, or of one `region` of it (CSS px from the top-left of the visible area, as `getBoundingClientRect()` reports them). Native pixel size unless capped with `maxEdge` (longest side in px); a capture that was scaled or cut reports that                                                                                                                                    |
| `get_inspected_element` | Tag, id, classes, attributes, and outerHTML of the element marked via `window.$0 = $0` in the DevTools console                                                                                                                                                                                                                                                                                                    |

### Navigation

| Tool           | Description                                                                                                                                        |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `navigate_tab` | Load a URL in the connected tab and wait for the new page's load event — or for execution to pause first, in which case the call stack is returned |
| `reload_tab`   | Reload the connected tab and wait the same way as `navigate_tab`. `ignoreCache` bypasses the HTTP cache, as Shift+F5 does                          |

▲Both work while paused at a breakpoint: the pause ends with the old page, and breakpoints hit again in the new one — so "set a breakpoint, then `reload_tab`" stops in the page's startup code and returns the call stack where it paused.

▲A failed navigation is reported as an error, not as `loaded` — Chrome still loads its own error page in that case and fires its load event — and a page whose load event does not fire within 30s comes back as `still-loading`.

### Console

| Tool               | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get_console_logs` | All messages visible in the DevTools Console, including output that predates this server's connection. Exceptions are reported with their full stack trace (source-mapped when available). Filterable by level (`error`, `warning`, …). Not pruned on navigation: entries from a replaced document, or from before the connection, carry a `from` marker while the current document's are unmarked, and `since: "current-page-load"` narrows the result to those; `clear_captures` empties the buffer outright. |

### Debugger

| Tool                  | Description                                                                                                                                                                                                     |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get_debugger_state`  | Paused status, pause reason, hit breakpoints, and full call stack with file + line, source-mapped when possible (with the position in the loaded script alongside)                                              |
| `get_scope_variables` | Variable values inside a call frame scope (`local`, `closure`, `block`, `global`, …)                                                                                                                            |
| `evaluate_at_frame`   | Evaluate a JS expression in a paused call frame's scope — reads local variables, closures, and `this`. Errors out when not paused rather than falling back to global scope                                      |
| `set_breakpoint`      | Set a breakpoint by source file + line (mapped through source maps), or by script URL / regex; supports conditions                                                                                              |
| `remove_breakpoint`   | Remove a breakpoint by its ID                                                                                                                                                                                   |
| `list_breakpoints`    | All breakpoints active in this session                                                                                                                                                                          |
| `pause_execution`     | Pause JS at the next statement that runs, returning the call stack if that happens within 2s. On an idle page the pause stays pending until script runs — a timer, a click, an `evaluate_js` expression         |
| `resume_execution`    | Resume after a pause or breakpoint. With `terminate: true`, skip the rest of the paused function and of its callers, `finally` blocks included, e.g. to stop an endless loop; the page may be left half-updated |
| `step_over`           | Run the current statement without stepping into the functions it calls, and pause at the next one; returns the new call stack                                                                                   |
| `step_into`           | Step into the function the current statement calls, or to the next statement if it calls none; returns the new call stack                                                                                       |
| `step_out`            | Run the rest of the current function and pause in its caller; returns the new call stack                                                                                                                        |

▲The three step tools work only while paused, and go by statement rather than by line, since a minified line holds many statements. A step past the end of an event handler or timer callback — from its last statement, or out of it — pauses at the first statement of whatever script runs next, which may be unrelated code. When a step has not paused within 5s, the result lists the possible reasons: the step went past the end of the code that was running, the code is waiting at an `await`, or the page is not responding.

### Network

| Tool                        | Description                                                                                                                                                                                                                                                                                                                                                                                      |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `get_network_requests`      | HTTP requests captured from the connected tab — method, URL, resource type, status, transferred size, duration, initiator, and failure reason. Redirects appear as one record per hop. Filter by URL substring, resource type, or status class (`2xx` / `3xx` / `4xx` / `5xx` / `failed` / `pending`). `headerKeys` returns the named request/response headers (omit for none, `["*"]` for all). |
| `get_network_response_body` | Response body for one `requestId` from `get_network_requests`. Fetched from Chrome on demand — never buffered by this server, and Chrome discards it on navigation, so fetch while the page is still up. Binary bodies are reported as metadata only.                                                                                                                                            |

▲Unlike `get_console_logs`, network capture is **not** retroactive: `Network.enable()` has no history replay, so capture begins when this server connects to the tab and nothing before that is visible. Requests belonging to a previous page are then pruned on navigation, mirroring the DevTools Network panel default — the new document's own request is kept.

### Capture buffers

| Tool             | Description                                                                                                                                               |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clear_captures` | Discard the console and/or network buffers this server holds, so the next read shows only what happens afterwards. Pick with `targets`; defaults to both. |

▲Affects this server only — nothing is cleared in Chrome or in the DevTools UI, and capture keeps running. Clearing cannot be undone: console entries are gone for good, since `Console.enable()` replays history only at attach time, while cleared network `requestId`s still resolve in `get_network_response_body` for as long as Chrome itself holds the body.

## Typical debugging workflow

◆Bring Chrome to the desired state manually — navigate to a specific route, trigger a flow, or pause at a breakpoint.

◆Ask the AI what you want to investigate, and it will call `get_debugger_state`, `get_scope_variables`, etc. automatically when needed.

◆To share a specific DOM element with the AI during debugging, select it in the Elements panel, then run this in the DevTools console:

```js
window.$0 = $0;
```

The AI can then call `get_inspected_element` to read its tag, attributes, and HTML.

```
# Example sequence Claude might use
get_debugger_state          → { paused: true, callStack: [{ functionName: "handleClick", url: "...", lineNumber: 42 }] }
get_scope_variables         → [{ name: "event", type: "object", value: "MouseEvent" }, ...]
evaluate_at_frame           → expression: "dropTargets.map(t => t.id)"  →  ["list-1", "list-2"]
```

> `evaluate_at_frame` runs in the paused frame's scope and can read local variables, whereas `evaluate_js` runs in the global scope and cannot.
> They stay separate tools so that the pause state — which the caller cannot see — is carried by the tool name instead of an optional
> parameter: `evaluate_js` never silently answers from the wrong scope, and `evaluate_at_frame` never silently answers from the global one.

## Development

●Install dependencies by `pnpm install`, and then:

```
pnpm dev          # development (tsx watch)
pnpm build        # tsc type-check + compile to dist/
pnpm start        # run compiled build
pnpm test         # run vitest
```

TDQS

A4.1/5.0

Scored across 25 tools

Disambiguation5/5

Each tool targets a distinct resource and operation: tab/page control, network capture, console capture, DOM/CSS inspection, and debugger stepping/evaluation are clearly separated. Even closely related pairs like evaluate_js vs evaluate_at_frame and get_debugger_state vs list_breakpoints have explicit scope boundaries in their descriptions.

Naming Consistency4/5

Almost all tools follow a predictable snake_case verb_noun or verb_action pattern: get_*, list_*, set_*, remove_*, navigate_tab, reload_tab, step_over, evaluate_js, clear_captures. The main minor deviation is the noun-only screenshot, so the set is highly consistent but not perfectly uniform.

Tool Count4/5

25 tools is at the upper end, but the server spans several genuine DevTools domains: tabs, DOM/CSS, JS evaluation, network, console, and debugging. Each tool maps to a distinct DevTools operation, though a few debugger controls could conceivably be consolidated.

Completeness4/5

Coverage is strong for inspection and debugging: navigation, screenshots, DOM/CSS reads, JS evaluation, console/network capture, breakpoints, stepping, and scope inspection. Notable gaps remain around DOM interaction, tab lifecycle, script source retrieval, and request headers, but evaluate_js provides partial workarounds for some.

Maintenance

ActivityActive
ResponsivenessNo recent community issues