Add `get_css_styles` tool which exposes matched styles and the CSS
cascade to empower accurate CSS debugging workflows.
It integrates `CssFormatter` into `McpPage` and `McpResponse`, adding
container query resolution and rule pagination support.
Co-authored-by: Samiya Caur <samiyac@chromium.org>
Fixes#2598
This lets people set filesystem roots through the MCP server or the
Chrome DevTools CLI. Multiple roots work together with any roots sent by
the MCP client, and temp directory access stays the same.
I added tests for the flags and root handling.
---------
Co-authored-by: Joseph B <289838966+joebasrawi@users.noreply.github.com>
Co-authored-by: Joseph B <joebasrawi@users.noreply.github.com>
`--screenshotFormat` says JPEG and WebP "helps reduce context size in AI
conversations". The byte saving is real, but it does not reach the
context: image tokens scale with the image's dimensions, not with the
size of the encoded payload.
Token delta on each Claude turn that followed exactly one image, with
the surrounding text small enough that the image dominates it, grouped
by pixel count so only the format differs:
| pixels | format | n | median bytes | median tokens |
| --- | --- | ---: | ---: | ---: |
| 1.5-1.8 MP | jpeg | 115 | 326 KB | 2362 |
| 1.5-1.8 MP | png | 298 | 357 KB | 2361 |
| 2.5-2.8 MP | jpeg | 45 | 444 KB | 3574 |
| 2.5-2.8 MP | png | 24 | 252 KB | 3604 |
Same pixels, same cost. Each delta carries a small fixed per-turn
overhead, so read the pairs against each other rather than as absolute
image cost. The formats come from separate captures rather than one
image encoded twice, so the byte column shows only that cost ignores it
– in the second pair the larger files are the cheaper ones.
The numbers are
[Claude](https://platform.claude.com/docs/en/build-with-claude/vision)'s,
but [GPT](https://developers.openai.com/api/docs/guides/images-vision)
and [Gemini](https://ai.google.dev/gemini-api/docs/tokens) price images
the same way, by pixel dimensions in patches or tiles. Neither charges
on encoded bytes.
`--screenshotMaxWidth` just below promises the same thing and does
deliver, since it changes the dimensions.
New wording:
> Override the default output format used by take_screenshot when the
caller does not specify one. JPEG and WebP are ~3-5x smaller than PNG,
which reduces transfer and storage size. To reduce context size use
--screenshotMaxWidth / --screenshotMaxHeight, since image tokens scale
with dimensions rather than encoded bytes. Unset preserves the existing
default ("png").
`docs/configuration.md` is generated from that description and updated
to match.
Removes `--experimental-page-id-routing` making it the default behavior.
To go back to the previous behavior, pass `--pageIdRouting=false` when
starting the server.
---------
Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>
Co-authored-by: Samiya Caur <samiyac@google.com>
Replace separate minimum and maximum heap-size filters with range
strings. Values can use byte counts or units, such as `1024-2048`,
`1MB-2MB`, `-1MiB`, and `1GiB-`. A bare value such as `10M` is treated
as a minimum.
The range syntax is used by `query_heapsnapshot_objects` for retained
and self sizes and by `get_heapsnapshot_edges` for retained sizes.
`query_heapsnapshot_objects` allows the agent to filter objects by
properties like class name, self size, retained size, property name
and/or detachedness. The resulting list of objects can be sorted as
well.
I noticed that we validate file paths locally that are handled by
potentially remote browsers. This PR extends `verifyFilesSchema` to
validate file paths for local or remote browsers or both + test
coverage.
---------
Co-authored-by: Nikolay Vitkov <34244704+Lightning00Blade@users.noreply.github.com>
Prints the size of objects kept alive by context objects in the heap
snapshot in the initial overview. This can help guide the agent into
looking into such objects in more detail.
Add additional parameters to get_heapsnapshot_edges for filtering and
sorting the emitted edges. By default this tool now does not print edges
to primitive values like true/false/null/undefined anymore unless
explicitly enabled. Edges are also sorted by retained size by default.
Closes#1217. `upload_file` only took a single path, so there was no way
to fill an `input[type=file][multiple]` in one call.
`filePath` now also accepts an array of paths. A plain string still
works for the single-file case, so existing callers don't change —
Puppeteer's `uploadFile`/`fileChooser.accept` already take multiple
paths, so the handler just normalizes to an array and forwards them.
The union (`string | string[]`) needed a bit of plumbing: the CLI
generator threw on non-scalar types, the docs generator rendered it as
`unknown`, and the telemetry transformer didn't know `ZodUnion`. The CLI
exposes the single-value form (it can't pass arrays anyway), the docs
show `string or array`, and metrics count it like an array with scalars
normalized to one. Regenerated artifacts are included.
Added a test that uploads two files to a multi-file input and asserts
both land, plus telemetry coverage for the union. `npm run typecheck`,
lint/prettier, and the upload + transformation tests all pass.
---------
Co-authored-by: Nikolay Vitkov <34244704+Lightning00Blade@users.noreply.github.com>
## What / Why
Adds an opt-in Progressive Web Apps tool category that exposes the
installed web app lifecycle to MCP clients and agents.
The browser-level PWA APIs were introduced in Puppeteer 25.4.0
(puppeteer/puppeteer#15235). The current base uses Puppeteer 25.5.0,
which also enforces URL restrictions for browser-level PWA operations
(puppeteer/puppeteer#15271).
## Tools
- `install_pwa`
- `launch_pwa`
- `get_os_app_state`
- `uninstall_pwa`
The category is disabled by default and enabled with `--category-pwa`.
These are browser-scoped operations and do not require a selected page.
Callers provide the resolved manifest ID and, for installation, an
explicit page or bundle URL. `open_current_page_in_app` and automatic
current-page installation are intentionally deferred until Puppeteer
exposes page-native APIs with reliable loaded-document and
browser-context semantics.
## Compatibility and safety
- Requires a pipe-launched browser. The CLI rejects `--category-pwa`
when combined with `--auto-connect`, `--browser-url`, or
`--ws-endpoint`.
- Puppeteer rejects PWA operations when URL allow/block restrictions are
configured, preventing browser-internal PWA fetch and redirect flows
from bypassing the configured policy.
- File-based install bundles are validated against the MCP filesystem
roots.
- DevTools installation defaults to browser display mode unless
`displayMode: "standalone"` is supplied. Launch uses the app's saved
display preference.
## Testing
- PWA tests cover lifecycle/state, standalone launch, successful
explicit-URL launch, use without a selected page, file-bundle path
validation, OS-state output, and selected-page fallback after uninstall.
- Existing off-by-default category tests cover PWA tool registration and
opt-in exposure.
- Generated README, tool reference, CLI metadata, and telemetry metrics
are updated.
- Type checking, formatting, targeted tests, broad supporting repository
tests, third-party notice validation, and diff checks pass locally.
- Validated against Chrome for Testing 150 and a local Chromium/Edge
build.
- A real stdio MCP lifecycle passed end to end with the final four-tool
design: category gating, explicit install, OS state, standalone launch,
selecting and scripting the returned page, uninstall, selection
fallback, and post-uninstall state rejection.
Refs #2270
Closes#2338
Adds a `waitForStableDom` parameter to `evaluate_script` (default
`true`). Passing `false` skips just the stable DOM wait in
`waitForEventsAfterAction()` – the navigation probe and dialog handling
stay, so a script that unexpectedly navigates is still awaited (and the
navigation reported), and an `alert()` is still auto-handled. The page
path passes the flag through, and service worker evals now always skip
the stable DOM wait (workers can't interact with the DOM – thanks
@Lightning00Blade).
Read-only evals drop from ~205ms to ~103ms on quiet pages, and from
~3.1s to ~103ms on pages that never go quiet – numbers and repro in
#2338.
This is the branch the issue promised – I see #2344 beat me to posting,
so take whichever is easier to review. Differences here: complete `npm
run gen` artifacts (`tool_call_metrics.json` +
`chrome-devtools-cli-options.ts`, so check-docs passes), a skip test
that spies `WaitForHelper.prototype.waitForStableDom` (fails if the
helper ignores the flag, not only if the option isn't passed through),
and a test pinning the safety property – a script that navigates with
the flag off still gets its navigation awaited and reported.
Co-authored-by: Nikolay Vitkov <34244704+Lightning00Blade@users.noreply.github.com>
## What
Adds an opt-in `includeStackTraces` parameter to
`list_console_messages`. When set, each listed message includes its
resolved (source-mapped, ignore-list-filtered) stack trace — in the text
output as indented frames under the message line, and in
`structuredContent` as a pre-formatted `stackTrace` string, matching the
format `get_console_message` already uses.
## Why
Fixes the workflow described in #383: today the list output only
contains plain message text, so an LLM debugging a page has to call
`get_console_message` once per message to learn which file/function/line
failed. With `includeStackTraces: true` the model can map every error to
its source in a single call.
Design notes:
- **Opt-in, default off** — the default output is byte-for-byte
unchanged, keeping the token cost of the common path identical.
- Reuses the existing DevTools stack resolution
(`createStackTraceForConsoleMessage` / `SymbolizedError`) and the
existing `formatStackTrace` formatting, so frames are source-mapped and
ignore-listed exactly like in `get_console_message`.
- The per-message "1-based indexing" note is emitted once for the whole
list instead of once per message to keep the output compact.
- Works for console API messages, uncaught errors (including `Caused
by:` chains), and grouped repeated messages (`[N times]`).
## Before / after
Default (unchanged):
```
## Console messages
Showing 1-1 of 1 (Page 1 of 1).
msgid=1 [log] Hello stack trace! (0 args)
```
With `includeStackTraces: true`:
```
## Console messages
Showing 1-1 of 1 (Page 1 of 1).
msgid=1 [log] Hello stack trace! (0 args)
at foo (foo.ts:10:2)
at bar (foo.ts:20:2)
--- setTimeout -------------------------
at schedule (util.ts:5:2)
Note: stack trace line and column numbers use 1-based indexing
```
`structuredContent.consoleMessages[i]` gains an optional `"stackTrace":
"at foo (foo.ts:10:2)\nat bar (foo.ts:20:2)\n..."` field.
## Relationship to #2060
Complements #2060 (source location for `list_console_messages`, issue
#903): that PR surfaces the top-frame *location* for every message by
default, while this one exposes the *full stack trace* behind an
explicit opt-in. The features are independent and the changes are
designed to coexist with minimal conflict.
## Testing
- `npm run build` — passes
- `npm run test:no-build` — full suite passes (exit 0)
- `npm run check-format` — passes
- `npm run gen` — regenerated `docs/tool-reference.md`, CLI options, and
tool-call metrics (included)
New coverage:
- `tests/formatters/ConsoleFormatter.test.ts` — snapshot tests for
concise `toString`/`toJSON` with `fetchStackTrace` set (console message
+ uncaught error) and for the unchanged default.
- `tests/tools/console.test.ts` — integration tests asserting frames
(`at failingFn`) appear in text and `structuredContent` when
`includeStackTraces: true`, and are absent by default.
Fixes#383🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: ZayanKhan-12 <khanzayan200@gmail.com>
Add an explicit per-navigation request limit to `NetworkCollector` and
evict the oldest entries as new requests arrive, using a production
default of 1,000 retained requests while preserving request order,
stable IDs for retained entries, and the existing three-navigation
policy. `NetworkCollector` limits preserved history to three navigation
buckets, but each bucket—especially the current navigation—can retain an
unlimited number of Puppeteer `HTTPRequest` objects.
Emit more than 1,000 requests on one page without navigating and verify
that only the newest 1,000 remain, in arrival order, with retained
stable IDs still resolving; Cross several main-frame navigations,
including redirect navigation requests and subframe events, and verify
that each of the three retained navigation buckets is independently
bounded without losing the current navigation request.
Refs #2431
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>
Add a get_heapsnapshot_object_details MCP tool which lets the agent
query all known information about a node in the heap snapshot.
Co-authored-by: Dominik Inführ <dinfuehr@chromium.org>
Emits native contexts and their sizes with the get_heapsnapshot_summary
MCP tool. This should help the agent get an overview about which native
contexts consume the most memory.
Co-authored-by: Dominik Inführ <dinfuehr@chromium.org>
This exposes DevTools named filters in get_heapsnapshot_details and
get_heapsnapshot_class_nodes tools. This allows the client to list e.g.
all objects retained through detached DOM objects.
Co-authored-by: Dominik Inführ <dinfuehr@chromium.org>
Instead of two separate MCP tools, we can have one instead which takes
classIndex as optional argument.
Co-authored-by: Dominik Inführ <dinfuehr@chromium.org>
This commit adds two MCP tools for comparing heap snapshots.
`compare_heapsnapshot_summary` compares two memory snapshot and returns
which classes have new/deleted objects.
`compare_heapsnapshot_class_nodes` can then be used to list the object
ids added and deleted for a specific class.
Co-authored-by: Dominik Inführ <dinfuehr@chromium.org>
Adding the get_heapsnapshot_dominators MCP tool to show the dominators
for a given node. In combination with get_heapsnapshot_retaining_paths
this should help understand what keeps an object reachable and thus
alive.
Co-authored-by: Dominik Inführ <dinfuehr@chromium.org>
Co-authored-by: Nicholas Roscino <nroscino@google.com>
This PR adds the get_heapsnapshot_edges MCP tool. Agents can use it to
look at the outgoing edges for a specifc object.
Co-authored-by: Dominik Inführ <dinfuehr@chromium.org>
This PR adds the `get_heapsnapshot_retaining_paths` MCP tool. This can
be used to find the paths from the target object to the GC roots which
keep that object alive.
Co-authored-by: Dominik Inführ <dinfuehr@chromium.org>
Updates the flag for the memory tooling to remove the experimental bit
(keep alias for backwards compatibility).
And updates the SKILLs to reflected the update names and point to the
available tools.
Q: Should it be called `take_heapsnapshot` or `take_heap_snapshot`?
This commit adds the close_heapsnapshot MCP tool such that the coding
agent can close heap snapshots again.
Co-authored-by: Dominik Inführ <dinfuehr@chromium.org>
Co-authored-by: Nicholas Roscino <nroscino@google.com>
## Summary
Extend the existing `emulate` tool with an `extraHTTPHeaders` parameter
that calls Puppeteer's `page.setExtraHTTPHeaders()` (which uses CDP
`Network.setExtraHTTPHeaders` under the hood).
Closes#1175
## Approach
Per [feedback from
@natorion](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1175#issuecomment-4097587153),
this integrates into the existing `emulate` tool rather than adding a
standalone tool. The `emulate` tool is already the central hub for
page-level state modifications (userAgent, viewport, networkConditions,
geolocation, colorScheme), and custom HTTP headers fit naturally
alongside them. This also avoids increasing the MCP tool count and LLM
token overhead.
## Changes
- **`src/types.ts`** — Added `extraHTTPHeaders?: Record<string, string>`
to `EmulationSettings`
- **`src/tools/emulation.ts`** — Added `extraHTTPHeaders` as an optional
zod parameter on the `emulate` tool
- **`src/McpContext.ts`** — Added handler logic in the `emulate()`
method:
- Calls `page.setExtraHTTPHeaders()` when `extraHTTPHeaders` is provided
- Clears from settings when an empty `{}` is passed
- Preserves existing headers when the param is **omitted** (unlike other
emulation settings that reset when omitted) — prevents
`emulate({colorScheme: "dark"})` from accidentally clearing
previously-set headers
- **`tests/tools/emulation.test.ts`** — Added 5 test cases:
1. Sets extra headers on requests
2. Clears headers with `{}`
3. Headers persist across navigations
4. Does not affect other emulation settings
5. Reports correctly per-page (new page has no headers)
## Use Case
This enables setting custom HTTP headers on **all** requests — including
the initial document navigation and `<script>` tag loads — which
`initScript` cannot do since it runs after the document is already
fetched.
## Usage
```js
// Set headers
emulate({ extraHTTPHeaders: { "X-Custom": "value", "Authorization": "Bearer token" } })
// Clear headers
emulate({ extraHTTPHeaders: {} })
// Combine with other emulation settings
emulate({ extraHTTPHeaders: { "X-Branch": "feature-1" }, userAgent: "MyBot/1.0" })
```
---------
Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>
Co-authored-by: Nicholas Roscino <nroscino@google.com>
Update the tools to all include the `heapsnapshot` term for easier
handling.
Renames UID to ID to reduce confusion with the snapshot UIDs.
Renames Id to NodeId to better differentiate from the base Id.
Remove EdgeIndex as it was not useful.
Closes:
https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1970
- add a message about successful configuration
- add a message about the currently emulated geolocation
- switch to comma separate format instead of `x` separator.
Tested with https://www.audero.it/demo/geolocation-api-demo.html
## Summary
Adds an optional `filePath` parameter to `evaluate_script` that saves
the script output to a file instead of returning it inline.
Refs #153
## Motivation
Issue #153 requested `filePath` support for `take_snapshot` and
`evaluate_script`. `take_snapshot` was addressed in #463. PR #248
previously attempted this but was closed due to conflicts. This PR
implements the same feature on the current codebase, completing the
remaining piece.
## Changes
- Add optional `filePath` parameter to the `evaluate_script` schema
- Add `context.validatePath(filePath)` call for path validation
- Pass `{filePath, context}` options to `performEvaluation()`
- In `performEvaluation()`: when `filePath` is provided, save output via
`context.saveFile()` with `.json` extension; otherwise return inline as
before
- Update `docs/tool-reference.md` via `npm run docs:generate`
- Add unit test for file output
## Key design decisions
- **Same pattern as existing tools**: Follows the `context.saveFile()`
pattern established by `take_snapshot` (#463), `take_screenshot`,
`get_network_request` (#795), and performance tools (#686).
- **Minimal change surface**: Only `performEvaluation()` gains an
optional `options` parameter. No new interfaces or abstractions.
- **Backwards compatible**: `filePath` is optional. When omitted,
behavior is identical to before.
## Testing
**Unit test added** (`tests/tools/script.test.ts`):
- Call `evaluate_script` with `filePath` set to a temp file
- Assert response contains "Output saved to"
- Assert file content matches the JSON-serialized return value
- Clean up temp file in `finally` block
**Manual testing performed**:
- `() => document.title` with `filePath: /tmp/test.json` → file contains
`"Example Domain"`
- `() => document.title` without `filePath` → inline ```json block
returned (no regression)
- `() => Array.from({length: 100}, ...)` with `filePath` → 100-item
array saved correctly
- `filePath` pointing to non-existent directory → directory
auto-created, file saved
- Relative path (`test.json`) → resolved to CWD, absolute path shown in
response
- Function that throws → error returned, no partial file created
- Existing file as `filePath` → file overwritten completely
---------
Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>
Fixes#1942
Verified using `npm run eval --
scripts/eval_scenarios/fill_select_and_checkboxes_test.ts`
Without this change, I observed 7 runs using fill_form for all controls
at once, 14 runs using click to select checkboxes and 10 runs that did
nothing (total 31 runs)
After this change: 9 fill_form using runs (passes), 1 click based
approach and 10 no-attempt fails (20 runs total)
Depending how we count the no-attempt runs, its either increase from 23%
to 45% or 33% to 90% in eval pass rate.
Co-authored-by: Piotr Paulski <piotrpaulski@chromium.org>
Enables "third-party developer tools" feature. This allows the inspected
web page to expose tools which provide debugging information to Chrome
DevTools for Agents.
Third-party developer tools enable web applications to expose internal
state, component hierarchies, or specific debug data that cannot be
deduced through static analysis. This allows Chrome DevTools for Agents
to provide richer, more actionable context to AI agents during debugging
sessions.
2 additional tools are enabled in Chrome DevTools for Agents for
interacting with third-party developer tools:
`list_3p_developer_tools()` and `execute_3p_developer_tool`.
Code changes in this PR:
- Rename "in-page tools" to "third-party developer tools"
- Unhide
- Make available in CLI
- Add documentation
Extracting WebMCP tools into a separate category for better grouping in
the docs. This changes `--experimentalWebmcp` to
`--categoryExperimentalWebmcp` to align with other experimental
categories. Debugging category was not a good fit since the tools
provided by WebMCP are not necessarily used for debugging.
cc @beaufortfrancois