Skip to content

Commit fa09557

Browse files
committed
chore: experimental devtools
1 parent 21e175b commit fa09557

File tree

5 files changed

+81
-32
lines changed

5 files changed

+81
-32
lines changed

src/McpContext.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,16 @@ export class McpContext implements Context {
5151

5252
#nextSnapshotId = 1;
5353
#traceResults: TraceResult[] = [];
54-
55-
private constructor(browser: Browser, logger: Debugger) {
54+
#devtools = false;
55+
56+
private constructor(
57+
browser: Browser,
58+
logger: Debugger,
59+
options: {
60+
devtools: boolean;
61+
},
62+
) {
63+
this.#devtools = options.devtools;
5664
this.browser = browser;
5765
this.logger = logger;
5866

@@ -85,8 +93,14 @@ export class McpContext implements Context {
8593
await this.#consoleCollector.init();
8694
}
8795

88-
static async from(browser: Browser, logger: Debugger) {
89-
const context = new McpContext(browser, logger);
96+
static async from(
97+
browser: Browser,
98+
logger: Debugger,
99+
options: {
100+
devtools: boolean;
101+
},
102+
) {
103+
const context = new McpContext(browser, logger, options);
90104
await context.#init();
91105
return context;
92106
}
@@ -229,6 +243,16 @@ export class McpContext implements Context {
229243
*/
230244
async createPagesSnapshot(): Promise<Page[]> {
231245
this.#pages = await this.browser.pages();
246+
if (this.#devtools) {
247+
for (const target of this.browser.targets()) {
248+
if (
249+
target.type() === 'other' &&
250+
target.url().startsWith('devtools://')
251+
) {
252+
this.#pages.push(await target.asPage());
253+
}
254+
}
255+
}
232256
return this.#pages;
233257
}
234258

src/browser.ts

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,45 @@ import fs from 'fs';
1717

1818
let browser: Browser | undefined;
1919

20-
const ignoredPrefixes = new Set([
21-
'chrome://',
22-
'chrome-extension://',
23-
'chrome-untrusted://',
24-
'devtools://',
25-
]);
20+
function makeTargetFilter(devtools: boolean) {
21+
const ignoredPrefixes = new Set([
22+
'chrome://',
23+
'chrome-extension://',
24+
'chrome-untrusted://',
25+
]);
2626

27-
function targetFilter(target: Target): boolean {
28-
if (target.url() === 'chrome://newtab/') {
29-
return true;
27+
if (!devtools) {
28+
ignoredPrefixes.add('devtools://');
3029
}
31-
for (const prefix of ignoredPrefixes) {
32-
if (target.url().startsWith(prefix)) {
33-
return false;
30+
return function targetFilter(target: Target): boolean {
31+
if (target.url() === 'chrome://newtab/') {
32+
return true;
3433
}
35-
}
36-
return true;
34+
for (const prefix of ignoredPrefixes) {
35+
if (target.url().startsWith(prefix)) {
36+
return false;
37+
}
38+
}
39+
return true;
40+
};
3741
}
3842

3943
const connectOptions: ConnectOptions = {
40-
targetFilter,
4144
// We do not expect any single CDP command to take more than 10sec.
4245
protocolTimeout: 10_000,
4346
};
4447

45-
async function ensureBrowserConnected(browserURL: string) {
48+
async function ensureBrowserConnected(options: {
49+
browserURL: string;
50+
devtools: boolean;
51+
}) {
4652
if (browser?.connected) {
4753
return browser;
4854
}
4955
browser = await puppeteer.connect({
5056
...connectOptions,
51-
browserURL,
57+
targetFilter: makeTargetFilter(options.devtools),
58+
browserURL: options.browserURL,
5259
defaultViewport: null,
5360
});
5461
return browser;
@@ -61,6 +68,7 @@ type McpLaunchOptions = {
6168
userDataDir?: string;
6269
headless: boolean;
6370
isolated: boolean;
71+
devtools: boolean;
6472
};
6573

6674
export async function launch(options: McpLaunchOptions): Promise<Browser> {
@@ -91,6 +99,9 @@ export async function launch(options: McpLaunchOptions): Promise<Browser> {
9199
if (customDevTools) {
92100
args.push(`--custom-devtools-frontend=file://${customDevTools}`);
93101
}
102+
if (options.devtools) {
103+
args.push('--auto-open-devtools-for-tabs');
104+
}
94105
let puppeterChannel: ChromeReleaseChannel | undefined;
95106
if (!executablePath) {
96107
puppeterChannel =
@@ -102,6 +113,7 @@ export async function launch(options: McpLaunchOptions): Promise<Browser> {
102113
try {
103114
return await puppeteer.launch({
104115
...connectOptions,
116+
targetFilter: makeTargetFilter(options.devtools),
105117
channel: puppeterChannel,
106118
executablePath,
107119
defaultViewport: null,
@@ -138,16 +150,16 @@ async function ensureBrowserLaunched(
138150
return browser;
139151
}
140152

141-
export async function resolveBrowser(options: {
142-
browserUrl?: string;
143-
executablePath?: string;
144-
customDevTools?: string;
145-
channel?: Channel;
146-
headless: boolean;
147-
isolated: boolean;
148-
}) {
153+
export async function resolveBrowser(
154+
options: McpLaunchOptions & {
155+
browserUrl?: string;
156+
},
157+
) {
149158
const browser = options.browserUrl
150-
? await ensureBrowserConnected(options.browserUrl)
159+
? await ensureBrowserConnected({
160+
browserURL: options.browserUrl,
161+
devtools: options.devtools,
162+
})
151163
: await ensureBrowserLaunched(options);
152164

153165
return browser;

src/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ export const cliOptions = {
8282
describe: 'Save the logs to file.',
8383
hidden: true,
8484
},
85+
experimentalDevTools: {
86+
type: 'boolean' as const,
87+
describe: 'Whether to enable automation over DevTools targets',
88+
default: false,
89+
hidden: true,
90+
},
8591
};
8692

8793
const yargsInstance = yargs(hideBin(process.argv))
@@ -155,9 +161,12 @@ async function getContext(): Promise<McpContext> {
155161
customDevTools: args.customDevtools,
156162
channel: args.channel as Channel,
157163
isolated: args.isolated,
164+
devtools: args.experimentalDevTools,
158165
});
159166
if (!context) {
160-
context = await McpContext.from(browser, logger);
167+
context = await McpContext.from(browser, logger, {
168+
devtools: args.experimentalDevTools,
169+
});
161170
}
162171
return context;
163172
}

tests/browser.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ describe('browser', () => {
1717
headless: true,
1818
isolated: false,
1919
userDataDir: folderPath,
20+
devtools: false,
2021
});
2122
try {
2223
try {
2324
const browser2 = await launch({
2425
headless: true,
2526
isolated: false,
2627
userDataDir: folderPath,
28+
devtools: false,
2729
});
2830
await browser2.close();
2931
assert.fail('not reached');

tests/utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ export async function withBrowser(
3434
}),
3535
);
3636
const response = new McpResponse();
37-
const context = await McpContext.from(browser, logger('test'));
37+
const context = await McpContext.from(browser, logger('test'), {
38+
devtools: false,
39+
});
3840

3941
await cb(response, context);
4042
}

0 commit comments

Comments
 (0)