Skip to content

Commit 166c3ab

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 c464ccc commit 166c3ab

16 files changed

Lines changed: 121 additions & 331 deletions

docs/tool-reference.md

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

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

55
- **[Input automation](#input-automation)** (8 tools)
66
- [`click`](#click)
@@ -71,6 +71,7 @@
7171
- **uid** (string) **(required)**: The uid of an element on the page from the page content snapshot
7272
- **value** (string) **(required)**: The value to [`fill`](#fill) in
7373
- **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false.
74+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
7475

7576
---
7677

@@ -82,6 +83,7 @@
8283

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

8688
---
8789

@@ -115,6 +117,7 @@
115117

116118
- **key** (string) **(required)**: A key or a combination (e.g., "Enter", "Control+A", "Control++", "Control+Shift+R"). Modifiers: Control, Shift, Alt, Meta
117119
- **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false.
120+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
118121

119122
---
120123

@@ -127,6 +130,7 @@
127130
- **filePath** (string) **(required)**: The local path of the file to upload
128131
- **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
129132
- **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false.
133+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
130134

131135
---
132136

@@ -159,6 +163,7 @@
159163
- **handleBeforeUnload** (enum: "accept", "decline") _(optional)_: Whether to auto accept or beforeunload dialogs triggered by this navigation. Default is accept.
160164
- **ignoreCache** (boolean) _(optional)_: Whether to ignore cache on reload.
161165
- **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.
162167
- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used.
163168
- **type** (enum: "url", "back", "forward", "reload") _(optional)_: Navigate the page by URL, back or forward in history, or reload.
164169
- **url** (string) _(optional)_: Target URL (only type=url)
@@ -196,6 +201,7 @@
196201
**Parameters:**
197202

198203
- **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.
199205
- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used.
200206

201207
---
@@ -212,6 +218,7 @@
212218
- **cpuThrottlingRate** (number) _(optional)_: Represents the CPU slowdown factor. Set the rate to 1 to disable throttling. If omitted, throttling remains unchanged.
213219
- **geolocation** (unknown) _(optional)_: Geolocation to [`emulate`](#emulate). Set to null to clear the geolocation override.
214220
- **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.
215222
- **userAgent** (unknown) _(optional)_: User agent to [`emulate`](#emulate). Set to null to clear the user agent override.
216223
- **viewport** (unknown) _(optional)_: Viewport to [`emulate`](#emulate). Set to null to reset to the default viewport.
217224

@@ -225,6 +232,7 @@
225232

226233
- **height** (number) **(required)**: Page height
227234
- **width** (number) **(required)**: Page width
235+
- **pageId** (number) _(optional)_: Targets a specific page by ID.
228236

229237
---
230238

@@ -250,6 +258,7 @@
250258
- **autoStop** (boolean) **(required)**: Determines if the trace recording should be automatically stopped.
251259
- **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.
252260
- **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.
253262

254263
---
255264

@@ -260,6 +269,7 @@
260269
**Parameters:**
261270

262271
- **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.
263273

264274
---
265275

@@ -320,6 +330,7 @@ so returned values have to be JSON-serializable.
320330
}`
321331

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

324335
---
325336

@@ -355,6 +366,7 @@ so returned values have to be JSON-serializable.
355366
- **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.
356367
- **format** (enum: "png", "jpeg", "webp") _(optional)_: Type of format to save the screenshot as. Default is "png"
357368
- **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.
358370
- **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.
359371
- **uid** (string) _(optional)_: The uid of an element on the page from the page content snapshot. If omitted takes a pages screenshot.
360372

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

371383
- **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.
372385
- **verbose** (boolean) _(optional)_: Whether to include all possible information available in the full a11y tree. Default is false.
373386

374387
---
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
@@ -131,8 +131,6 @@ export class McpContext implements Context {
131131
#pages: Page[] = [];
132132
#pageToDevToolsPage = new Map<Page, Page>();
133133
#selectedPage?: Page;
134-
// Per-context selected page tracking for parallel agent support.
135-
#contextSelectedPage = new Map<string, Page>();
136134
#textSnapshot: TextSnapshot | null = null;
137135
#networkCollector: NetworkCollector;
138136
#consoleCollector: ConsoleCollector;
@@ -505,39 +503,11 @@ export class McpContext implements Context {
505503
return page;
506504
}
507505

508-
resolvePageByContext(isolatedContext?: string): Page {
509-
if (isolatedContext === undefined) {
506+
resolvePageById(pageId?: number): Page {
507+
if (pageId === undefined) {
510508
return this.getSelectedPage();
511509
}
512-
513-
// Try the per-context selected page first.
514-
const tracked = this.#contextSelectedPage.get(isolatedContext);
515-
if (tracked && !tracked.isClosed()) {
516-
return tracked;
517-
}
518-
519-
// Fall back: find any non-closed page in the context.
520-
const ctx = this.#isolatedContexts.get(isolatedContext);
521-
if (!ctx) {
522-
throw new Error(
523-
`No isolated context named "${isolatedContext}" exists. ` +
524-
`Create one first with new_page(isolatedContext: "${isolatedContext}").`,
525-
);
526-
}
527-
528-
for (const page of this.#pages) {
529-
if (
530-
!page.isClosed() &&
531-
this.#pageToIsolatedContextName.get(page) === isolatedContext
532-
) {
533-
this.#contextSelectedPage.set(isolatedContext, page);
534-
return page;
535-
}
536-
}
537-
538-
throw new Error(
539-
`No open page found in isolated context "${isolatedContext}".`,
540-
);
510+
return this.getPageById(pageId);
541511
}
542512

543513
getPageById(pageId: number): Page {
@@ -574,12 +544,6 @@ export class McpContext implements Context {
574544
void newPage.emulateFocusedPage(true).catch(error => {
575545
this.logger('Error turning on focused page emulation', error);
576546
});
577-
578-
// Track per-context selected page for parallel agent routing.
579-
const contextName = this.#pageToIsolatedContextName.get(newPage);
580-
if (contextName) {
581-
this.#contextSelectedPage.set(contextName, newPage);
582-
}
583547
}
584548

585549
#updateSelectedPageTimeouts() {

src/tools/ToolDefinition.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export type Context = Readonly<{
108108
recordedTraces(): TraceResult[];
109109
storeTraceRecording(result: TraceResult): void;
110110
getSelectedPage(): Page;
111-
resolvePageByContext(isolatedContext?: string): Page;
111+
resolvePageById(pageId?: number): Page;
112112
getDialog(): Dialog | undefined;
113113
clearDialog(): void;
114114
getPageById(pageId: number): Page;
@@ -182,16 +182,8 @@ export function defineTool<Schema extends zod.ZodRawShape>(
182182
export const CLOSE_PAGE_ERROR =
183183
'The last open page cannot be closed. It is fine to keep it open.';
184184

185-
export const isolatedContextSchema = {
186-
isolatedContext: zod
187-
.string()
188-
.optional()
189-
.describe(
190-
'The name of the isolated browser context to resolve the page from. ' +
191-
'When provided, the tool operates on the page belonging to this context ' +
192-
'instead of the globally selected page. ' +
193-
'Use this to avoid race conditions when multiple agents work in parallel.',
194-
),
185+
export const pageIdSchema = {
186+
pageId: zod.number().optional().describe('Targets a specific page by ID.'),
195187
};
196188

197189
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)