Skip to content

Commit 532f5e0

Browse files
committed
fix: enable extensions when connecting to existing Chrome
1 parent 94de19c commit 532f5e0

3 files changed

Lines changed: 131 additions & 36 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,9 @@ In these cases, start Chrome first and let the Chrome DevTools MCP server connec
625625
- **Automatic connection (available in Chrome 144)**: best for sharing state between manual and agent-driven testing.
626626
- **Manual connection via remote debugging port**: best when running inside a sandboxed environment.
627627

628+
> [!NOTE]
629+
> To include extension pages and extension service workers while connecting to an existing Chrome instance, add `--category-extensions`. This applies to `--autoConnect`, `--browserUrl`, and `--wsEndpoint`.
630+
628631
#### Automatically connecting to a running Chrome instance
629632

630633
**Step 1:** Set up remote debugging in Chrome

src/index.ts

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,65 @@ import {pageIdSchema} from './tools/ToolDefinition.js';
2828
import {createTools} from './tools/tools.js';
2929
import {VERSION} from './version.js';
3030

31+
type ServerArgs = ReturnType<typeof parseArguments>;
32+
33+
interface BrowserResolvers {
34+
ensureBrowserConnected: typeof ensureBrowserConnected;
35+
ensureBrowserLaunched: typeof ensureBrowserLaunched;
36+
}
37+
38+
export async function resolveBrowser(
39+
serverArgs: ServerArgs,
40+
options: {
41+
logFile?: fs.WriteStream;
42+
},
43+
resolvers: BrowserResolvers = {
44+
ensureBrowserConnected,
45+
ensureBrowserLaunched,
46+
},
47+
) {
48+
const chromeArgs: string[] = (serverArgs.chromeArg ?? []).map(String);
49+
const ignoreDefaultChromeArgs: string[] = (
50+
serverArgs.ignoreDefaultChromeArg ?? []
51+
).map(String);
52+
if (serverArgs.proxyServer) {
53+
chromeArgs.push(`--proxy-server=${serverArgs.proxyServer}`);
54+
}
55+
const devtools = serverArgs.experimentalDevtools ?? false;
56+
return serverArgs.browserUrl ||
57+
serverArgs.wsEndpoint ||
58+
serverArgs.autoConnect
59+
? await resolvers.ensureBrowserConnected({
60+
browserURL: serverArgs.browserUrl,
61+
wsEndpoint: serverArgs.wsEndpoint,
62+
wsHeaders: serverArgs.wsHeaders,
63+
// Important: only pass channel, if autoConnect is true.
64+
channel: serverArgs.autoConnect
65+
? (serverArgs.channel as Channel)
66+
: undefined,
67+
userDataDir: serverArgs.userDataDir,
68+
devtools,
69+
enableExtensions: serverArgs.categoryExtensions,
70+
})
71+
: await resolvers.ensureBrowserLaunched({
72+
headless: serverArgs.headless,
73+
executablePath: serverArgs.executablePath,
74+
channel: serverArgs.channel as Channel,
75+
isolated: serverArgs.isolated ?? false,
76+
userDataDir: serverArgs.userDataDir,
77+
logFile: options.logFile,
78+
viewport: serverArgs.viewport,
79+
chromeArgs,
80+
ignoreDefaultChromeArgs,
81+
acceptInsecureCerts: serverArgs.acceptInsecureCerts,
82+
devtools,
83+
enableExtensions: serverArgs.categoryExtensions,
84+
viaCli: serverArgs.viaCli,
85+
});
86+
}
87+
3188
export async function createMcpServer(
32-
serverArgs: ReturnType<typeof parseArguments>,
89+
serverArgs: ServerArgs,
3390
options: {
3491
logFile?: fs.WriteStream;
3592
},
@@ -59,42 +116,8 @@ export async function createMcpServer(
59116

60117
let context: McpContext;
61118
async function getContext(): Promise<McpContext> {
62-
const chromeArgs: string[] = (serverArgs.chromeArg ?? []).map(String);
63-
const ignoreDefaultChromeArgs: string[] = (
64-
serverArgs.ignoreDefaultChromeArg ?? []
65-
).map(String);
66-
if (serverArgs.proxyServer) {
67-
chromeArgs.push(`--proxy-server=${serverArgs.proxyServer}`);
68-
}
69119
const devtools = serverArgs.experimentalDevtools ?? false;
70-
const browser =
71-
serverArgs.browserUrl || serverArgs.wsEndpoint || serverArgs.autoConnect
72-
? await ensureBrowserConnected({
73-
browserURL: serverArgs.browserUrl,
74-
wsEndpoint: serverArgs.wsEndpoint,
75-
wsHeaders: serverArgs.wsHeaders,
76-
// Important: only pass channel, if autoConnect is true.
77-
channel: serverArgs.autoConnect
78-
? (serverArgs.channel as Channel)
79-
: undefined,
80-
userDataDir: serverArgs.userDataDir,
81-
devtools,
82-
})
83-
: await ensureBrowserLaunched({
84-
headless: serverArgs.headless,
85-
executablePath: serverArgs.executablePath,
86-
channel: serverArgs.channel as Channel,
87-
isolated: serverArgs.isolated ?? false,
88-
userDataDir: serverArgs.userDataDir,
89-
logFile: options.logFile,
90-
viewport: serverArgs.viewport,
91-
chromeArgs,
92-
ignoreDefaultChromeArgs,
93-
acceptInsecureCerts: serverArgs.acceptInsecureCerts,
94-
devtools,
95-
enableExtensions: serverArgs.categoryExtensions,
96-
viaCli: serverArgs.viaCli,
97-
});
120+
const browser = await resolveBrowser(serverArgs, options);
98121

99122
if (context?.browser !== browser) {
100123
context = await McpContext.from(browser, logger, {

tests/resolveBrowser.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import assert from 'node:assert';
8+
import {describe, it} from 'node:test';
9+
10+
import {parseArguments} from '../src/bin/chrome-devtools-mcp-cli-options.js';
11+
import {resolveBrowser} from '../src/index.js';
12+
import type {Browser} from '../src/third_party/index.js';
13+
14+
describe('resolveBrowser', () => {
15+
for (const [label, args] of [
16+
[
17+
'browserUrl',
18+
['--browser-url', 'http://127.0.0.1:9222', '--category-extensions'],
19+
],
20+
[
21+
'wsEndpoint',
22+
[
23+
'--ws-endpoint',
24+
'ws://127.0.0.1:9222/devtools/browser/test',
25+
'--category-extensions',
26+
],
27+
],
28+
[
29+
'autoConnect',
30+
[
31+
'--auto-connect',
32+
'--user-data-dir',
33+
'/tmp/profile',
34+
'--category-extensions',
35+
],
36+
],
37+
] as const) {
38+
it(`passes enableExtensions on the connected-browser path via ${label}`, async () => {
39+
const serverArgs = parseArguments('0.0.0', [
40+
'node',
41+
'chrome-devtools-mcp',
42+
...args,
43+
]);
44+
const browser = {} as Browser;
45+
const connectedCalls: Array<Record<string, unknown>> = [];
46+
let launchedCallCount = 0;
47+
48+
const resolvedBrowser = await resolveBrowser(
49+
serverArgs,
50+
{},
51+
{
52+
ensureBrowserConnected: async options => {
53+
connectedCalls.push(options as Record<string, unknown>);
54+
return browser;
55+
},
56+
ensureBrowserLaunched: async () => {
57+
launchedCallCount += 1;
58+
return browser;
59+
},
60+
},
61+
);
62+
63+
assert.strictEqual(resolvedBrowser, browser);
64+
assert.strictEqual(launchedCallCount, 0);
65+
assert.strictEqual(connectedCalls.length, 1);
66+
assert.strictEqual(connectedCalls[0]?.['enableExtensions'], true);
67+
});
68+
}
69+
});

0 commit comments

Comments
 (0)