- `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
- renamed getSelectedPage to getSelectedPptrPage
- removes getSelectedPptrPage from tool interfaces
- moves dialog handling to McpPage
- makes responses to be optionally McpPage-scoped
## 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
## 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
- Add `background` boolean parameter to the `new_page` tool
- When set to `true`, the new page opens in the background without
bringing it to the front
- Uses Puppeteer's existing `background` option for `browser.newPage()`
Fixes#826
---------
Co-authored-by: Alex Rudenko <OrKoN@users.noreply.github.com>
Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>
Related Issue -
[465](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/465)
### Description
This change make sure that the browser window is restored to a `normal`
state before resizing the page. When the window is in `fullscreen` mode
we have to set the state twice to match how Chrome CDP behaves.
Tests cover window resizing for every available window states `type
WindowState = 'normal' | 'minimized' | 'maximized' | 'fullscreen';`
---------
Co-authored-by: Alex Rudenko <OrKoN@users.noreply.github.com>
bringToFront() not only brings the tab to front in the browser but also
the browser window itself. This PR fixes the issue by making the call to
bringToFront() optional allowing agents to bring a tab to the user's
attention if needed but not always. To mitigate the risk of selected
page that is in the background being throttled, this PR implements
emulation of the focused page to make background tabs run as usual.
I split this PR off from my "create one DevTools universe per page" PR
in preparation. This allows tests to re-use browser instances without
creating an `McpContext`.
Drive-by: Move mocked browser/page into utils.ts.
This PR prevents license notices being dropped when creating package for
publication.
This can happen when first import in the file is type-only import that
gets removed during build. When there is no empty line between the
license block comment and such import, the comment is treated as related
to the import and gets removed alongside it.
Adding an empty line between copyright notice and the import fixes the
issue.
Co-authored-by: Piotr Paulski <piotrpaulski@chromium.org>
McpContext currently assumes that there is a page. Eventually we might
support closing the browser by closing the last page but right now we do
not really have responses that indicate that the browser was closed. So
this PR helps the LLM understand that it is okay to keep the last page
running (and there are platform-specific differences as to what happens
if the last page is closed).
Ref https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/87