Skip to content

Commit f2c2686

Browse files
committed
feat: add BrowserContext support to McpContext
McpContext now accepts an optional browserContext in McpContextOptions. When provided, page creation, listing, and event subscriptions use the BrowserContext instead of the Browser directly, enabling isolated sessions with separate cookies, localStorage, and network state. - Export McpContextOptions interface - Add browserContext field to McpContextOptions - Use BrowserContext for newPage(), createPagesSnapshot(), detectOpenDevToolsWindows(), and collector subscriptions - Fall back to Browser when no BrowserContext is provided - Add SESSION category to ToolCategory enum
1 parent cf33726 commit f2c2686

2 files changed

Lines changed: 33 additions & 16 deletions

File tree

src/McpContext.ts

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,16 @@ import {
1515
urlsEqual,
1616
} from './DevtoolsUtils.js';
1717
import type {ListenerMap, UncaughtError} from './PageCollector.js';
18-
import {NetworkCollector, ConsoleCollector} from './PageCollector.js';
18+
import {
19+
asTargetEmitter,
20+
NetworkCollector,
21+
ConsoleCollector,
22+
} from './PageCollector.js';
1923
import {Locator} from './third_party/index.js';
2024
import type {DevTools} from './third_party/index.js';
2125
import type {
2226
Browser,
27+
BrowserContext,
2328
ConsoleMessage,
2429
Debugger,
2530
Dialog,
@@ -64,13 +69,15 @@ export interface TextSnapshot {
6469
verbose: boolean;
6570
}
6671

67-
interface McpContextOptions {
72+
export interface McpContextOptions {
6873
// Whether the DevTools windows are exposed as pages for debugging of DevTools.
6974
experimentalDevToolsDebugging: boolean;
7075
// Whether all page-like targets are exposed as pages.
7176
experimentalIncludeAllPages?: boolean;
7277
// Whether CrUX data should be fetched.
7378
performanceCrux: boolean;
79+
// Optional BrowserContext for session isolation.
80+
browserContext?: BrowserContext;
7481
}
7582

7683
const DEFAULT_TIMEOUT = 5_000;
@@ -108,12 +115,11 @@ function getExtensionFromMimeType(mimeType: string) {
108115
export class McpContext implements Context {
109116
browser: Browser;
110117
logger: Debugger;
118+
#browserContext?: BrowserContext;
111119

112-
// The most recent page state.
113120
#pages: Page[] = [];
114121
#pageToDevToolsPage = new Map<Page, Page>();
115122
#selectedPage?: Page;
116-
// The most recent snapshot.
117123
#textSnapshot: TextSnapshot | null = null;
118124
#networkCollector: NetworkCollector;
119125
#consoleCollector: ConsoleCollector;
@@ -148,12 +154,15 @@ export class McpContext implements Context {
148154
) {
149155
this.browser = browser;
150156
this.logger = logger;
157+
this.#browserContext = options.browserContext;
151158
this.#locatorClass = locatorClass;
152159
this.#options = options;
153160

154-
this.#networkCollector = new NetworkCollector(this.browser);
161+
const targetEmitter = asTargetEmitter(this.#browserContext ?? this.browser);
162+
163+
this.#networkCollector = new NetworkCollector(targetEmitter);
155164

156-
this.#consoleCollector = new ConsoleCollector(this.browser, collect => {
165+
this.#consoleCollector = new ConsoleCollector(targetEmitter, collect => {
157166
return {
158167
console: event => {
159168
collect(event);
@@ -166,7 +175,7 @@ export class McpContext implements Context {
166175
},
167176
} as ListenerMap;
168177
});
169-
this.#devtoolsUniverseManager = new UniverseManager(this.browser);
178+
this.#devtoolsUniverseManager = new UniverseManager(targetEmitter);
170179
}
171180

172181
async #init() {
@@ -263,7 +272,9 @@ export class McpContext implements Context {
263272
}
264273

265274
async newPage(background?: boolean): Promise<Page> {
266-
const page = await this.browser.newPage({background});
275+
const page = this.#browserContext
276+
? await this.#browserContext.newPage()
277+
: await this.browser.newPage({background});
267278
await this.createPagesSnapshot();
268279
this.selectPage(page);
269280
this.#networkCollector.addPage(page);
@@ -485,9 +496,9 @@ export class McpContext implements Context {
485496
* Creates a snapshot of the pages.
486497
*/
487498
async createPagesSnapshot(): Promise<Page[]> {
488-
const allPages = await this.browser.pages(
489-
this.#options.experimentalIncludeAllPages,
490-
);
499+
const allPages = this.#browserContext
500+
? await this.#browserContext.pages()
501+
: await this.browser.pages(this.#options.experimentalIncludeAllPages);
491502

492503
for (const page of allPages) {
493504
if (!this.#pageIdMap.has(page)) {
@@ -518,9 +529,9 @@ export class McpContext implements Context {
518529

519530
async detectOpenDevToolsWindows() {
520531
this.logger('Detecting open DevTools windows');
521-
const pages = await this.browser.pages(
522-
this.#options.experimentalIncludeAllPages,
523-
);
532+
const pages = this.#browserContext
533+
? await this.#browserContext.pages()
534+
: await this.browser.pages(this.#options.experimentalIncludeAllPages);
524535
this.#pageToDevToolsPage = new Map<Page, Page>();
525536
for (const devToolsPage of pages) {
526537
if (devToolsPage.url().startsWith('devtools://')) {
@@ -770,7 +781,8 @@ export class McpContext implements Context {
770781
* We need to ignore favicon request as they make our test flaky
771782
*/
772783
async setUpNetworkCollectorForTesting() {
773-
this.#networkCollector = new NetworkCollector(this.browser, collect => {
784+
const targetEmitter = asTargetEmitter(this.#browserContext ?? this.browser);
785+
this.#networkCollector = new NetworkCollector(targetEmitter, collect => {
774786
return {
775787
request: req => {
776788
if (req.url().includes('favicon.ico')) {
@@ -780,7 +792,10 @@ export class McpContext implements Context {
780792
},
781793
} as ListenerMap;
782794
});
783-
await this.#networkCollector.init(await this.browser.pages());
795+
const pages = this.#browserContext
796+
? await this.#browserContext.pages()
797+
: await this.browser.pages();
798+
await this.#networkCollector.init(pages);
784799
}
785800

786801
async installExtension(extensionPath: string): Promise<string> {

src/tools/categories.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
export enum ToolCategory {
8+
SESSION = 'session',
89
INPUT = 'input',
910
NAVIGATION = 'navigation',
1011
EMULATION = 'emulation',
@@ -15,6 +16,7 @@ export enum ToolCategory {
1516
}
1617

1718
export const labels = {
19+
[ToolCategory.SESSION]: 'Session management',
1820
[ToolCategory.INPUT]: 'Input automation',
1921
[ToolCategory.NAVIGATION]: 'Navigation automation',
2022
[ToolCategory.EMULATION]: 'Emulation',

0 commit comments

Comments
 (0)