Skip to content

Commit 2982195

Browse files
authored
chore: experimental devtools-on-devtools support (#51)
Allows using the MCP server for the devtools development.
1 parent 1674c09 commit 2982195

File tree

4 files changed

+48
-24
lines changed

4 files changed

+48
-24
lines changed

src/browser.ts

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,45 +11,48 @@ import path from 'node:path';
1111
import type {
1212
Browser,
1313
ChromeReleaseChannel,
14-
ConnectOptions,
1514
LaunchOptions,
1615
Target,
1716
} from 'puppeteer-core';
1817
import puppeteer from 'puppeteer-core';
1918

2019
let browser: Browser | undefined;
2120

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

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

41-
const connectOptions: ConnectOptions = {
42-
targetFilter,
43-
};
44-
45-
export async function ensureBrowserConnected(browserURL: string) {
44+
export async function ensureBrowserConnected(options: {
45+
browserURL: string;
46+
devtools: boolean;
47+
}) {
4648
if (browser?.connected) {
4749
return browser;
4850
}
4951
browser = await puppeteer.connect({
50-
...connectOptions,
51-
browserURL,
52+
targetFilter: makeTargetFilter(options.devtools),
53+
browserURL: options.browserURL,
5254
defaultViewport: null,
55+
handleDevToolsAsPage: options.devtools,
5356
});
5457
return browser;
5558
}
@@ -68,6 +71,7 @@ interface McpLaunchOptions {
6871
height: number;
6972
};
7073
args?: string[];
74+
devtools: boolean;
7175
}
7276

7377
export async function launch(options: McpLaunchOptions): Promise<Browser> {
@@ -101,6 +105,9 @@ export async function launch(options: McpLaunchOptions): Promise<Browser> {
101105
args.push('--screen-info={3840x2160}');
102106
}
103107
let puppeteerChannel: ChromeReleaseChannel | undefined;
108+
if (options.devtools) {
109+
args.push('--auto-open-devtools-for-tabs');
110+
}
104111
if (!executablePath) {
105112
puppeteerChannel =
106113
channel && channel !== 'stable'
@@ -110,15 +117,16 @@ export async function launch(options: McpLaunchOptions): Promise<Browser> {
110117

111118
try {
112119
const browser = await puppeteer.launch({
113-
...connectOptions,
114120
channel: puppeteerChannel,
121+
targetFilter: makeTargetFilter(options.devtools),
115122
executablePath,
116123
defaultViewport: null,
117124
userDataDir,
118125
pipe: true,
119126
headless,
120127
args,
121128
acceptInsecureCerts: options.acceptInsecureCerts,
129+
handleDevToolsAsPage: options.devtools,
122130
});
123131
if (options.logFile) {
124132
// FIXME: we are probably subscribing too late to catch startup logs. We

src/cli.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ export const cliOptions = {
8888
type: 'boolean',
8989
description: `If enabled, ignores errors relative to self-signed and expired certificates. Use with caution.`,
9090
},
91+
experimentalDevtools: {
92+
type: 'boolean' as const,
93+
describe: 'Whether to enable automation over DevTools targets',
94+
hidden: true,
95+
},
9196
} satisfies Record<string, YargsOptions>;
9297

9398
export function parseArguments(version: string, argv = process.argv) {

src/main.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,12 @@ async function getContext(): Promise<McpContext> {
7373
if (args.proxyServer) {
7474
extraArgs.push(`--proxy-server=${args.proxyServer}`);
7575
}
76+
const devtools = args.experimentalDevtools ?? false;
7677
const browser = args.browserUrl
77-
? await ensureBrowserConnected(args.browserUrl)
78+
? await ensureBrowserConnected({
79+
browserURL: args.browserUrl,
80+
devtools,
81+
})
7882
: await ensureBrowserLaunched({
7983
headless: args.headless,
8084
executablePath: args.executablePath,
@@ -85,6 +89,7 @@ async function getContext(): Promise<McpContext> {
8589
viewport: args.viewport,
8690
args: extraArgs,
8791
acceptInsecureCerts: args.acceptInsecureCerts,
92+
devtools,
8893
});
8994

9095
if (context?.browser !== browser) {
@@ -143,6 +148,9 @@ function registerTool(tool: ToolDefinition): void {
143148
isError: true,
144149
};
145150
}
151+
} catch (err) {
152+
logger(`${tool.name} error: ${err.message}`);
153+
throw err;
146154
} finally {
147155
guard.dispose();
148156
}

tests/browser.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ describe('browser', () => {
2121
isolated: false,
2222
userDataDir: folderPath,
2323
executablePath: executablePath(),
24+
devtools: false,
2425
});
2526
try {
2627
try {
@@ -29,6 +30,7 @@ describe('browser', () => {
2930
isolated: false,
3031
userDataDir: folderPath,
3132
executablePath: executablePath(),
33+
devtools: false,
3234
});
3335
await browser2.close();
3436
assert.fail('not reached');
@@ -55,6 +57,7 @@ describe('browser', () => {
5557
width: 1501,
5658
height: 801,
5759
},
60+
devtools: false,
5861
});
5962
try {
6063
const [page] = await browser.pages();

0 commit comments

Comments
 (0)