| name | cdp-tools |
| description | Debug JavaScript/TypeScript running in Chrome or Node.js via the cdp-tools MCP server - set breakpoints and logpoints, inspect call stacks and variables, monitor console/network activity, automate browser interactions (navigate, click, type, screenshot), manage dev servers, and record/replay reproduction sequences with automated fix verification. Use whenever a task involves debugging a running app, reproducing or verifying a bug, tracing runtime behavior, or the user mentions breakpoints, Chrome DevTools, CDP, replay sequences, or cdp-tools MCP tools (launchChrome, navigate, breakpoint, inspect, replay, server, issues, etc.). |
| compatibility | Requires the cdp-tools-mcp MCP server to be connected (tools such as launchChrome, breakpoint, inspect, replay, server, issues). |
| version | 0.7.1 |
cdp-tools Debugger Usage
Chrome DevTools Protocol debugging for JavaScript/TypeScript in Chrome, Node.js, or CDP-compatible environments.
Quick Start
Web apps (most common):
1. launchChrome({ reference: "your-descriptive-name" }) # Auto-connects, ready immediately
2. navigate({ action: 'goto', connectionReason: "your-descriptive-name", url: "..." })
# Navigation automatically caches interactive elements (links, buttons, inputs) for the page
3. content({ action: 'findInteractive', connectionReason: "your-descriptive-name" })
# Shows summary of all interactive elements. Use search/types to filter
4. content({ action: 'extractText', mode: 'outline' }) # Read page content (preferred over screenshot)
5. Use other tools as needed with connectionReason parameter
Alternative (rename later):
1. launchChrome() # Uses default "unnamed-connection-default"
2. tab({ action: 'rename', reference: "unnamed-connection-default", newReference: "your-name" })
3. Use other tools with connectionReason: "your-name"
Node.js debugging:
1. Start app: node --inspect=9229 app.js
2. connectDebugger({ reference: "my-app-debug", port: 9229 })
3. breakpoint({ action: 'set', connectionReason: "my-app-debug", ... })
Basic Workflow
- Connect:
launchChrome({ reference: "name" }) - Launches AND auto-connects (ready immediately, don't call connectDebugger)
connectDebugger({ reference: "name" }) - Only for existing Node.js/remote debuggers
- Navigate & interact: Use connectionReason in all tool calls
navigate({ action: 'goto', connectionReason: "name", url: "..." })
input({ action: 'click', connectionReason: "name", selector: "..." })
- Debug:
breakpoint({ action: 'set', connectionReason: "name", ... })
- Inspect when paused:
inspect({ action: 'getCallStack', ... }) → inspect({ action: 'getVariables', ... })
- Monitor:
console({ action: 'list', connectionReason: "name" }), network({ action: 'list', connectionReason: "name" })
Key Practices
Breakpoints:
- Use conditional:
breakpoint({ action: 'set', condition: "userId === '123'" })
- Prefer
breakpoint({ action: 'setLogpoint' }) for loops/high-frequency code
- Clean up with
breakpoint({ action: 'remove' }) or check breakpoint({ action: 'list' })
DOM/Event/XHR Breakpoints (Chrome only):
breakpoint({ action: 'setDOMBreakpoint' }): Pause when element changes
subtree-modified: Children added/removed
attribute-modified: Attributes changed (class, style, etc.)
node-removed: Element deleted from DOM
breakpoint({ action: 'setEventBreakpoint' }): Pause when events fire (click, submit, input, keydown, etc.)
breakpoint({ action: 'setXHRBreakpoint' }): Pause when XHR/Fetch URL contains pattern
- Example:
breakpoint({ action: 'setDOMBreakpoint', selector: '.todo-list', domBreakpointType: 'subtree-modified' })
- Note: DOM breakpoints use nodeIds which are invalidated on page reload
Code search:
inspect({ action: 'searchCode' }): Find patterns
inspect({ action: 'searchFunctions' }): Locate definitions
getSourceCode: View context
Modal handling:
- Use
handleModals: true on input({ action: 'click' | 'type' | 'hover' })
- Strategies:
auto (smart), accept, reject, close, remove
- Example:
input({ action: 'click', selector: '.btn', handleModals: true, dismissStrategy: 'auto' })
- Limitation: English-only, no Shadow DOM/iframes
Multiple connections:
listConnections → switchConnection
- Each connection = separate tab/process
Re-running work you already did:
- Every tool response carries its own history index in the footer:
**Repeat:** replay({ action: 'repeat', indices: [58] }). That is not only
for failures - it is on every call, all the time. (The replay tool's own
responses are the exception: replay calls are not recorded into history)
indices takes a list, so a whole stretch of work replays in one call:
replay({ action: 'repeat', indices: [58, 59, 60, 61] }) re-runs those four
steps in order
- Reach for this whenever you are about to redo something you already did -
a browser relaunch, re-logging in, retyping a form, getting back to the
screen where a bug appears. Re-issuing the calls by hand is slower, and
retyped arguments drift from what actually ran
replay({ action: 'history' }) lists the indices when they have scrolled
out of view
- Each repeated call replays against the connection it was recorded with, so a
batch spanning two browsers stays on both. Pass
connectionReason on every
call while driving multiple browsers and this holds; drive one implicitly and
those calls have no connection to replay against
- If the stretch is worth keeping, turn it into a sequence:
replay({ action: 'create', name: '...', indices: [58, 59, 60, 61] })
Common Patterns
Bug debugging:
launchChrome → navigate({ action: 'goto' })
inspect({ action: 'searchCode' | 'searchFunctions' })
breakpoint({ action: 'set' | 'setLogpoint' })
- Trigger bug
inspect({ action: 'getCallStack' }) + inspect({ action: 'getVariables' })
inspect({ action: 'evaluateExpression' })
Performance:
network({ action: 'enable' })
navigate({ action: 'goto' })
network({ action: 'search' }) (find slow)
network({ action: 'get' }) (timing)
breakpoint({ action: 'setLogpoint' }) in slow paths
Frontend state:
dom({ action: 'querySelector' }) + dom({ action: 'getProperties' })
storage({ action: 'getLocalStorage' }) + storage({ action: 'getCookies' })
inspect({ action: 'evaluateExpression' })
dom({ action: 'snapshot' })
UI verification:
content({ action: 'verify' }) - Run all default checks
- Reports: dead buttons, small touch targets, overflow clipping, dead links, viewport issues
- Filter checks:
checks: ['handlers', 'touch'] for specific issues
- Available checks:
handlers, viewport, touch, overflow, clickability, links, scroll
Important Notes
- After
launchChrome(): You are ALREADY connected. Do NOT call connectDebugger(). Use the reference parameter when launching, or rename later with tab({ action: 'rename' })
- Interactive elements cache: Navigation (goto, reload, back, forward) automatically caches all interactive elements. Cache expires after 5 minutes.
findInteractive shows a summary by default; use search or types parameters to filter elements
- Logpoint limits: Default 20 executions. Use
breakpoint({ action: 'resetCounter' }) or adjust maxExecutions
- Expression failures: Wrapped in try-catch, shows
[Error: message]. Search: console({ action: 'search', pattern: "Logpoint Error" })
- CDP line mapping: May map to nearest valid line. Use
breakpoint({ action: 'validate' }) first
- Source maps: Auto-handled. Use
loadSourceMaps for manual
- File paths: Full URLs (
http://localhost:3000/app.js) or file://
- Network monitoring: Must enable with
network({ action: 'enable' })
- Working an issue:
comment on it as you go - once when you start (what you're about to change and why) and once when you finish (what you actually changed, files touched, tests added, and anything that contradicts the issue as written). The issue is the durable record; someone reviewing later reads the timeline, not your diff
- Closing an issue:
issues({ action: 'resolve' }) waits on a browser overlay only a human can click - don't call it unattended, use issues({ action: 'comment' }) to record findings instead
Recovering from a failed tool call
Two different mechanisms fix two different failure points - don't confuse them.
1. Missing/invalid parameters -> continuationToken (fix and resubmit, cheaply)
If a call fails validation (code: 'MISSING_PARAMETERS' or 'INVALID_PARAMS'), the error includes a continuationToken and a missingParameters list (name/type/description/enum). Don't resend the whole call - retry with just:
{ continuationToken: '<token>', <only the missing/bad field(s)> }
The server merges this with what you already sent and re-validates. Repeat (same token) until it succeeds. The token expires after 5 minutes. This only applies to calls that never passed validation in the first place - it has nothing to do with guard blocks below.
2. A validated call gets blocked by a guard (port failure, dead server, breakpoint pause, etc.) -> replay
Once a call passes validation, cdp-tools records it (even if a guard then blocks it before the handler runs) and every response footer includes a hint like:
**Repeat:** `replay({ action: 'repeat', indices: [N] })`
Acknowledge whatever blocked it (e.g. server({ action: 'acknowledgePort' }), server({ action: 'acknowledgeStartup' })), then use that replay hint to resume the exact same call - do not reconstruct the arguments by hand, and do not try to reuse a continuationToken for this case (that mechanism is for fixing bad input, not for retrying a call that was already valid).
Note this is only one use of repeat. The footer hint is on every response, not just blocked ones, and indices takes a list - see "Re-running work you already did" above.
Restarting cdp-tools
If cdp-tools itself seems stuck or broken (not the target app), restart it yourself rather than asking the user to reconnect - don't wait to be told to.
- Preferred:
config({ action: 'restart' }). Under the hood this reads .cdp-tools/mcp-supervisor.pid and sends the running mcp-supervisor process a SIGUSR2, the same signal npm run build's postbuild hook sends automatically after a rebuild. The supervisor replays the original MCP initialize handshake to the freshly spawned child, so the host session never needs to reconnect.
- If that returns
CONFIG_RESTART_NOT_SUPERVISED (this server isn't running through the supervisor - e.g. a bare node build/index.js), fall back to Bash: kill -USR2 $(cat .cdp-tools/mcp-supervisor.pid).
- If it returns
CONFIG_RESTART_STALE_PID, the supervisor died without cleaning up its pidfile - ask the user to run /mcp to reconnect.
Expect the triggering call itself to come back as an error - that's normal, not a failure. In practice config({ action: 'restart' }) almost never returns its own CONFIG_RESTART_REQUESTED success message: the old process gets torn down before it can flush that response, so the supervisor's restart-coordinator answers with a synthesized MCP error -32000: MCP server is restarting; this request will not receive a response from the previous process. Please retry. instead. Just retry the next call - it'll hit the freshly restarted (and by then ready) process. Two things to expect on that next call: it runs against a new PID (visible in tool response footers), and any acknowledged monitored-port failures (server({ action: 'acknowledgePort' })) reset and may need re-acknowledging, since that state lived in the process that just got replaced.
config({ action: 'status' }) says which build is answering: the version, the entry file it loaded, that file's timestamp, and the server/supervisor pids. If you have just rebuilt cdp-tools and the behaviour still looks old, check that timestamp before believing the code - a build signals the supervisor named in its own project's pidfile, which is not always the one serving this session.
Either way, a restart kills any Chrome instances this session launched (relaunch with launchChrome), but managed dev servers (server tool) survive and reattach automatically. config({ action: 'reload' }) is different and lighter-weight - it hot-applies most config.json edits without a restart; a restart is only needed for tools.enabled/tools.disabled changes (the tool list is frozen at server startup) or when the process itself is actually stuck.
Tool Categories
The full list of tools grouped by category (connection, tab, breakpoint, execution, inspection, source, console, network, page, DOM, content, screenshot, input, modal, storage, HTTP/assertions, issues, server, replay, dashboard, config) is not needed for most tasks. Load it only when you need to look up a specific tool name or action:
references/tool-categories.md
Replay Sequences
Recording or replaying a sequence - to build a repro, a regression test, or a
multi-device flow - has its own workflow: capturing values mid-run with
saveAs, per-step connectionReason, conditionals, and verifying an issue's
fix. Load it when the task involves sequences:
references/sequences.md