Skip to content

Commit 300bb17

Browse files
committed
refactor: replace isolatedContext routing with pageId routing
Replace the isolatedContext-based page resolution with the more general pageId parameter. The isolatedContext approach only worked when agents used different browser contexts. pageId works for any multi-page scenario, uses an already-existing concept (page IDs), and does not depend on isolated contexts. - Rename isolatedContextSchema to pageIdSchema (string to number) - Replace resolvePageByContext() with resolvePageById() in McpContext - Remove per-context page tracking (#contextSelectedPage map) - Update all tool files to use pageId routing - Keep isolatedContext on new_page (browser context isolation, not routing) - Update tests to use pageId-based assertions
1 parent b8db132 commit 300bb17

16 files changed

Lines changed: 122 additions & 344 deletions

docs/tool-reference.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
<!-- AUTO GENERATED DO NOT EDIT - run 'npm run docs' to update-->
22

3-
# Chrome DevTools MCP Tool Reference (~7084 cl100k_base tokens)
3+
# Chrome DevTools MCP Tool Reference (~7267 cl100k_base tokens)
44

5-
- **[Input automation](#input-automation)** (9 tools)
5+
- **[Input automation](#input-automation)** (8 tools)
66
- [`click`](#click)
77
- [`drag`](#drag)
88
- [`fill`](#fill)
99
- [`fill_form`](#fill_form)
1010
- [`handle_dialog`](#handle_dialog)
1111
- [`hover`](#hover)
1212
- [`press_key`](#press_key)
13-
- [`type_text`](#type_text)
1413
- [`upload_file`](#upload_file)
1514
- **[Navigation automation](#navigation-automation)** (6 tools)
1615
- [`close_page`](#close_page)
@@ -72,6 +71,7 @@
7271
- **uid** (string) **(required)**: The uid of an element on the page from the page content snapshot
7372
- **value** (string) **(required)**: The value to [`fill`](#fill) in
7473
- **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false.
74+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
7575

7676
---
7777

@@ -83,6 +83,7 @@
8383

8484
- **elements** (array) **(required)**: Elements from snapshot to [`fill`](#fill) out.
8585
- **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false.
86+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
8687

8788
---
8889

@@ -116,17 +117,7 @@
116117

117118
- **key** (string) **(required)**: A key or a combination (e.g., "Enter", "Control+A", "Control++", "Control+Shift+R"). Modifiers: Control, Shift, Alt, Meta
118119
- **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false.
119-
120-
---
121-
122-
### `type_text`
123-
124-
**Description:** Type text using keyboard into a previously focused input
125-
126-
**Parameters:**
127-
128-
- **text** (string) **(required)**: The text to type
129-
- **submitKey** (string) _(optional)_: Optional key to press after typing. E.g., "Enter", "Tab", "Escape"
120+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
130121

131122
---
132123

@@ -139,6 +130,7 @@
139130
- **filePath** (string) **(required)**: The local path of the file to upload
140131
- **uid** (string) **(required)**: The uid of the file input element or an element that will open file chooser on the page from the page content snapshot
141132
- **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false.
133+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
142134

143135
---
144136

@@ -171,6 +163,7 @@
171163
- **handleBeforeUnload** (enum: "accept", "decline") _(optional)_: Whether to auto accept or beforeunload dialogs triggered by this navigation. Default is accept.
172164
- **ignoreCache** (boolean) _(optional)_: Whether to ignore cache on reload.
173165
- **initScript** (string) _(optional)_: A JavaScript script to be executed on each new document before any other scripts for the next navigation.
166+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
174167
- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used.
175168
- **type** (enum: "url", "back", "forward", "reload") _(optional)_: Navigate the page by URL, back or forward in history, or reload.
176169
- **url** (string) _(optional)_: Target URL (only type=url)
@@ -208,6 +201,7 @@
208201
**Parameters:**
209202

210203
- **text** (array) **(required)**: Non-empty list of texts. Resolves when any value appears on the page.
204+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
211205
- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used.
212206

213207
---
@@ -224,6 +218,7 @@
224218
- **cpuThrottlingRate** (number) _(optional)_: Represents the CPU slowdown factor. Set the rate to 1 to disable throttling. If omitted, throttling remains unchanged.
225219
- **geolocation** (unknown) _(optional)_: Geolocation to [`emulate`](#emulate). Set to null to clear the geolocation override.
226220
- **networkConditions** (enum: "No emulation", "Offline", "Slow 3G", "Fast 3G", "Slow 4G", "Fast 4G") _(optional)_: Throttle network. Set to "No emulation" to disable. If omitted, conditions remain unchanged.
221+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
227222
- **userAgent** (unknown) _(optional)_: User agent to [`emulate`](#emulate). Set to null to clear the user agent override.
228223
- **viewport** (unknown) _(optional)_: Viewport to [`emulate`](#emulate). Set to null to reset to the default viewport.
229224

@@ -237,6 +232,7 @@
237232

238233
- **height** (number) **(required)**: Page height
239234
- **width** (number) **(required)**: Page width
235+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
240236

241237
---
242238

@@ -262,6 +258,7 @@
262258
- **autoStop** (boolean) **(required)**: Determines if the trace recording should be automatically stopped.
263259
- **reload** (boolean) **(required)**: Determines if, once tracing has started, the current selected page should be automatically reloaded. Navigate the page to the right URL using the [`navigate_page`](#navigate_page) tool BEFORE starting the trace if reload or autoStop is set to true.
264260
- **filePath** (string) _(optional)_: The absolute file path, or a file path relative to the current working directory, to save the raw trace data. For example, trace.json.gz (compressed) or trace.json (uncompressed).
261+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
265262

266263
---
267264

@@ -272,6 +269,7 @@
272269
**Parameters:**
273270

274271
- **filePath** (string) _(optional)_: The absolute file path, or a file path relative to the current working directory, to save the raw trace data. For example, trace.json.gz (compressed) or trace.json (uncompressed).
272+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
275273

276274
---
277275

@@ -332,6 +330,7 @@ so returned values have to be JSON-serializable.
332330
}`
333331

334332
- **args** (array) _(optional)_: An optional list of arguments to pass to the function.
333+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
335334

336335
---
337336

@@ -367,6 +366,7 @@ so returned values have to be JSON-serializable.
367366
- **filePath** (string) _(optional)_: The absolute path, or a path relative to the current working directory, to save the screenshot to instead of attaching it to the response.
368367
- **format** (enum: "png", "jpeg", "webp") _(optional)_: Type of format to save the screenshot as. Default is "png"
369368
- **fullPage** (boolean) _(optional)_: If set to true takes a screenshot of the full page instead of the currently visible viewport. Incompatible with uid.
369+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
370370
- **quality** (number) _(optional)_: Compression quality for JPEG and WebP formats (0-100). Higher values mean better quality but larger file sizes. Ignored for PNG format.
371371
- **uid** (string) _(optional)_: The uid of an element on the page from the page content snapshot. If omitted takes a pages screenshot.
372372

@@ -381,6 +381,7 @@ in the DevTools Elements panel (if any).
381381
**Parameters:**
382382

383383
- **filePath** (string) _(optional)_: The absolute path, or a path relative to the current working directory, to save the snapshot to instead of attaching it to the response.
384+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
384385
- **verbose** (boolean) _(optional)_: Whether to include all possible information available in the full a11y tree. Default is false.
385386

386387
---
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import assert from 'node:assert';
8+
9+
import type {TestScenario} from '../eval_gemini.ts';
10+
11+
export const scenario: TestScenario = {
12+
prompt: `Open three new pages in isolated contexts:
13+
- Page A at data:text/html,<h1>Page A</h1>
14+
- Page B at data:text/html,<h1>Page B</h1>
15+
- Page C at data:text/html,<h1>Page C</h1>
16+
Then take screenshots of all three pages in parallel.`,
17+
maxTurns: 8,
18+
expectations: calls => {
19+
// Exactly 3 screenshot calls.
20+
const screenshots = calls.filter(c => c.name === 'take_screenshot');
21+
assert.strictEqual(screenshots.length, 3, 'Should take 3 screenshots');
22+
23+
// Each screenshot must carry a numeric pageId.
24+
for (const ss of screenshots) {
25+
assert.strictEqual(
26+
typeof ss.args.pageId,
27+
'number',
28+
'Screenshot should use pageId',
29+
);
30+
}
31+
32+
// All pageIds should be distinct (one per page).
33+
const pageIds = new Set(screenshots.map(s => s.args.pageId));
34+
assert.strictEqual(
35+
pageIds.size,
36+
3,
37+
'Each screenshot should target a different page',
38+
);
39+
},
40+
};

src/McpContext.ts

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,6 @@ export class McpContext implements Context {
140140

141141
#pageToDevToolsPage = new Map<Page, Page>();
142142
#selectedPage?: Page;
143-
// Per-context selected page tracking for parallel agent support.
144-
#contextSelectedPage = new Map<string, Page>();
145143
#textSnapshot: TextSnapshot | null = null;
146144
#networkCollector: NetworkCollector;
147145
#consoleCollector: ConsoleCollector;
@@ -518,39 +516,11 @@ export class McpContext implements Context {
518516
return page;
519517
}
520518

521-
resolvePageByContext(isolatedContext?: string): Page {
522-
if (isolatedContext === undefined) {
519+
resolvePageById(pageId?: number): Page {
520+
if (pageId === undefined) {
523521
return this.getSelectedPage();
524522
}
525-
526-
// Try the per-context selected page first.
527-
const tracked = this.#contextSelectedPage.get(isolatedContext);
528-
if (tracked && !tracked.isClosed()) {
529-
return tracked;
530-
}
531-
532-
// Fall back: find any non-closed page in the context.
533-
const ctx = this.#isolatedContexts.get(isolatedContext);
534-
if (!ctx) {
535-
throw new Error(
536-
`No isolated context named "${isolatedContext}" exists. ` +
537-
`Create one first with new_page(isolatedContext: "${isolatedContext}").`,
538-
);
539-
}
540-
541-
for (const page of this.#pages) {
542-
if (
543-
!page.isClosed() &&
544-
this.#pageToIsolatedContextName.get(page) === isolatedContext
545-
) {
546-
this.#contextSelectedPage.set(isolatedContext, page);
547-
return page;
548-
}
549-
}
550-
551-
throw new Error(
552-
`No open page found in isolated context "${isolatedContext}".`,
553-
);
523+
return this.getPageById(pageId);
554524
}
555525

556526
getPageById(pageId: number): Page {
@@ -587,12 +557,6 @@ export class McpContext implements Context {
587557
void newPage.emulateFocusedPage(true).catch(error => {
588558
this.logger('Error turning on focused page emulation', error);
589559
});
590-
591-
// Track per-context selected page for parallel agent routing.
592-
const contextName = this.#pageToIsolatedContextName.get(newPage);
593-
if (contextName) {
594-
this.#contextSelectedPage.set(contextName, newPage);
595-
}
596560
}
597561

598562
#updateSelectedPageTimeouts() {

src/tools/ToolDefinition.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export type Context = Readonly<{
109109
recordedTraces(): TraceResult[];
110110
storeTraceRecording(result: TraceResult): void;
111111
getSelectedPage(): Page;
112-
resolvePageByContext(isolatedContext?: string): Page;
112+
resolvePageById(pageId?: number): Page;
113113
getDialog(): Dialog | undefined;
114114
clearDialog(): void;
115115
getPageById(pageId: number): Page;
@@ -196,16 +196,8 @@ export function defineTool<
196196
export const CLOSE_PAGE_ERROR =
197197
'The last open page cannot be closed. It is fine to keep it open.';
198198

199-
export const isolatedContextSchema = {
200-
isolatedContext: zod
201-
.string()
202-
.optional()
203-
.describe(
204-
'The name of the isolated browser context to resolve the page from. ' +
205-
'When provided, the tool operates on the page belonging to this context ' +
206-
'instead of the globally selected page. ' +
207-
'Use this to avoid race conditions when multiple agents work in parallel.',
208-
),
199+
export const pageIdSchema = {
200+
pageId: zod.number().optional().describe('Targets a specific page by ID.'),
209201
};
210202

211203
export const timeoutSchema = {

src/tools/emulation.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import {zod, PredefinedNetworkConditions} from '../third_party/index.js';
99

1010
import {ToolCategory} from './categories.js';
11-
import {defineTool, isolatedContextSchema} from './ToolDefinition.js';
11+
import {defineTool, pageIdSchema} from './ToolDefinition.js';
1212

1313
const throttlingOptions: [string, ...string[]] = [
1414
'No emulation',
@@ -24,7 +24,7 @@ export const emulate = defineTool({
2424
readOnlyHint: false,
2525
},
2626
schema: {
27-
...isolatedContextSchema,
27+
...pageIdSchema,
2828
networkConditions: zod
2929
.enum(throttlingOptions)
3030
.optional()
@@ -105,9 +105,7 @@ export const emulate = defineTool({
105105
),
106106
},
107107
handler: async (request, _response, context) => {
108-
const page = context.resolvePageByContext(
109-
request.params.isolatedContext,
110-
);
108+
const page = context.resolvePageById(request.params.pageId);
111109
await context.emulate(request.params, page);
112110
},
113111
});

0 commit comments

Comments
 (0)