Skip to content

Commit 221954a

Browse files
committed
feat(new_page): support incognito browser contexts
1 parent a90378a commit 221954a

5 files changed

Lines changed: 85 additions & 0 deletions

File tree

src/McpContext.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ export class McpContext implements Context {
254254
async newPage(
255255
background?: boolean,
256256
isolatedContextName?: string,
257+
incognito = false,
257258
): Promise<McpPage> {
258259
let page: Page;
259260
if (isolatedContextName !== undefined) {
@@ -263,6 +264,11 @@ export class McpContext implements Context {
263264
this.#isolatedContexts.set(isolatedContextName, ctx);
264265
}
265266
page = await ctx.newPage();
267+
} else if (incognito) {
268+
const contextName = `incognito-context-${this.#nextIsolatedContextId++}`;
269+
const ctx = await this.browser.createBrowserContext();
270+
this.#isolatedContexts.set(contextName, ctx);
271+
page = await ctx.newPage();
266272
} else {
267273
page = await this.browser.newPage({background});
268274
}

src/bin/chrome-devtools-cli-options.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,13 @@ export const commands: Commands = {
594594
'Whether to open the page in the background without bringing it to the front. Default is false (foreground).',
595595
required: false,
596596
},
597+
incognito: {
598+
name: 'incognito',
599+
type: 'boolean',
600+
description:
601+
'Whether to create the page in a fresh incognito browser context. A new context is created for each call and is isolated from all other pages.',
602+
required: false,
603+
},
597604
isolatedContext: {
598605
name: 'isolatedContext',
599606
type: 'string',

src/tools/ToolDefinition.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ export type Context = Readonly<{
181181
newPage(
182182
background?: boolean,
183183
isolatedContextName?: string,
184+
incognito?: boolean,
184185
): Promise<ContextPage>;
185186
closePage(pageId: number): Promise<void>;
186187
selectPage(page: ContextPage): void;

src/tools/pages.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@ export const newPage = defineTool(args => {
168168
.describe(
169169
'Whether to open the page in the background without bringing it to the front. Default is false (foreground).',
170170
),
171+
incognito: zod
172+
.boolean()
173+
.optional()
174+
.describe(
175+
'Whether to create the page in a fresh incognito browser context. A new context is created for each call and is isolated from all other pages.',
176+
),
171177
isolatedContext: zod
172178
.string()
173179
.optional()
@@ -190,9 +196,16 @@ export const newPage = defineTool(args => {
190196
},
191197
blockedByDialog: false,
192198
handler: async (request, response, context) => {
199+
if (request.params.incognito && request.params.isolatedContext) {
200+
throw new Error(
201+
'The incognito and isolatedContext parameters are mutually exclusive.',
202+
);
203+
}
204+
193205
const page = await context.newPage(
194206
request.params.background,
195207
request.params.isolatedContext,
208+
request.params.incognito,
196209
);
197210

198211
await navigateWithInterception(

tests/tools/pages.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,64 @@ describe('pages', () => {
275275
assert.ok(response.includePages);
276276
});
277277
});
278+
279+
it('creates a page in a fresh incognito context', async () => {
280+
await withMcpContext(async (response, context) => {
281+
await newPage().handler(
282+
{params: {url: 'about:blank', incognito: true}},
283+
response,
284+
context,
285+
);
286+
const page = context.getSelectedPptrPage();
287+
const isolatedContextName = context.getIsolatedContextName(page);
288+
assert.ok(isolatedContextName);
289+
assert.ok(isolatedContextName.startsWith('incognito-context-'));
290+
assert.ok(response.includePages);
291+
});
292+
});
293+
294+
it('creates a new context for each incognito page', async () => {
295+
await withMcpContext(async (response, context) => {
296+
await newPage().handler(
297+
{params: {url: 'about:blank', incognito: true}},
298+
response,
299+
context,
300+
);
301+
const page1 = context.getSelectedPptrPage();
302+
const isolatedContext1 = context.getIsolatedContextName(page1);
303+
304+
await newPage().handler(
305+
{params: {url: 'about:blank', incognito: true}},
306+
response,
307+
context,
308+
);
309+
const page2 = context.getSelectedPptrPage();
310+
const isolatedContext2 = context.getIsolatedContextName(page2);
311+
312+
assert.notStrictEqual(page1.browserContext(), page2.browserContext());
313+
assert.notStrictEqual(isolatedContext1, isolatedContext2);
314+
});
315+
});
316+
317+
it('throws when incognito and isolatedContext are both provided', async () => {
318+
await withMcpContext(async (response, context) => {
319+
await assert.rejects(
320+
() =>
321+
newPage().handler(
322+
{
323+
params: {
324+
url: 'about:blank',
325+
incognito: true,
326+
isolatedContext: 'session-a',
327+
},
328+
},
329+
response,
330+
context,
331+
),
332+
/mutually exclusive/,
333+
);
334+
});
335+
});
278336
});
279337
describe('new_page with isolatedContext', () => {
280338
it('creates a page in an isolated context', async () => {

0 commit comments

Comments
 (0)