Skip to main content
Glama
chrome-dev-mcp

chrome-dev-mcp

chrome-dev-mcp

npm chrome-dev-mcp package chrome-dev-mcp MCP server – quality and maintenance score on Glama

●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

Inspecting html and css

Inspecting html and css

Related MCP server: chrome-devtools-mcp-lite

Why This Exists

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

●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:

"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:

"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:

"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 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 requestIds 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:

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

Available Tools

25 tools
clear_capturesA
DestructiveIdempotent

Discard the buffers this server holds, so a following get_console_logs / get_network_requests shows only what happens next. Affects this server only: nothing is cleared in Chrome or in the DevTools UI, and capture keeps running — no reconnect or reload is needed. Cannot be undone. Console entries are gone for good, because Console.enable() replays history only at attach time. Cleared network requestIds still resolve in get_network_response_body for as long as Chrome itself holds the body; once Chrome drops it the error says the data was discarded rather than that the id is unknown. Works without a connected tab.

Inputs: targets. Returns: cleared

evaluate_at_frameA

Evaluate a JavaScript expression in the scope of a paused call frame — it reads local variables, closure variables and the current this, which evaluate_js cannot. Only works while execution is paused: when it is not, this returns an error rather than silently falling back to global scope. Results come back as a preview — class name plus a first level of properties, marked … where Chrome truncated it — readable, not parseable as the value. Use get_debugger_state to find frame indices.

Inputs: expression (required), frameIndex.

evaluate_jsA

Evaluate a JavaScript expression in the page, in global scope, with the same semantics as the DevTools console. Returns the real value when it serialises; objects that cannot (DOM nodes, Errors, Maps, class instances) come back as a preview instead: class name plus a first level of properties, marked … where Chrome truncated it — readable, not parseable as the value. Top-level await works, but an expression that merely RETURNS a promise is NOT awaited — it comes back as a pending Promise, exactly as in the console. The call returns as soon as your expression finishes its synchronous work, before queued microtasks run, so the state triggered by a click is not visible in the same call: put await Promise.resolve() between the click and the read, or read in a second call. At a breakpoint this still evaluates globally and cannot see local or closure variables — use evaluate_at_frame for those. For the element selected in the Elements panel ($0), use get_inspected_element.

Inputs: expression (required).

get_computed_styleA
Read-only

Get computed CSS values for the given properties on the element matched by selector.

Inputs: selector (required), properties (required). Returns: styles

get_console_logsA
Read-only

Return browser console messages and uncaught exceptions, including the history already visible in DevTools before this server connected. Exceptions are reported with their full stack trace (source-mapped when available). Reloading or navigating the page does NOT clear this buffer: output from the old document stays and the new one is appended to it, so an entry carries from when it predates the document showing now. Replayed entries carry no timestamp — Chrome hands its backlog over without one.

Inputs: level, limit, since. Returns: logs

get_debugger_stateA
Read-only

Get the debugger state: whether execution is paused, the pause reason, hit breakpoints and the full call stack. A pause is reported whatever caused it. hitBreakpoints lists only breakpoints set through this server, so it is empty at a breakpoint set in DevTools. Positions are 1-based, and source-mapped where the script has a source map. A source-mapped frame also gives compiledUrl/compiledLine/compiledColumn, its position in the loaded script.

Inputs: no named parameters. Returns: note, paused, reason, callStack, +2 more

get_htmlA
Read-only

Get the full HTML source of the currently connected tab (document.documentElement.outerHTML). Truncated to 20000 characters for large pages; a truncated result ends with a … marker giving how much was cut and the real length of the document, so a short result is never ambiguous between "small page" and "cut off here".

Inputs: no named parameters.

get_inspected_elementA
Read-only

Get the element marked for MCP inspection. To mark an element: select it in the Elements panel, then run window.$0 = $0 in the DevTools console. Returns an error if no element is marked.

Inputs: no named parameters. Returns: id, tagName, className, outerHTML, +1 more

get_network_requestsA
Read-only

Return HTTP requests captured from the connected tab — method, URL, resource type, status, transferred size, duration, initiator, and failure reason. Capture starts when this server connects to the tab: requests issued before that are NOT visible (unlike get_console_logs, which replays pre-connect history). Requests belonging to a previous page are pruned on navigation, mirroring the DevTools Network panel default; the new document request itself is kept. A redirect chain appears as one record per hop, sharing a requestId and distinguished by hop. WebSocket frames are not captured. In a bulk listing, a URL ending in … is truncated and not usable — query its requestId for it in full. size is compressed on-the-wire bytes; canceled marks an abort (e.g. navigating away), usually not a real failure.

Inputs: limit, status, requestId, urlFilter, +2 more. Returns: requests

get_network_response_bodyA
Read-only

Fetch the response body for one requestId from get_network_requests. Bodies are never buffered by this server — they are read from Chrome on demand, and Chrome discards them on navigation or when its own buffer limits are exceeded, so fetch promptly and before navigating away. Chrome stores at most one body per requestId, so for a redirect chain only the final hop has a body. Returns a JSON metadata block, then the body as a separate text block — kept separate so a large body is not JSON-escaped. Metadata carries requestId, base64Encoded (boolean; true when the body is base64, as CDP reports it), byteLength and method/url/status/mimeType, the last four replaced by a note when the capture buffer no longer holds the request, plus truncated: true when the body was cut at 50000 characters. A binary body is never returned: metadata carries omitted and there is no second content block.

