This should reduce the flakiness and issue with finding open DevTools
tabs for the corresponding pages.
Currently we do multiple loops over all the targets, so this should have
a nice performance improvement when multiple pages are opened.
- `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
The client is meant to start the daemon if it is not running or connect
to the existing one. Long-running client connections are not needed yet.
Removed previous direct daemon test as we better test it via the client,
the intended way to manage the daemon.
## Summary
Adds optional `pageId` routing to page-scoped tools, gated behind
`--experimental-page-id-routing`. When enabled, multi-agent callers can
target a specific page without relying on global selection state. Fully
backward-compatible: without the flag, behavior is unchanged.
### Key changes
- **`pageScoped` annotation**: tools declare `pageScoped: true`; the
server merges `pageId` into their schema at registration time (when the
flag is on)
- **`McpPage` wrapper**: consolidates per-page state (numeric id,
isolated context name, focus tracking) into a single class
- **Request-scoped page routing**: `resolvePageById()` resolves the
target page, `setRequestPage()` threads it through the handler so tools
like `getSelectedPage()` see the right page
- **`assertPageIsFocused`**: keyboard/input tools validate that the
target page holds browser focus, returning an actionable error ("call
select_page first") instead of silently dispatching to the wrong page
- **`--experimental-page-id-routing` CLI flag** (hidden): gates schema
injection and request-scoped routing so the feature can be tested before
graduating
- **Eval scenarios**: `page_id_routing_test` and
`page_focus_keyboard_test` with `serverArgs` support in the eval harness
Addresses #1019
Implements a daemon process needed for the CLI. The daemon has commands
to start/stop itself as well as an ability to forward data to the
managed MCP server. Local sockets or named pipes are used depending on
the platform.
The .sock and .pid files are placed under
XDG_RUNTIME_DIR/chrome-devtools-mcp or /tmp/chrome-devtools-mcp for
short socket paths.
It uses Puppeteer's PipeTransport to avoid re-implementing \0 terminated
messages.
Note the code is not used anywhere at the moment.
`type_text` is useful for pages that do not have a11y and, thus, uid is
not known. It's also useful for testing user-like keyboard input and
testing the focus state changes. The `fill` tools force the focus change
and require an uid and therefore are not suitable for these tasks.
## Summary
Enhances wait_for to support waiting on multiple possible texts and
resolve when any one appears.
This addresses long-running flows that can end in different UI outcomes
(for example, "Complete" or "Error"), avoiding unnecessary 300s waits
when only one expected string is provided.
Closes#916.
## Tool Update
### wait_for
Waits for text on the selected page, now with any-match support.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| text | string \| string[] | yes | A single text or a non-empty list of
texts. Resolves when any value appears. |
| timeout | integer | no | Maximum wait in ms (0 keeps default
behavior). |
---
## Design / Implementation
- Kept backward compatibility: existing single-string text calls
continue to work unchanged.
- Added schema support for string | string[] with non-empty array
validation.
- Updated context API to accept string | string[].
- Matching logic now normalizes to an array and races all candidates
across all frames using both:
- aria/<text>
- text/<text>
- Added clearer response output for array input:
- Element matching one of ["Complete","Error"] found.
- Updated generated tool docs for the new wait_for contract.
- Improved docs generation to render ZodUnion types (so union params are
documented correctly, not as unknown).
---
## Tests
Added/updated coverage for:
- Schema acceptance of:
- single string
- non-empty string array
- rejection of empty array
- Any-match array success case
- Any-match array when matching text appears later (async/delayed
content)
- Existing wait_for behavior remains covered for single-text usage
Executed relevant test suites:
- tests/tools/snapshot.test.ts
- tests/McpContext.test.ts
- tests/index.test.ts
Confirmation after the change applied:
https://opncd.ai/share/8m6I4r4a
## Summary
Adds storage-isolated browser contexts via an optional `isolatedContext`
parameter on the `new_page` tool, following the simplified design
proposed by @OrKoN in #926.
Pages created with the same `isolatedContext` name share cookies,
localStorage, and storage. Pages in different isolated contexts (or the
default context) are fully isolated — ideal for testing multi-user
real-time features like chat, notifications, or collaborative editing.
## Changes
### `new_page` tool
- New optional `isolatedContext: string` parameter
- If specified, creates/reuses a named `BrowserContext` and opens a page
in it
- If omitted, uses the default browser context (existing behavior
unchanged)
### `McpContext`
- `#isolatedContexts` Map: LLM-provided names → Puppeteer
`BrowserContext` instances
- `#pageToIsolatedContextName` WeakMap: GC-safe page → context name
reverse lookup
- Auto-discovery: externally created browser contexts get
`isolated-context-1`, `isolated-context-2`, etc.
- `getIsolatedContextName(page)`: returns the isolated context name for
a page (used by response formatting)
- `page.browserContext()` used for context membership detection (no
custom target event forwarding needed)
- No context cleanup in `dispose()` or `closePage()` — either the entire
browser is closed or we disconnect without destroying state
### `McpResponse`
- Page list includes `isolatedContext=${name}` labels (both text and
structured JSON output)
### `ToolDefinition`
- `Context` interface extended with `getIsolatedContextName(page)`
method
## What's NOT included (by design)
- **No `TargetEventEmitter`**: Puppeteer forwards target events from
`BrowserContext` → `Browser` internally
- **No context cleanup**: Browser contexts are not closed on `dispose()`
or page close, per maintainer guidance
- **No `about:blank` cleanup**: Default context and isolated contexts
coexist side-by-side
## Example
```
> new_page url="https://app.example.com/chat" isolatedContext="userA"
> new_page url="https://app.example.com/chat" isolatedContext="userB"
> list_pages
Page 1: [app.example.com/chat] isolatedContext=userA
Page 2: [app.example.com/chat] isolatedContext=userB [selected]
```
Pages in different isolated contexts have fully independent cookies,
localStorage, IndexedDB, and WebSocket connections.
## Tests
- 6 new tests covering `isolatedContext` feature in
`tests/tools/pages.test.ts`
- All existing tests pass (333+)
- Zero type errors, lint clean
Closes#926
---------
Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>
## Summary
Adds `screencast_start` and `screencast_stop` MCP tools that allow
agents to record a video of a page using Puppeteer's `page.screencast()`
API.
Only enabled if the command line option `--experimental-screencast` is
provided
Closes#878.
## New Tools
### `screencast_start`
Starts recording a screencast (video) of the selected page in mp4
format.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `path` | string (optional) | temp file | Output file path |
### `screencast_stop`
Stops the active recording and reports the saved file path.
## Design
- **Start/stop pair** following the `performance_start_trace` /
`performance_stop_trace` pattern
- **State management** via `getScreenRecorder()` / `setScreenRecorder()`
on the Context interface
- **Category**: `DEBUGGING` (alongside `take_screenshot`)
- **ffmpeg dependency**: Clear error message when ffmpeg is not
installed
- **Temp file fallback**: When no `path` is provided, creates a temp
file with the correct extension
---------
Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>