chrome-dev-mcp
# chrome-dev-mcp
[](https://npmjs.org/package/chrome-dev-mcp) [](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
[](https://github.com/user-attachments/assets/9d4de615-9b96-4780-ac72-c4097083bf8c)
### Inspecting html and css
[](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
Scored across 25 tools
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.
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.
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.
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.