Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ A [CLI](docs/cli.md) is also provided for use without MCP.
- **Get performance insights**: Uses [Chrome
DevTools](https://github.com/ChromeDevTools/devtools-frontend) to record
traces and extract actionable performance insights.
- **Advanced browser debugging**: Analyze network requests, take screenshots and
check browser console messages (with source-mapped stack traces).
- **Advanced browser debugging**: Analyze and override network requests, take
screenshots and check browser console messages (with source-mapped stack
traces).
- **Reliable automation**. Uses
[puppeteer](https://github.com/puppeteer/puppeteer) to automate actions in
Chrome and automatically wait for action results.
Expand Down Expand Up @@ -512,9 +513,12 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
- [`performance_analyze_insight`](docs/tool-reference.md#performance_analyze_insight)
- [`performance_start_trace`](docs/tool-reference.md#performance_start_trace)
- [`performance_stop_trace`](docs/tool-reference.md#performance_stop_trace)
- **Network** (2 tools)
- **Network** (5 tools)
- [`add_network_override`](docs/tool-reference.md#add_network_override)
- [`get_network_request`](docs/tool-reference.md#get_network_request)
- [`list_network_overrides`](docs/tool-reference.md#list_network_overrides)
- [`list_network_requests`](docs/tool-reference.md#list_network_requests)
- [`remove_network_override`](docs/tool-reference.md#remove_network_override)
- **Debugging** (8 tools)
- [`evaluate_script`](docs/tool-reference.md#evaluate_script)
- [`get_console_message`](docs/tool-reference.md#get_console_message)
Expand Down
37 changes: 36 additions & 1 deletion docs/tool-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@
- [`performance_analyze_insight`](#performance_analyze_insight)
- [`performance_start_trace`](#performance_start_trace)
- [`performance_stop_trace`](#performance_stop_trace)
- **[Network](#network)** (2 tools)
- **[Network](#network)** (5 tools)
- [`add_network_override`](#add_network_override)
- [`get_network_request`](#get_network_request)
- [`list_network_overrides`](#list_network_overrides)
- [`list_network_requests`](#list_network_requests)
- [`remove_network_override`](#remove_network_override)
- **[Debugging](#debugging)** (8 tools)
- [`evaluate_script`](#evaluate_script)
- [`get_console_message`](#get_console_message)
Expand Down Expand Up @@ -319,6 +322,20 @@

## Network

### `add_network_override`

**Description:** Adds a page-scoped network override that redirects matching requests or fulfills them from a local file. Add the override before navigating or reloading the page.

**Parameters:**

- **urlPattern** (string) **(required)**: CDP URL pattern to match. Use '*' for any sequence, '?' for one character, and a backslash to escape a wildcard.
- **contentType** (string) _(optional)_: Content-Type for a local-file response. When omitted, it is inferred from the file extension.
- **redirectUrl** (string) _(optional)_: Absolute HTTP(S) URL to load instead. Exactly one of redirectUrl or responseFilePath is required.
- **resourceType** (enum: "document", "stylesheet", "image", "media", "font", "script", "texttrack", "xhr", "fetch", "prefetch", "eventsource", "websocket", "manifest", "signedexchange", "ping", "cspviolationreport", "preflight", "fedcm", "other") _(optional)_: Only override requests of this resource type. When omitted, all resource types can match.
- **responseFilePath** (string) _(optional)_: Local file to serve as the response. The file is read again for every matching request so rebuilds are picked up. Exactly one of responseFilePath or redirectUrl is required.

---

### `get_network_request`

**Description:** Gets a network request by an optional reqid, if omitted returns the currently selected request in the DevTools Network panel.
Expand All @@ -331,6 +348,14 @@

---

### `list_network_overrides`

**Description:** Lists the network overrides configured for the selected page.

**Parameters:** None

---

### `list_network_requests`

**Description:** List all requests for the currently selected page since the last navigation.
Expand All @@ -344,6 +369,16 @@

---

### `remove_network_override`

**Description:** Removes a network override from the selected page.

**Parameters:**

- **id** (integer) **(required)**: ID returned by [`add_network_override`](#add_network_override).

---

## Debugging

### `evaluate_script`
Expand Down
42 changes: 42 additions & 0 deletions scripts/eval_scenarios/network_override_local_file_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import assert from 'node:assert';

import type {TestScenario} from '../eval_gemini.ts';

export const scenario: TestScenario = {
prompt:
'Open the website at <TEST_URL> and replace the script named my-third-party.js using my local JavaScript file at /my-third-party-local.js.',
maxTurns: 4,
htmlRoute: {
path: '/network_override_local_file_test.html',
htmlContent: `
<h1>Local network override test</h1>
<script src="/my-third-party.js"></script>
`,
},
expectations: result => {
const pageId = result.consumePageNavigation();
const overrideCall = result.remainingCalls.find(
call => call.name === 'add_network_override',
);
assert.ok(
overrideCall,
`Expected add_network_override after navigation, got: ${result.remainingCalls.map(call => call.name).join(', ')}`,
);
assert.strictEqual(overrideCall.args.urlPattern, '*my-third-party.js*');
assert.strictEqual(overrideCall.args.resourceType, 'script');
assert.strictEqual(
overrideCall.args.responseFilePath,
'/my-third-party-local.js',
);
assert.strictEqual(overrideCall.args.redirectUrl, undefined);
if (result.hasPageIdRouting) {
assert.strictEqual(overrideCall.args.pageId, pageId);
}
},
};
42 changes: 42 additions & 0 deletions scripts/eval_scenarios/network_override_redirect_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import assert from 'node:assert';

import type {TestScenario} from '../eval_gemini.ts';

export const scenario: TestScenario = {
prompt:
'Open the website at <TEST_URL> and replace the script named my-third-party.js with the script hosted at https://www.examplescripts.agi/another.js.',
maxTurns: 4,
htmlRoute: {
path: '/network_override_redirect_test.html',
htmlContent: `
<h1>Redirect network override test</h1>
<script src="/my-third-party.js"></script>
`,
},
expectations: result => {
const pageId = result.consumePageNavigation();
const overrideCall = result.remainingCalls.find(
call => call.name === 'add_network_override',
);
assert.ok(
overrideCall,
`Expected add_network_override after navigation, got: ${result.remainingCalls.map(call => call.name).join(', ')}`,
);
assert.strictEqual(overrideCall.args.urlPattern, '*my-third-party.js*');
assert.strictEqual(overrideCall.args.resourceType, 'script');
assert.strictEqual(
overrideCall.args.redirectUrl,
'https://www.examplescripts.agi/another.js',
);
assert.strictEqual(overrideCall.args.responseFilePath, undefined);
if (result.hasPageIdRouting) {
assert.strictEqual(overrideCall.args.pageId, pageId);
}
},
};
4 changes: 4 additions & 0 deletions skills/chrome-devtools-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ chrome-devtools list_network_requests # List all network requests
chrome-devtools list_network_requests --pageSize 50 --pageIdx 0 # List network requests with pagination
chrome-devtools list_network_requests --resourceTypes Fetch # Filter requests by resource type
chrome-devtools list_network_requests --includePreservedRequests true # Include preserved requests
chrome-devtools add_network_override --urlPattern 'https://example.com/app.js*' --resourceType script --redirectUrl 'https://dev.example.com/app.js'
chrome-devtools add_network_override --urlPattern 'https://example.com/app.js*' --resourceType script --responseFilePath /absolute/path/to/app.js
chrome-devtools list_network_overrides
chrome-devtools remove_network_override --id 1
```

## Debugging & Inspection
Expand Down
22 changes: 22 additions & 0 deletions skills/chrome-devtools/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,28 @@ Addional tooling can be enabled by providing the following flags:
- Use pagination (`pageIdx`, `pageSize`) and filtering (`types`) to minimize data
- Set `includeSnapshot: false` on input actions unless you need updated page state

### Testing with a network override

Use network overrides when a production page should load a development resource
without changing the production HTML:

1. Call `add_network_override` before the target navigation or reload.
2. Provide `urlPattern` and exactly one action:
- `redirectUrl` to load another HTTP(S) URL.
- `responseFilePath` to fulfill the request from a local file.
3. Restrict the rule with `resourceType` when possible, for example `script`.
4. Navigate or reload, then use `list_network_requests` and page behavior to
verify the replacement.
5. Call `list_network_overrides` to inspect active rules and
`remove_network_override` when the experiment is complete.

Overrides are scoped to the selected page, persist across its navigations, and
are removed when that page closes. Newer matching rules take precedence. Local
files are read again for every match, so a rebuild is picked up on the next
reload. Service-worker-generated responses can bypass network interception.
Changed script bytes still fail an incompatible Subresource Integrity hash, and
cross-origin redirects remain subject to CSP and CORS.

### Tool selection

- **Automation/interaction**: `take_snapshot` (text-based, faster, better for automation)
Expand Down
9 changes: 9 additions & 0 deletions src/McpContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,15 @@ export class McpContext implements Context {
throw new Error(`Not allowed by allowlist: ${url}`);
}

validateNetworkUrl(url: string): void {
const parsedUrl = new URL(url);
if (parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') {
throw new Error('Unsupported URL protocol: ' + parsedUrl.protocol);
}
this.#validateUrlNotBlocked(parsedUrl);
this.#validateUrlAllowed(parsedUrl);
}

async loadResource(path: string): Promise<string> {
const url = new URL(path);

Expand Down
20 changes: 20 additions & 0 deletions src/McpPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ import {
createTargetUniverse,
type TargetUniverse,
} from './devtools/DevtoolsUtils.js';
import {
NetworkOverrideManager,
type NetworkOverride,
type NetworkOverrideInput,
} from './NetworkOverrideManager.js';
import {
ConsoleCollector,
NetworkCollector,
Expand Down Expand Up @@ -137,6 +142,7 @@ export class McpPage implements ContextPage {

#hasNetworkBlockOrAllowlist: boolean;
#locatorClass: typeof Locator;
#networkOverrideManager: NetworkOverrideManager;

constructor(
page: Page,
Expand All @@ -150,6 +156,7 @@ export class McpPage implements ContextPage {
this.#hasNetworkBlockOrAllowlist = options.hasNetworkBlockOrAllowlist;
this.#locatorClass = options.locatorClass;
this.pptrPage = page;
this.#networkOverrideManager = new NetworkOverrideManager(page);
this.id = id;
this.isolatedContextName = options.isolatedContextName;
this.#dialogHandler = (dialog: Dialog): void => {
Expand Down Expand Up @@ -424,10 +431,23 @@ export class McpPage implements ContextPage {

dispose(): void {
this.pptrPage.off('dialog', this.#dialogHandler);
void this.#networkOverrideManager.dispose();
this.networkCollector.dispose();
this.consoleCollector.dispose();
}

addNetworkOverride(input: NetworkOverrideInput): Promise<NetworkOverride> {
return this.#networkOverrideManager.add(input);
}

listNetworkOverrides(): NetworkOverride[] {
return this.#networkOverrideManager.list();
}

removeNetworkOverride(id: number): Promise<boolean> {
return this.#networkOverrideManager.remove(id);
}

async executeThirdPartyDeveloperTool(
toolName: string,
params: Record<string, unknown>,
Expand Down
Loading