## Support for Network Blocklists and Allowlists
(`--blocked-url-pattern` & `--allowed-url-pattern` arguments)
This PR adds support for CLI options to restrict network access in the
browser session via URL patterns.
### Key Features & How It Works
- **Pattern Matching:** Utilizes the [URLPattern
Standard](https://urlpattern.spec.whatwg.org/) for pattern matching.
- **Target Detachment:** Silently detaches from targets (pages/tabs)
whose URLs match blocked patterns (or do not match allowed patterns)
upon connection.
- **Runtime Blocking:** Prevents navigations and blocks runtime requests
(such as fetch/XHR and subresources) if they violate the pattern rules.
- **Mutual Exclusivity:** `--blocked-url-pattern` and
`--allowed-url-pattern` conflict with each other and cannot be
configured simultaneously.
- **Browser Requirements:**
- **`--allowed-url-pattern`**: Requires **Chrome 149+**.
- **`--blocked-url-pattern`**: Works on Chrome versions older than 149,
but **Chrome 149+ is highly recommended**.
### Important Limitations & Side Effects
- **Network Emulation/Throttling Conflict:** Network throttling is
disabled when a network blocklist/allowlist is configured, to avoid
conflicting with Puppeteer's underlying blocking mechanisms.
- Using the `emulate` tool to modify `networkConditions` (e.g. setting
to `Offline`) will throw an error: *`Network throttling is not supported
when network blocking (allowlist/blocklist) is configured.`*
- Other emulation settings (e.g., `cpuThrottlingRate`, `geolocation`,
`viewport`) are unaffected and remain fully functional.
---
### Configuration Examples
#### 1. Blocking specific domains or endpoints (Blocklist)
Add the `--blocked-url-pattern` options to the `args` list in your MCP
settings file:
```json
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": [
"chrome-devtools-mcp@latest",
"--blocked-url-pattern=*://*.blocked-example.com/*",
"--blocked-url-pattern=*://*.another-blocked-example.com/*"
]
}
}
}
```
#### 2. Restricting access to authorized domains (Allowlist)
Add the `--allowed-url-pattern` options to restrict the browser to
permitted hosts (requires Chrome 149+):
```json
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": [
"chrome-devtools-mcp@latest",
"--allowed-url-pattern=https://*.allowed-example.com/*",
"--allowed-url-pattern=https://*.another-allowed-example.com/*"
]
}
}
}
```
---------
Co-authored-by: Natallia Harshunova <nharshunova@chromium.org>
Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>
💡 **What:** The optimization implemented is refactoring the sequential
file save loops for Lighthouse audits in `src/tools/lighthouse.ts` to
utilize concurrent execution via `Promise.allSettled`.
🎯 **Why:** Previously, the tool saved generated lighthouse reports
linearly (e.g. `html` then `json`), causing unnecessary synchronous
blocking on I/O operations (`fs.writeFile` under the hood via
`context.saveFile`).
📊 **Measured Improvement:** In a benchmark designed to simulate I/O
constraint simulating a 250ms delay for each file write
(`tests/performance/lighthouse_save.bench.ts`), the optimization reduced
file save time from 2749.58ms to 2460.11ms, showing the I/O times were
successfully overlapped and providing a ~250ms gain.
---
*PR created automatically by Jules for task
[9309630185581322418](https://jules.google.com/task/9309630185581322418)
started by @Lightning00Blade*
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
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>
This allows a page to have multiple providers of third-party developer
tools, which each respond to the `devtoolstooldiscovery` event.
- Multiple `ToolGroup`s
- MCP tool responses only mention third-party developer tools, if there
are any. Otherwise this part of the output is skipped.
This change aligns most of our logger allowing them to be omitted when
needed meaning code inside the args will not be executed unconditionally
- example `logger?.(JSON.stringy(X))`.
Currently only changes the type, while in a follow up I will remove the
`debug` package and update the file logs.
Refs #1777
This change makes pageId as the first argument for CLI if
experimentalPageIdRouting flag is enabled
Co-authored-by: Samiya Caur <samiyac@chromium.org>
Fixes#2116.
`chrome-devtools-mcp-main.ts` currently has no shutdown handler. After a
session calls `navigate_page` (or anything else that launches Chrome),
the Chrome subprocess keeps the Node event loop ref'd, so closing stdin
(the stdio MCP convention for "I'm done") doesn't make the server exit.
Callers that close stdin to terminate the server have to fall back to
SIGTERM / SIGKILL on every page-loaded session — deterministically, not
flakily.
This change:
1. Adds `closeBrowser()` in `browser.ts` that calls `browser.close()`
for launched instances (reaps the Chrome subprocess) and
`browser.disconnect()` for attached instances (leaves the user's Chrome
alive). No-op if no browser is active or the connection has already been
dropped.
2. Registers shutdown handlers in `chrome-devtools-mcp-main.ts` for:
- `stdin.on('end' | 'close')` — stdio MCP transport convention
- `SIGTERM` / `SIGINT` / `SIGHUP` — clients that signal instead of
closing stdin (`SIGHUP` for parity with `src/daemon/daemon.ts`)
The handler is idempotent (guarded `shuttingDown` flag), and has an
unref'd 10s timeout backstop in case Chrome teardown hangs (slow
`beforeunload` handlers, many tabs, etc.).
### Note on scope
This complements (does not replace) the client-side fixes filed against
#1765, e.g. google-gemini/gemini-cli#13391 and
anthropics/claude-code#42300. The MCP stdio convention is that closing
stdin signals shutdown; a server that doesn't honor that forces every
client to special-case it. The watchdog sub-process
(`src/telemetry/watchdog/main.ts:145-146`) and the daemon
(`src/daemon/daemon.ts:224-230`) both already implement this for the
same reason — this PR extends the same pattern to the main entry point
so all three execution paths behave consistently.
### Measurement
Repro script in #2116, same env (chrome-devtools-mcp@1.0.1, Chrome
148.0.7778.178, Node v24.11.1, Linux), 10 iterations:
| Scenario | Before | After |
|---|---|---|
| `tools/list only` (no navigation) | 10/10 clean, 30-37 ms | 10/10
clean, 29-40 ms |
| `navigate example.com` | 10/10 SIGTERM at ~5080 ms | 10/10 clean at
145-180 ms |
### Notes
- I didn't add a subprocess-based test for this; the existing
`tests/utils.ts:runCli` infrastructure targets the `chrome-devtools`
CLI, not the stdio MCP server, and a shutdown-timing test would
introduce non-trivial Chrome-startup flakiness in CI. Happy to add one
if maintainers want it — pointer to the right test directory
appreciated.
This addresses #1955
CPU throttling needs to be applied to both the primary puppeteer session
and the secondary CDP session from the DevTools universe to have an
effect.
For network throttling this does not seem to be the case, I can see a
slowdown with the current implementation which only applies the network
throttling to the primary CDP session.
I also had to increase the navigation timeout to prevent timeout errors.
## 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
- register tool input schemas as passthrough so extra named arguments
reach ToolHandler validation
- report unknown arguments with an explicit error that names the unknown
and expected arguments
- stop before invoking tool handlers when unknown arguments are present
- add ToolHandler coverage for reporting an extra argument
Fixes#1940
## Tests
- `npm run check-format`
- `npx tsc --noEmitOnError false` *(emits build artifacts but still
reports the existing `chrome-devtools-frontend` type conflict in
`ModelImpl.ts`)*
- `node --experimental-strip-types --no-warnings=ExperimentalWarning
scripts/post-build.ts`
- `NODE_TEST_REPORTER=spec npm run test:no-build --
tests/ToolHandler.test.ts`
This also caught and fixed an issue where the enum index is not
sequential :/
No error enums have been defined on the service side yet so it's
possible to modify the enum index directly.
Underscore followed by numbers is not encouraged in the proto style
guide. See "Underscores in Identifiers" in
https://protobuf.dev/programming-guides/style/.
This recently became an issue because we have `list_3p_developer_tools`
and `execute_3p_developer_tool` which would have been dis-allowed. This
change replaces them with `list3p_developer_tools` and
`execute3p_developer_tool` respectively, as suggested by the style
guide.
This only affects the logged version. The tool name is still the
existing one.
This transformation is also applied to other similar places, like flag
names, tool name in error logging, and tool args.
The `tool_name_metrics.json` was manually updated because we never
landed the server side change because it was disallowed by proto style
check.
## 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>
There is no behavior change.
This groups utilities into two modules:
- `transformation.ts`: for any logic related to mutating / filtering of
the names, values of telemetry entries.
- `metricsRegistry.ts`: for any logic that related to the maintenance of
metrics.json files.
This adds logging to the read & write operations to the telemetry state
file. In particular,
- for read, no error is logged if the state file doesn't exist, which is
expected to happen when the user is new. An error is logged otherwise
(e.g. file format error, errors when reading an existing file like
permission issue).
- for write, any error in the path will be logged.
This commit also puts the instantiation of the `Persistence` object out
of the `ClearcutLogger` object to avoid circular dependency.
## Problem
The startup update check in `src/bin/check-latest-version.ts` hardcodes
`https://registry.npmjs.org/chrome-devtools-mcp/latest`. Users behind
corporate proxies or on a private registry (e.g. JFrog Artifactory)
cannot reach that URL — the request silently fails on every startup, and
they lose update notifications unless they set
`CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS=1`.
Fixes#1943.
The earlier discussion on that issue surfaced two competing approaches:
- Reading only `npm_config_registry`. The maintainer pointed out this
only covers `npx` / `npm run` invocations, not direct CLI use.
- Pulling in `@npmcli/config`. Solves all cases but adds a runtime
dependency.
This PR uses a third approach suggested in the issue: shell out to `npm
config get registry`. That command:
- Honors `.npmrc` files at every scope (global, user, project) and
respects `npm_config_registry`.
- Works the same regardless of how the CLI is invoked (direct, `npx`,
`npm run`).
- Adds no new dependency.
- Runs at most once per 24 hours from a detached process, so the
shell-out cost is negligible.
## Solution
Add a small `getRegistry()` helper that runs `npm config get registry`
with a 5 s timeout and validates the result before using it. Fall back
to the hardcoded public registry when:
- `npm` is not on `PATH`,
- the command times out or errors,
- the value is empty, `"undefined"`, or not an http(s) URL.
The fetch URL is then composed from that resolved registry.
## Testing
- `npm run typecheck` — passes
- `npm run check-format` — passes
- `npm run build` — passes
- `node --test build/tests/check-for-updates.test.js` — 6/6 pass (no
regressions in the existing test suite for this area)
- Manual: ran `node build/src/bin/check-latest-version.js /tmp/out.json`
against the public registry → wrote `{"version":"0.25.0"}`. Re-ran with
`npm_config_registry=https://example.com` → fetch fails silently as
expected, no cache written.
(Unrelated E2E browser tests in
`tests/e2e/chrome-devtools-commands.test.js` fail in my local
environment; they appear environment-dependent and are not touched by
this change.)
## 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
the updates made to these files from #1982 came out a bit weird. So I
reverted changes on these files and re-run `npm run gen` (which includes
`npm run update-metrics`) to correct them.
we'd always want old entries to be marked as deprecated and new entries
to be added to the very last. Don't manually resolve conflicts in these
files. When there are conflicts, just revert back to a clean state and
`npm run gen` will take care of them 😉
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>