@@ -19,6 +19,7 @@ import {NetworkCollector, ConsoleCollector} from './PageCollector.js';
1919import type { DevTools } from './third_party/index.js' ;
2020import type {
2121 Browser ,
22+ BrowserContext ,
2223 ConsoleMessage ,
2324 Debugger ,
2425 Dialog ,
@@ -74,7 +75,7 @@ interface EmulationSettings {
7475 viewport ?: Viewport | null ;
7576}
7677
77- interface McpContextOptions {
78+ export interface McpContextOptions {
7879 // Whether the DevTools windows are exposed as pages for debugging of DevTools.
7980 experimentalDevToolsDebugging : boolean ;
8081 // Whether all page-like targets are exposed as pages.
@@ -119,11 +120,17 @@ export class McpContext implements Context {
119120 browser : Browser ;
120121 logger : Debugger ;
121122
122- // The most recent page state.
123+ // Maps LLM-provided isolatedContext name → Puppeteer BrowserContext.
124+ #isolatedContexts = new Map < string , BrowserContext > ( ) ;
125+ // Reverse lookup: Page → isolatedContext name (for snapshot labeling).
126+ // WeakMap so closed pages are garbage-collected automatically.
127+ #pageToIsolatedContextName = new WeakMap < Page , string > ( ) ;
128+ // Auto-generated name counter for when no name is provided.
129+ #nextIsolatedContextId = 1 ;
130+
123131 #pages: Page [ ] = [ ] ;
124132 #pageToDevToolsPage = new Map < Page , Page > ( ) ;
125133 #selectedPage?: Page ;
126- // The most recent snapshot.
127134 #textSnapshot: TextSnapshot | null = null ;
128135 #networkCollector: NetworkCollector ;
129136 #consoleCollector: ConsoleCollector ;
@@ -187,6 +194,9 @@ export class McpContext implements Context {
187194 this . #networkCollector. dispose ( ) ;
188195 this . #consoleCollector. dispose ( ) ;
189196 this . #devtoolsUniverseManager. dispose ( ) ;
197+ // Isolated contexts are intentionally not closed here.
198+ // Either the entire browser will be closed or we disconnect
199+ // without destroying browser state.
190200 }
191201
192202 static async from (
@@ -269,8 +279,22 @@ export class McpContext implements Context {
269279 return this . #consoleCollector. getById ( this . getSelectedPage ( ) , id ) ;
270280 }
271281
272- async newPage ( background ?: boolean ) : Promise < Page > {
273- const page = await this . browser . newPage ( { background} ) ;
282+ async newPage (
283+ background ?: boolean ,
284+ isolatedContextName ?: string ,
285+ ) : Promise < Page > {
286+ let page : Page ;
287+ if ( isolatedContextName !== undefined ) {
288+ let ctx = this . #isolatedContexts. get ( isolatedContextName ) ;
289+ if ( ! ctx ) {
290+ ctx = await this . browser . createBrowserContext ( ) ;
291+ this . #isolatedContexts. set ( isolatedContextName , ctx ) ;
292+ }
293+ page = await ctx . newPage ( ) ;
294+ this . #pageToIsolatedContextName. set ( page , isolatedContextName ) ;
295+ } else {
296+ page = await this . browser . newPage ( { background} ) ;
297+ }
274298 await this . createPagesSnapshot ( ) ;
275299 this . selectPage ( page ) ;
276300 this . #networkCollector. addPage ( page ) ;
@@ -283,6 +307,7 @@ export class McpContext implements Context {
283307 }
284308 const page = this . getPageById ( pageId ) ;
285309 await page . close ( { runBeforeUnload : false } ) ;
310+ this . #pageToIsolatedContextName. delete ( page ) ;
286311 }
287312
288313 getNetworkRequestById ( reqid : number ) : HTTPRequest {
@@ -558,13 +583,8 @@ export class McpContext implements Context {
558583 }
559584 }
560585
561- /**
562- * Creates a snapshot of the pages.
563- */
564586 async createPagesSnapshot ( ) : Promise < Page [ ] > {
565- const allPages = await this . browser . pages (
566- this . #options. experimentalIncludeAllPages ,
567- ) ;
587+ const allPages = await this . #getAllPages( ) ;
568588
569589 for ( const page of allPages ) {
570590 if ( ! this . #pageIdMap. has ( page ) ) {
@@ -573,8 +593,6 @@ export class McpContext implements Context {
573593 }
574594
575595 this . #pages = allPages . filter ( page => {
576- // If we allow debugging DevTools windows, return all pages.
577- // If we are in regular mode, the user should only see non-DevTools page.
578596 return (
579597 this . #options. experimentalDevToolsDebugging ||
580598 ! page . url ( ) . startsWith ( 'devtools://' )
@@ -593,11 +611,44 @@ export class McpContext implements Context {
593611 return this . #pages;
594612 }
595613
596- async detectOpenDevToolsWindows ( ) {
597- this . logger ( 'Detecting open DevTools windows' ) ;
598- const pages = await this . browser . pages (
614+ async #getAllPages ( ) : Promise < Page [ ] > {
615+ const defaultCtx = this . browser . defaultBrowserContext ( ) ;
616+ const allPages = await this . browser . pages (
599617 this . #options. experimentalIncludeAllPages ,
600618 ) ;
619+
620+ // Build a reverse lookup from BrowserContext instance → name.
621+ const contextToName = new Map < BrowserContext , string > ( ) ;
622+ for ( const [ name , ctx ] of this . #isolatedContexts) {
623+ contextToName . set ( ctx , name ) ;
624+ }
625+
626+ // Auto-discover BrowserContexts not in our mapping (e.g., externally
627+ // created incognito contexts) and assign generated names.
628+ const knownContexts = new Set ( this . #isolatedContexts. values ( ) ) ;
629+ for ( const ctx of this . browser . browserContexts ( ) ) {
630+ if ( ctx !== defaultCtx && ! ctx . closed && ! knownContexts . has ( ctx ) ) {
631+ const name = `isolated-context-${ this . #nextIsolatedContextId++ } ` ;
632+ this . #isolatedContexts. set ( name , ctx ) ;
633+ contextToName . set ( ctx , name ) ;
634+ }
635+ }
636+
637+ // Use page.browserContext() to determine each page's context membership.
638+ for ( const page of allPages ) {
639+ const ctx = page . browserContext ( ) ;
640+ const name = contextToName . get ( ctx ) ;
641+ if ( name ) {
642+ this . #pageToIsolatedContextName. set ( page , name ) ;
643+ }
644+ }
645+
646+ return allPages ;
647+ }
648+
649+ async detectOpenDevToolsWindows ( ) {
650+ this . logger ( 'Detecting open DevTools windows' ) ;
651+ const pages = await this . #getAllPages( ) ;
601652 this . #pageToDevToolsPage = new Map < Page , Page > ( ) ;
602653 for ( const devToolsPage of pages ) {
603654 if ( devToolsPage . url ( ) . startsWith ( 'devtools://' ) ) {
@@ -629,6 +680,10 @@ export class McpContext implements Context {
629680 return this . #pages;
630681 }
631682
683+ getIsolatedContextName ( page : Page ) : string | undefined {
684+ return this . #pageToIsolatedContextName. get ( page ) ;
685+ }
686+
632687 getDevToolsPage ( page : Page ) : Page | undefined {
633688 return this . #pageToDevToolsPage. get ( page ) ;
634689 }
@@ -857,7 +912,8 @@ export class McpContext implements Context {
857912 } ,
858913 } as ListenerMap ;
859914 } ) ;
860- await this . #networkCollector. init ( await this . browser . pages ( ) ) ;
915+ const pages = await this . browser . pages ( ) ;
916+ await this . #networkCollector. init ( pages ) ;
861917 }
862918
863919 async installExtension ( extensionPath : string ) : Promise < string > {
0 commit comments