- 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>
## Summary
- Input tools (`click`, `fill`, `press_key`, `hover`, `drag`,
`type_text`, `fill_form`, `click_at`) and `evaluate_script` now append a
`Page navigated to <url>.` line to the response when the action triggers
a cross-document navigation.
- `WaitForHelper.waitForEventsAfterAction` returns `{navigated:
boolean}` instead of `void`, surfacing the navigation signal that was
already being detected internally.
- No change to `navigate_page` or `new_page` since they already report
the URL explicitly.
Fixes#243
## Why
Today, if a `click` causes a page navigation, the response says
*"Successfully clicked on the element"* with no indication that the page
URL changed. The agent has to make an extra `list_pages` call to
discover where it landed. This saves that round-trip for every
navigation-triggering action.
## Design
The existing `waitForNavigationStarted` in `WaitForHelper` already knows
whether a cross-document navigation started. We propagate that signal as
`{navigated: boolean}` through the return value of
`waitForEventsAfterAction` → `McpPage` → `ContextPage` interface, and
let each handler append the URL line when `navigated` is true.
Same-document (history API) navigations remain filtered out by the
existing `waitForNavigationStarted` logic, matching current behavior.
Click-opens-new-tab is a separate concern (#367).
## Test plan
- [x] New test: click on a link that causes navigation → response
includes `Page navigated to <url>.`
- [x] New test: click on a button that doesn't navigate → no navigation
line in response
- [x] Full test suite (563 tests) passes
- [x] TypeScript typecheck clean
- [x] ESLint + Prettier clean
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
## Summary
Teach the `click` tool to handle accessibility snapshot targets that
point at an `option` inside a native, single-select `<select>` element.
When the target is a native select option, `click` now selects that
option through the owning `<select>` element instead of asking Puppeteer
to click an option node that has no clickable box while the dropdown is
collapsed. Other option-like targets, including custom ARIA options,
continue through the normal click path.
## Motivation
Fixes#1941.
The text snapshot can expose native `<option>` nodes with stable uids.
This makes it natural for an agent to call `click` on an option uid
after seeing the desired option in the snapshot.
However, a collapsed native `<select>` does not expose its child
`<option>` nodes as directly clickable page boxes. In that state, the
option can exist in the DOM and accessibility tree while still having no
clickable geometry. Puppeteer therefore waits for the option to become
interactive and eventually times out.
The existing `fill` tool already handles selecting native `<select>`
options. This change keeps that guidance explicit in the `click` tool
description, while also making `click(option_uid)` robust for the native
select case that appears in the snapshot.
## Changes
- Added a constrained native select fallback in `click`:
- Only runs for single-click requests.
- Only runs when the snapshot node role is `option`.
- Only handles real `HTMLOptionElement` nodes owned by a native
`<select>`.
- Does not handle disabled selects, disabled options, disabled
optgroups, or multi-selects.
- Falls back to the existing locator click behavior for all other
targets.
- Dispatches `input` and `change` events when selecting a different
native option.
- Updated the `click` tool description to steer agents toward `fill` for
native `<select>` option selection.
- Regenerated CLI/tool reference docs with `npm run gen`.
- Added regression coverage for:
- Clicking an option uid in a collapsed native `<select>`.
- Clicking an option uid inside a native `<optgroup>`.
- Clicking a custom ARIA `role="option"` element through the normal
click path.
## Test Plan
Passed:
```bash
npm run test -- tests/tools/input.test.ts
npm run check-format
```
Also attempted:
```bash
npm run test
```
The full suite hit unrelated local failures outside this change area:
- `tests/tools/network.test.ts`: snapshot ordering difference for
redirected requests.
- `tests/tools/screenshot.test.ts`: `Page.captureScreenshot` failed for
the large full-page screenshot case with `Page is too large`.
The changed input tool tests pass locally.
## Risk
Low. The fallback is intentionally narrow:
- It is gated by the accessibility role being `option`.
- It verifies the DOM node is an actual `HTMLOptionElement`.
- It only applies to native, non-disabled, single-select `<select>`
controls.
- It does not reinterpret custom combobox/listbox implementations.
- It preserves the existing locator click path for non-native option
targets.
The main behavior change is that `click(option_uid)` can now succeed for
native collapsed dropdowns that previously timed out.
## Related Issue
Closes#1941.
## Maintainer Context
This targets a small but high-impact mismatch between the snapshot
representation and browser interaction semantics.
The snapshot correctly exposes native options because they exist in the
accessibility tree. The click implementation previously treated that uid
like any other clickable element, but collapsed native options do not
have normal clickable layout boxes. This PR makes the native select case
explicit without expanding `click` into a general custom dropdown
heuristic.
The tool description still recommends `fill` for native `<select>`
selection so agents are guided toward the more direct tool. The code
fallback exists for the common case where an agent already selected an
option uid from the snapshot.
This is the first part of adding tests for checking tool behavior when a
dialog is already open (related to #1069)
In future CL, I will be focusing on tools which currently get blocked
due to open dialogs. As part of those CLs, proactive rejection of tool
execution will be implemented.
This is a better measurement as if object self size is X and the
retainerSize Y. X will always be smaller then Y, there Y will be the
actual size on the Heap.
Improvements for handling in-page tool responses. In order to
successfully pass an in-page tool response from the page context to the
MCP server, the response needs to be serializable. The code walks the
response object an performs the following changes:
- DOM elements are stashed onto the window object and replaced with an
ID. On the MCP server side this ID is used to map back to the
corresponding UID in the page snapshot generated from the accessibility
tree.
- Circular references are replaced with a string.
- Class instances (which can be complex or non-serializable) are
replaced with a string.
- Functions are replaced with a string.
If the in-page tool response contains DOM elements which are not part of
the page snapshot, a new snapshot is created add the missing elements
are added explicitly.
This PR adds an experimental allowlist for the page navigate tool call
(requires `--experimentalNavigationAllowlist`, off by default). The
feature uses a list of URLPatterns to decline navigations that land on
disallowed URLs. If that happens, the client can update the allowlist
and re-try. The purpose of the this feature is to offer additional
guardrails on top of the MCP server. It does not restrict subresources
or JS/iframe navigations in any way. The performance impact is minimized
by turning off interception as soon as the navigation request is done.
This refactors the code to extract the Id logic from the PageCollector
and provide it in the heapsnapshot.
We need to use an UID as we need the internal ClassKey to query the
heapsnapshot, but that is a strange string (usually looking like
`,ClassName`) which may get the LLM confused as we use comma separated
output.
Provide a argument to LLM to handle dialogs that come up during code
execution.
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This PR adds a CLI flag to enable redacting network headers in the same
way they are redacted in DevTools. Note that sometimes it might prevent
the agent from properly analysing network issues. Pass
`--redact-headers=false` to revert to the previous behavior.
DOM elements are non-serializable and therefore cannot be directly sent
between the inspected page and the MCP server. JSONSchema also has no
native type for DOM elements.
If an in-page tool expects a DOM element as an input parameter, it
should specify this in its input schema by adding `'x-mcp-type':
'HTMLElement'` to the object it expects to be a DOM element.
The MCP server internally refers to DOM elements by a UID (UIDs are
assigned when generating a page snapshot which is based on the page's
accessibility tree).
This change provides the mapping between DOM element and UID in both
directions:
1) The tool's input schema is rewritten internally, adding a required
UID attribute to objects with `'x-mcp-type': 'HTMLElement'`. This allows
the MCP server to call the in-page tool with UIDs where the tool expects
DOM elements.
2) In the page context, the UIDs are replaced with the corresponding DOM
elements, before the actual in-page tool is called. This means that the
in-page tool receives DOM elements as parameters where it expects them.
This moves the stored `ToolGroup` from `McpContext` to `McpPage`, where
per-page state should be stored in order for pageId-based routing to
work correctly.
When appending the list of in-page tools to a response, the list now
corresponds to the response's `McpPage`, and only falls back to the
selected `McpPage` for tools which are not page-specific.
---------
Co-authored-by: browser-automation-bot <133232582+browser-automation-bot@users.noreply.github.com>
Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>
Co-authored-by: Tolgahan Demirbaş <49946947+bcfmtolgahan@users.noreply.github.com>
This allows the MCP server to call the in-page tools provided by the
inspected page.
Handling of (non-serializable) DOM elements as tool parameters or tool
output will be added in follow-ups.
There is no mechanism which would allow a page to push an updated list
of in-page tools to the MCP server. The next best thing I can think of
is to append an updated list of in-page tools to the response for each
tool call related to page navigation (navigate_page, list_pages,
select_page, close_page, new_page).
This adds a `list_in_page_tools` MCP tool. When called, it dispatches a
`devtoolstooldiscovery` event on the active page. The page announces its
exposing tools by calling the event's `respondWith` method, which causes
the exposed tools to be stashed on the page's `window` object. This list
of in-page tools is then appended to the `list_in_page_tools` response.
Calling the exposed in-page tools from the MCP server will be handled in
a follow-up.
## Problem
After closing the currently selected page, calling `list_pages` throws
an error:
```
The selected page has been closed. Call list_pages to see open pages.
```
This creates a deadlock: the error message tells users to call
`list_pages`, but `list_pages` itself throws the same error.
## Root Cause
`list_pages` is defined with `definePageTool`, which marks it as
`pageScoped: true`. The handler dispatch in `index.ts` (line ~186) calls
`context.getSelectedMcpPage()` for all page-scoped tools **before**
invoking the handler. When the selected page is closed,
`getSelectedPptrPage()` throws.
However, `list_pages` doesn't actually use the `page` parameter — its
handler only calls `response.setIncludePages(true)`.
## Fix
Change `list_pages` from `definePageTool` to `defineTool`. This bypasses
the page-scoped check while preserving all existing behavior since the
handler never used the page reference.
## Testing
Reproduced the issue following the steps in #1138:
1. Call `list_pages` → returns page list ✅
2. Close the selected page
3. Call `list_pages` → now returns updated page list instead of throwing
✅Fixes#1138
---------
Co-authored-by: Piotr Paulski <piotrpaulski@chromium.org>
- `chrome-devtools-mcp.js` is the `npx chrome-devtools-mcp`
- `chrome-devtools.js` is the new CLI
- `-cli-options.js` is the corresponding options
- all these files are in the bin folder to indicate they are executable
This PR implements the ability to trigger an extension action by passing
it an extension id.
It uses puppeteer internals and the canary chrome version in tests since
the TriggerAction CDP command is still not available in the current
stable release.
- reduces the token usage.
- makes emulation and script compatible with CLI.
- documents .nullable() and .object() restrictions for the future.
- the emulation tools do not have nullable anymore and undefined would
clear the emulation instead. The model is thus required to provide all
emulation settings at once.
Closes https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/918
- renamed getSelectedPage to getSelectedPptrPage
- removes getSelectedPptrPage from tool interfaces
- moves dialog handling to McpPage
- makes responses to be optionally McpPage-scoped