From 71e550b0f959fa63bb69232dd60ef04868452ce9 Mon Sep 17 00:00:00 2001 From: Alex Rudenko Date: Mon, 8 Dec 2025 12:10:13 +0100 Subject: [PATCH 1/2] feat: support --auto-connect --- README.md | 5 +++++ src/browser.ts | 12 +++++++++++- src/cli.ts | 21 +++++++++++++++++++++ src/main.ts | 4 +++- tests/cli.test.ts | 14 ++++++++++++++ 5 files changed, 54 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a51a92ca2..637176e0f 100644 --- a/README.md +++ b/README.md @@ -350,6 +350,11 @@ The Chrome DevTools MCP server supports the following configuration option: +- **`--autoConnect`** + If specified, automatically connects to a browser (Chrome 145+) running in the user data directory identified by the channel param. + - **Type:** boolean + - **Default:** `false` + - **`--browserUrl`, `-u`** Connect to a running, debuggable Chrome instance (e.g. `http://127.0.0.1:9222`). For more details see: https://github.com/ChromeDevTools/chrome-devtools-mcp#connecting-to-a-running-chrome-instance. - **Type:** string diff --git a/src/browser.ts b/src/browser.ts index 3120a413f..631cb2a25 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -48,7 +48,9 @@ export async function ensureBrowserConnected(options: { wsEndpoint?: string; wsHeaders?: Record; devtools: boolean; + channel?: Channel; }) { + const {channel} = options; if (browser?.connected) { return browser; } @@ -66,8 +68,16 @@ export async function ensureBrowserConnected(options: { } } else if (options.browserURL) { connectOptions.browserURL = options.browserURL; + } else if (channel) { + const puppeteerChannel = + channel !== 'stable' + ? (`chrome-${channel}` as ChromeReleaseChannel) + : 'chrome'; + connectOptions.channel = puppeteerChannel; } else { - throw new Error('Either browserURL or wsEndpoint must be provided'); + throw new Error( + 'Either browserURL, wsEndpoint or channel must be provided', + ); } logger('Connecting Puppeteer to ', JSON.stringify(connectOptions)); diff --git a/src/cli.ts b/src/cli.ts index 4a5d89ceb..bcad62b73 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -8,6 +8,19 @@ import type {YargsOptions} from './third_party/index.js'; import {yargs, hideBin} from './third_party/index.js'; export const cliOptions = { + autoConnect: { + type: 'boolean', + description: + 'If specified, automatically connects to a browser (Chrome 145+) running in the user data directory identified by the channel param.', + conflicts: ['isolated', 'executablePath', 'userDataDir'], + default: false, + coerce: (value: boolean | undefined) => { + if (!value) { + return; + } + return value; + }, + }, browserUrl: { type: 'string', description: @@ -221,6 +234,14 @@ export function parseArguments(version: string, argv = process.argv) { '$0 --user-data-dir=/tmp/user-data-dir', 'Use a custom user data directory', ], + [ + '$0 --auto-connect', + 'Connect to a stable Chrome instance running instead of launching a new instance', + ], + [ + '$0 --auto-connect --channel=canary', + 'Connect to a canary Chrome instance running instead of launching a new instance', + ], ]); return yargsInstance diff --git a/src/main.ts b/src/main.ts index 2d96ab406..f3d07a1b5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -54,11 +54,13 @@ async function getContext(): Promise { } const devtools = args.experimentalDevtools ?? false; const browser = - args.browserUrl || args.wsEndpoint + args.browserUrl || args.wsEndpoint || args.autoConnect ? await ensureBrowserConnected({ browserURL: args.browserUrl, wsEndpoint: args.wsEndpoint, wsHeaders: args.wsHeaders, + // Important: only pass channel, if autoConnect is true. + channel: args.autoConnect ? (args.channel as Channel) : undefined, devtools, }) : await ensureBrowserLaunched({ diff --git a/tests/cli.test.ts b/tests/cli.test.ts index df0835c1f..11e93a3de 100644 --- a/tests/cli.test.ts +++ b/tests/cli.test.ts @@ -17,6 +17,8 @@ describe('cli args parsing', () => { categoryPerformance: true, 'category-network': true, categoryNetwork: true, + 'auto-connect': undefined, + autoConnect: undefined, }; it('parses with default args', async () => { @@ -208,4 +210,16 @@ describe('cli args parsing', () => { categoryEmulation: false, }); }); + it('parses auto-connect', async () => { + const args = parseArguments('1.0.0', ['node', 'main.js', '--auto-connect']); + assert.deepStrictEqual(args, { + ...defaultArgs, + _: [], + headless: false, + $0: 'npx chrome-devtools-mcp@latest', + channel: 'stable', + 'auto-connect': true, + autoConnect: true, + }); + }); }); From c946d41f7258298cd1fd43766035c5de575b1e59 Mon Sep 17 00:00:00 2001 From: Alex Rudenko Date: Mon, 8 Dec 2025 13:16:59 +0100 Subject: [PATCH 2/2] error --- src/browser.ts | 11 ++++++++++- src/cli.ts | 4 ++-- src/main.ts | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/browser.ts b/src/browser.ts index 631cb2a25..b15b5efa4 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -81,7 +81,16 @@ export async function ensureBrowserConnected(options: { } logger('Connecting Puppeteer to ', JSON.stringify(connectOptions)); - browser = await puppeteer.connect(connectOptions); + try { + browser = await puppeteer.connect(connectOptions); + } catch (err) { + throw new Error( + 'Could not connect to Chrome. Check if Chrome is running and remote debugging is enabled.', + { + cause: err, + }, + ); + } logger('Connected Puppeteer'); return browser; } diff --git a/src/cli.ts b/src/cli.ts index bcad62b73..9da407510 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -236,11 +236,11 @@ export function parseArguments(version: string, argv = process.argv) { ], [ '$0 --auto-connect', - 'Connect to a stable Chrome instance running instead of launching a new instance', + 'Connect to a stable Chrome instance (Chrome 145+) running instead of launching a new instance', ], [ '$0 --auto-connect --channel=canary', - 'Connect to a canary Chrome instance running instead of launching a new instance', + 'Connect to a canary Chrome instance (Chrome 145+) running instead of launching a new instance', ], ]); diff --git a/src/main.ts b/src/main.ts index f3d07a1b5..e785e9c74 100644 --- a/src/main.ts +++ b/src/main.ts @@ -142,7 +142,10 @@ function registerTool(tool: ToolDefinition): void { }; } catch (err) { logger(`${tool.name} error:`, err, err?.stack); - const errorText = err && 'message' in err ? err.message : String(err); + let errorText = err && 'message' in err ? err.message : String(err); + if ('cause' in err && err.cause) { + errorText += `\nCause: ${err.cause.message}`; + } return { content: [ {