CDP-MCP Server
Detects MobX bindings used through useSyncExternalStore and traces external store changes, including changed paths, snapshots, subscribed components, and the stack of the code that changed the store.
Reads Next.js runtime data from NEXT_DATA and the App Router flight payload for inspection during browser debugging.
Provides React-specific browser debugging: inspect component chains and state, map contexts/providers, trace commits and re-renders, locate component functions in minified bundles, and inspect React internals.
Provides Redux-specific debugging: inspect store state at dot paths, trace actions and payloads, changed state paths, and dispatching call stacks, including actions dispatched from middleware patterns such as thunks and sagas.
Searches the webpack module registry and maps vendor libraries to module ranges, supporting blackboxing and breakpoint placement in minified bundles.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
CDP-MCP Server
A system-wide MCP server that wraps the Chrome DevTools Protocol, giving AI agents full access to low-level browser debugging: breakpoints, stack traces, stepping, scope inspection, network interception, and source maps.
72 MCP Tools
Category | Count | Highlights |
Connection | 5 | Connect, disconnect, list/attach targets, raw CDP commands |
Debugger | 19 | Breakpoints, valid-position lookup, range blackboxing, non-pausing logpoints, stepping, call stacks with async parents, scope inspection, eval on frame, exception breakpoints, blackboxing |
Runtime | 5 | JS evaluation, object property drilling, function → source location, console capture |
Network | 11 | Request monitoring with initiator stacks, response bodies, body search, WebSocket/SSE frames, Fetch interception (continue/fail/fulfill) |
Source | 6 | Script listing, source retrieval, ranged excerpts, search with coordinates, source maps in both directions |
DOM / events | 4 | Break on DOM change, on request URL, on event type; list event listeners with locations |
Coverage | 2 | Which functions ran during one action, with call counts |
Frameworks | 10 | Detect; React and Vue component chains with state; React context map; external store detection; Redux state and action tracing; Next.js data; webpack module search |
Data flow | 8 | One timeline of actions, commits, store changes, writes and calls; value finder; property write watch; function argument/return tracing; render tracing; vendor detection |
Trace | 2 | Cursor-based timeline and UI action markers |
Related MCP server: MCP JS Debugger
Minified bundles and data flow
A production React, Next.js or Vue chunk is usually one very long line, so work by position rather than by file:
source_search:{"query":"saveProfile","urlFilter":"chunk"}— each match has a 1-based line, 0-based column and surrounding text.source_get_excerpt: read a window around that position instead of the whole file.runtime_function_location:{"expression":"getEventListeners($0).click[0].listener"}— go from a live function (handler, component, store action) to its definition.debugger_set_logpoint:{"urlRegex":"chunk.*\\.js$","lineNumber":1,"columnNumber":48213,"expression":"({id: e.id, next: t})"}— records the value on every pass without pausing; read withdebugger_get_logpoint_hits.network_get_requests: each request carriesinitiatorwith the JS stack that sent it.network_search_bodies:{"query":"ord_777"}— find which response, request body, WebSocket frame or SSE message carried a value, then follow itsrequestIdback to the initiator.network_get_stream_frameslists the socket and event-stream traffic.
Body search reads bodies from Chrome on demand, so it only covers the latest 500
requests and bodies Chrome still holds. A response the page never read, one still
loading, or one Chrome has evicted is counted in bodiesUnavailable; binary
responses and binary frames are skipped. The stream buffer keeps the latest 1,000
frames per attachment, each capped at 100,000 characters.
When execution does pause, debugger_get_pause_info includes asyncStack: the
promise, timer or event chain that scheduled the paused code. Logpoint values are
JSON, truncated to 2,000 characters, in a 1,000-entry buffer per attachment; an
expression or condition that is not valid JavaScript is rejected when the logpoint
is set.
Finding the code behind an action
dom_get_event_listenerslists the handlers on an element with their locations; passincludeAncestorsfor frameworks that delegate events to the root.coverage_start, replay one action,coverage_take: the functions that ran, with call counts and positions. Counts reset on each take.xhr_breakpoint,dom_breakpointandevent_breakpointpause in the code that sends a request, changes a node or handles an event. The pause'sreasonisXHR,DOMorEventListeneranddatacarries the engine's details.debugger_get_possible_breakpointslists where the engine can pause near a position;debugger_blackbox_rangesskips vendor code that shares a chunk with application code.
Source maps
Parsed maps are cached per attachment. source_resolve_sourcemap goes from a
generated position to the original, source_find_generated_position from an original
file and line to the running bundle, and debugger_get_call_stack with
resolveSourceMaps: true adds original positions to each frame. Map URLs resolve the
way a browser resolves them (absolute, root-relative and relative). Production sites
often publish no maps; the tools above work without them.
Data flow without source maps
Everything below works on minified production builds. Tracers report into one
timeline kept in the server, read with flow_get_events; it holds the latest 2,000
events per attachment and survives page navigations. Replay a UI action, then read
the timeline to see the chain: action dispatched → state paths changed → components
re-rendered and why.
Event kind | Source | Contents |
|
| Action type and payload, changed state paths, dispatching call stack |
|
| Components that re-rendered and why: changed props, hook indexes, context IDs, or parent only |
|
| Changed paths and snapshot of a non-Redux store, with the stack of the code that changed it |
|
| New and previous value of a watched property, with the writer's stack |
|
| Recorded expression, or a traced function's arguments ( |
|
| The function's return value |
Every stack frame and component location is a generated position (1-based line,
0-based column) that source_get_excerpt and debugger_set_logpoint accept.
Redux. redux_inspect finds the store through react-redux's Provider and reads
state at a dot path. redux_trace start reads the closure that store.getState
shares with Redux's internal dispatch and hooks that function with a non-pausing
breakpoint, so actions dispatched from thunks, sagas and listener middleware are
captured as well as direct ones; the result says "capture":"reducer" when this
worked and "store.dispatch" when it fell back to wrapping the public method. After
a navigation the tracer re-attaches by itself; actions during page startup are
recorded with their stack but without a state diff.
Other stores. react_find_stores finds anything components subscribe to through
useSyncExternalStore — Zustand, Jotai, MobX bindings, modern react-redux — and
reports the subscribed components and where the store's subscribe is defined.
store_trace records its changes. Store IDs are per page load.
Context. Production builds strip context names, so each context gets a stable ID
(ctx3) and a label from the shape of its value ({theme, setTheme}).
react_context_map lists every context with its providers, the component that
computes each provided value and where it is defined, and the components that read
it. react_inspect shows the same IDs per component, and react-commit events name
the context that caused a re-render.
Renders. react_trace_renders installs the hook React reports commits to. React
only registers with a hook that exists before it loads, so the first start usually
needs reload: true. Locate a reported component with runtime_function_location
and the expression __cdpMcpReact.component(<id>).
One value, end to end. flow_find_value reports everywhere a value currently
is: network bodies and frames, Redux paths, external stores, component props, hooks
and class state, context values, DOM nodes and web storage.
Outside any store. watch_property records assignments to one property of a live
object, including DOM properties such as an input's value. debugger_trace_function
records a function's arguments on each call, and with captureReturn its return
value.
Vendor code. source_identify_vendors recognizes libraries by string literals
that survive minification. On webpack builds it resolves to module ranges and
blackbox: true makes stepping skip exactly those; on other bundlers it reports
libraries per script.
Limits:
These tools read React, Redux and bundler internals and place hooks by matching minified code shapes. A new major version can break one of them while the page itself is fine.
The Redux hook needs Redux's own
createStoreclosure. A store with a different implementation falls back tostore.dispatchwrapping, which sees the state change and stack of a middleware-internal dispatch but not its action object.captureReturnpauses the page for an instant at every return of the traced function; avoid it on hot functions. Argument capture never pauses. Destructured parameters of arrow functions are not captured.watch_propertysees assignments to the property, not mutation inside its value, and cannot watch non-configurable properties.react_trace_rendersreports at most 30 components per commit and 25 commits per second; the rest are counted, not listed.Tracers change the page: they wrap
store.dispatch, redefine watched properties and set debugger breakpoints.stop/untracerestore each one; a detach removes the breakpoints.
Framework helpers
framework_detect, react_inspect, vue_inspect, next_get_data and
webpack_find_modules read framework internals from the page: React fibers, Vue 2/3
component instances with Pinia/Vuex state, __NEXT_DATA__ and the App Router flight
payload, and the webpack module registry. Component and module results include
definedAt, a position usable for breakpoints and logpoints. These internals are not
public API and change between versions, so a helper can fail on a page that is
otherwise fine. The automated tests exercise them against stand-in objects shaped
like each framework's internals; they were also checked by hand against production
builds of React 19 with the Next.js Pages Router and webpack, Vue 3.5, and the Next.js
App Router with Turbopack, and a production Redux Toolkit app that also uses Zustand.
Vue 2, Pinia/Vuex, webpack 4, Jotai, MobX and redux-saga are covered by neither.
Turbopack's registry is not supported.
source_search and coverage_take leave out browser-extension content scripts
unless a urlFilter or scriptId selects them. webpack_find_modules pushes one empty chunk into the
page's chunk array to obtain the require function.
Tracing lazy-loaded SPAs
Connecting or attaching installs script listeners before enabling Debugger, so
source_list_scripts includes existing scripts immediately. Set a URL or URL-regex
breakpoint before replaying an action to catch a chunk that has not loaded yet.
debugger_set_breakpoint returns locations from CDP; an empty array means the
breakpoint has not resolved yet. Later breakpointResolved events update
debugger_list_breakpoints and appear in the timeline.
All public generated coordinates use 1-based lines and 0-based columns,
including breakpoint inputs, resolved locations, call stacks, script inventory,
source-map inputs, and trace events. Column defaults to 0. This changes the old
breakpoint column convention (previously documented as 1-based) and the inventory's
startLine/endLine convention. Raw cdp_send_command requests and replies retain
native CDP coordinates (both 0-based).
Every tool's text JSON and structuredContent use this envelope:
{
"targetId": "chrome-target-id",
"sessionId": "attachment-uuid",
"data": { "paused": false, "callFrames": [] }
}Clients should read the former result from data. sessionId is a server-generated
attachment UUID, not a flattened CDP protocol session ID. Reattaching to the same
target generates a new UUID. Disconnected results have null IDs. Disconnect,
target switch, failed attachment, and unexpected socket loss clear manager state,
subscriptions, breakpoints, pause state, scripts, console/network buffers, timers,
and the timeline. Old asynchronous callbacks cannot populate a replacement session.
Pending steps reject on session loss; commands spanning a switch return an error
rather than evidence labeled with the new target.
Automatic reattachment
If the Chrome connection is missing when a tool is called — the socket dropped, or
the MCP host restarted this server — the server attaches again before running the
tool. After a drop it returns to the same target; in a fresh server process it
behaves like cdp_connect. The envelope of that call carries "reconnected": true
and a notice, because the new session starts with no breakpoints, interception,
buffers or timeline. It never moves to a different tab by itself: if the previous
target is gone the call fails and asks for cdp_list_targets/cdp_attach_target.
After an explicit cdp_disconnect nothing reattaches until cdp_connect or
cdp_attach_target is called. cdp_connect skips DevTools windows when choosing
the first page.
Example MCP tool sequence (arguments shown as JSON):
cdp_attach_target:{"targetId":"..."}debugger_set_breakpoint:{"urlRegex":"lazy-chunk.*\\.js$","lineNumber":1,"columnNumber":86}trace_mark:{"label":"click Search"}— savedata.cursor.Replay the UI action, then inspect pause info, resolved breakpoints, and scopes.
trace_get_events:{"cursor":123,"limit":100}— use the mark's cursor minus one to include the mark, or the mark's cursor to read events after it.Continue with the returned
data.nextCursorwhiledata.hasMoreis true.
The timeline retains at most 1,000 events per attachment: scriptParsed,
breakpointResolved, paused, resumed, script network requests (scriptRequest),
and mark. Cursors are exclusive, strictly increasing within one MCP server
instance, and are not reused after switching targets. truncated signals that
events after the supplied cursor were evicted or cleared on session reset;
oldestCursor identifies the oldest retained event. Persist events externally
before switching targets if you need a cross-tab investigation history.
Each event carries target/session IDs and a Unix-millisecond receipt timestamp.
Use cursor order for arrival order; this is not a claim about browser causal timing.
URLs, script IDs, and generated coordinates are included when CDP makes them
available. Script network requests carry request ID, URL, and method; CDP does not
supply a script ID or generated location at that stage, so correlate with subsequent
scriptParsed events by URL. Resume events carry the preceding pause's top location.
Initial script events replayed by Debugger.enable describe inventory, not new loads.
The trace records no request/response bodies, cookies, authorization headers, other headers, console values, or scope values. It does retain URLs and explicit marker labels. The existing explicit network and scope inspection tools retain their behavior. Step over/into/out wait for a new pause event and command reply, with a 5-second timeout; a timeout is returned as an MCP tool error.
Tests
npm run build
npm test # build, regression tests, and real Chromium acceptance
npm run test:unit # regression tests only
npm run test:acceptance # real Chromium two-tab fixture onlyThe acceptance test requires Chromium (/usr/bin/chromium by default; override
with CHROMIUM_PATH) and permission to bind loopback ports and launch a headless
browser. It uses a temporary browser profile and a local HTTP fixture, verifies a
pre-load breakpoint in a single-line dynamically imported chunk, local/closure
scopes, stepping, ordered trace events, and isolation across target switches,
reattachment, disconnect, failed attachment, and target closure. MCP tool calls use
the SDK's in-memory transport and a real CDP connection. No account or external
website is involved.
Quick Start
# Install
npm install
# Build
npm run build
# Launch Chromium with debugging
chromium --remote-debugging-port=9222
# Run (stdio mode for single agent)
node dist/bin/cdp-mcp-server.js --stdio
# Run (HTTP mode for multi-agent)
node dist/bin/cdp-mcp-server.js --http --port 3100In HTTP mode each MCP session (one per initialize) gets its own server and Chrome
attachment, addressed by the mcp-session-id header on POST, GET and DELETE /mcp.
DELETE ends a session; sessions whose client vanishes stay open until the server
stops. GET /health reports the number of active sessions.
MCP Client Config
{
"mcpServers": {
"cdp-debugger": {
"command": "node",
"args": ["/path/to/cdp-mcp-server/dist/bin/cdp-mcp-server.js", "--stdio"]
}
}
}CLI Options
--stdio Stdio transport (default)
--http HTTP transport
--port <n> HTTP port (default: 3100)
--chrome-port <n> Chrome debug port (default: 9222)
--chrome-path <p> Chrome binary (default: /usr/bin/chromium)
--profile isolated Temp profile (default)
--profile real Use real profile
--attach Only attach, never launch
--launch Always launch new instanceArchitecture
Built from scratch — No forks, full control
Browser page debugging — Not Node.js-only like alternatives
Never kills your Chrome — Detaches on exit; only a browser this server launched itself is terminated
Network interception safety — 30s auto-continue watchdog, emergency cleanup on crash
Dual transport — stdio for single-agent, HTTP for multi-agent
Guarded raw access —
cdp_send_commandrefuses the few commands that would desynchronize the server (Debugger/Runtime/Network.disable,Fetch.enable/disable)
License
MIT
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Related MCP Connectors
Live browser debugging for AI assistants — DOM, console, network via MCP.
Control a real browser from AI agents: navigate, click, type, screenshot, read the page.
Cloud browser, web extraction, screenshots, and remote CDP tools for AI agents.
Give AI agents a real cloud Chrome: open sessions, navigate, read pages, click, type and screenshot.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to debug Node.js applications using Chrome DevTools Protocol. Provides comprehensive debugging capabilities including breakpoints, stepping, variable inspection, expression evaluation, and console monitoring.5,737 npm350MIT
- AlicenseAqualityDmaintenanceEnables AI assistants to debug JavaScript and TypeScript applications by connecting to Chrome DevTools Protocol-compatible debuggers, allowing them to set breakpoints, step through code, inspect variables, and evaluate expressions with full source map support.1810 npm2Apache 2.0
- -licenseNot gradedqualityNot gradedmaintenanceEnables AI coding assistants to control and inspect a live Chrome browser for automated debugging, performance analysis, and web interaction. It leverages Puppeteer and Chrome DevTools to provide capabilities like network monitoring, console logging, and automated browser actions.-
- AlicenseBqualityBmaintenanceEnables AI agents to debug code and automate browsers using Chrome DevTools Protocol, supporting breakpoints, variable inspection, and replayable interaction recording.35548 npm19MIT