Inputs: requestId (required).

get_page_infoA
Read-only

Get the URL and title of the currently connected tab, as { url, title }. Still answers while the page is not responding.

Inputs: no named parameters. Returns: url, title

get_scope_variablesA
Read-only

Inspect variable values in a call frame scope. Only works when execution is paused. Use get_debugger_state first to find available frame indices.

Inputs: scopeType, frameIndex. Returns: variables

list_breakpointsA
Read-only

List breakpoints tracked by this server (set via set_breakpoint). Breakpoints set outside this server (DevTools UI, other CDP clients, prior sessions) are not visible — CDP has no API to enumerate them.

Inputs: no named parameters. Returns: breakpoints

list_tabsA
Read-only

List open Chrome page tabs with their targetIds, titles and URLs. active marks a tab showing on screen — the selected tab of a window that is not minimised or fully covered, so several windows mean several — and is absent if the tab did not answer. connected marks the tab this server acts on; selecting another tab in Chrome does not change it, only switch_tab does.

Inputs: no named parameters. Returns: tabs

pause_executionA
Idempotent

Pause JavaScript at the next statement that runs, and return the call stack if that happens within 2s. On an idle page nothing runs, so the pause stays pending until script next does — a timer, an event, a click — and get_debugger_state reports it once it lands.

Inputs: no named parameters.

reload_tabA
Destructive

Reload the connected tab and wait up to 30s for the new page's load event — or for execution to pause first, e.g. on a breakpoint hit during startup, in which case the call stack is returned. outcome is "loaded", "paused" or "still-loading" (the new page is in place but its load event had not fired by the deadline). Breakpoints carry over to the reloaded page. Also works while paused, no need to resume first: the pause ends with the old page.

Inputs: ignoreCache. Returns: url, note, title, reason, +3 more

remove_breakpointA
Destructive

Remove a breakpoint by ID. The ID is invalidated; use set_breakpoint to restore (returns a new ID).

Inputs: breakpointId (required).

resume_executionA
Destructive

Resume execution while paused; it runs until the next breakpoint or pause. Returns at once rather than waiting for one.

Inputs: terminate.

screenshotA
Read-only

Capture a PNG screenshot of the current viewport (the visible page area only — not the full scrollable page, not the browser chrome, not DevTools), or of one rectangle of it with region. Captured at the tab's native pixel size unless you cap it with maxEdge; a capture that was scaled or cut says so in a note beside the image, which for a region also gives the CSS rect the image covers and how many image pixels a CSS pixel became. A tab that is not painting is raised in its window first, which changes which tab is selected there.

Inputs: region, maxEdge.

set_breakpointA
Idempotent

Set a breakpoint at a 1-based line (and column) of a file. Name the file with source — a path like "src/App.tsx", or the url of a frame get_debugger_state or get_console_logs reported. Either way the position is mapped through source maps into the code Chrome runs, so that code must already be loaded. url/urlRegex instead take a position in the script exactly as Chrome loads it, so a source-mapped position given to them lands on the wrong line or never binds; use a frame's compiledUrl/compiledLine/compiledColumn. The script need not be loaded yet: an empty resolvedLocations means no loaded script matches yet, and the breakpoint binds when one loads — or never, if the URL is wrong. A source breakpoint is removed once its file changes, so set it again after an edit; the next state or navigation result lists it under droppedBreakpoints. A url/urlRegex breakpoint is kept through edits, so it may then stop on other code, or not at all.

Inputs: lineNumber (required), url, source, urlRegex, +2 more. Returns: note, breakpointId, resolvedLocations

step_intoA

While paused, step into the function the current statement calls, or to the next statement if it calls none. After an event handler or timer callback returns, the step stays in effect: it pauses at the first statement of whatever script runs next, which may be unrelated code. Returns the new call stack, or a note if execution has not paused again within 5s.

Inputs: no named parameters.

step_outA

While paused, run the rest of the current function and pause in its caller. An event handler or timer callback has no caller, so stepping out of one pauses in whatever script runs next. Returns the new call stack, or a note if execution has not paused again within 5s.

Inputs: no named parameters.

step_overA

While paused, run the current statement without stepping into the functions it calls, and pause at the next statement. After an event handler or timer callback returns, the step stays in effect: it pauses at the first statement of whatever script runs next, which may be unrelated code. Returns the new call stack, or a note if execution has not paused again within 5s.

Inputs: no named parameters.

switch_tabA

Connect this server to another Chrome tab, without bringing it to the front. Breakpoints this server set in the previous tab are removed. A pause there ends, unless DevTools is open on that tab. The console and network buffers are emptied and refilled from the new tab.

Inputs: targetId (required). Returns: url, title, targetId

Tool Schema Changelog

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

Loading tool schema changelog…

    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

    ActivityNo data
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • -
      license
      Not graded
      quality
      Not graded
      maintenance
      MCP server that connects AI agents to a real Chrome browser via a WebSocket extension bridge, enabling over 40 browser control tools without debug mode or profile isolation.
      -
    • F
      license
      Not graded
      quality
      D
      maintenance
      MCP server that connects AI agents to browser DevTools via CDP, enabling real-time access to console logs, network requests, and page state.
      -