From 320e974c6a3e3a90a2fcf8dcfe8cf43bf85e54fc Mon Sep 17 00:00:00 2001 From: Yasindu20 Date: Mon, 12 Jan 2026 13:29:16 +0530 Subject: [PATCH 1/6] feat: add --profile-directory option to specify Chrome profile Added a new CLI option to allow users to specify which Chrome profile to use when launching the browser. Changes: - Added --profile-directory CLI argument - Updated BrowserOptions interface - Connected CLI arg to Chrome launch flag - Added documentation in README.md --- README.md | 4 ++++ src/browser.ts | 4 ++++ src/cli.ts | 15 +++++++++++++++ src/main.ts | 1 + 4 files changed, 24 insertions(+) diff --git a/README.md b/README.md index 437233389..53dc86bcb 100644 --- a/README.md +++ b/README.md @@ -443,6 +443,10 @@ The Chrome DevTools MCP server supports the following configuration option: - **Type:** boolean - **Default:** `true` +- **`--profileDirectory`/ `--profile-directory`, `-profile-dir`** + Specify which Chrome profile to use by its directory name (e.g., "Profile 1", "Default"). Only works with --autoConnect or when launching Chrome. + - **Type:** string + Pass them via the `args` property in the JSON configuration. For example: diff --git a/src/browser.ts b/src/browser.ts index e7dffd8db..c1f1bb295 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -144,6 +144,7 @@ interface McpLaunchOptions { }; args?: string[]; devtools: boolean; + profileDirectory?: string; } export async function launch(options: McpLaunchOptions): Promise { @@ -170,6 +171,9 @@ export async function launch(options: McpLaunchOptions): Promise { ...(options.args ?? []), '--hide-crash-restore-bubble', ]; + if (options.profileDirectory) { + args.push(`--profile-directory=${options.profileDirectory}`); + } if (headless) { args.push('--screen-info={3840x2160}'); } diff --git a/src/cli.ts b/src/cli.ts index e1a623e78..6f4d73125 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -183,6 +183,13 @@ export const cliOptions = { default: true, describe: 'Set to false to exclude tools related to network.', }, + profileDirectory: { + type: 'string', + description: + 'Specify which Chrome profile to use by its directory name (e.g., "Profile 1", "Default"). Only works with --autoConnect or when launching Chrome.', + alias: 'profile-dir', + conflicts: ['browserUrl', 'wsEndpoint'], + }, } satisfies Record; export function parseArguments(version: string, argv = process.argv) { @@ -247,6 +254,14 @@ export function parseArguments(version: string, argv = process.argv) { '$0 --auto-connect --channel=canary', 'Connect to a canary Chrome instance (Chrome 145+) running instead of launching a new instance', ], + [ + '$0 --auto-connect --profile-directory="Profile 1"', + 'Connect to Chrome using a specific profile (requires Chrome 145+)', + ], + [ + '$0 --channel=stable --profile-directory="Work Profile"', + 'Launch stable Chrome with a specific profile', + ], ]); return yargsInstance diff --git a/src/main.ts b/src/main.ts index ab6ee3019..557102605 100644 --- a/src/main.ts +++ b/src/main.ts @@ -81,6 +81,7 @@ async function getContext(): Promise { args: extraArgs, acceptInsecureCerts: args.acceptInsecureCerts, devtools, + profileDirectory: args.profileDirectory, }); if (context?.browser !== browser) { From e15f5dc8d944e96a965d7ba74eeefd14d90ab5a6 Mon Sep 17 00:00:00 2001 From: Yasindu Dasanga De Mel <89267432+Yasindu20@users.noreply.github.com> Date: Tue, 13 Jan 2026 08:56:31 +0530 Subject: [PATCH 2/6] Update src/cli.ts Co-authored-by: Sebastian Benz --- src/cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli.ts b/src/cli.ts index 6f4d73125..0ac5f6dc9 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -186,7 +186,7 @@ export const cliOptions = { profileDirectory: { type: 'string', description: - 'Specify which Chrome profile to use by its directory name (e.g., "Profile 1", "Default"). Only works with --autoConnect or when launching Chrome.', + 'Specify which Chrome profile to use by specifying its directory name (e.g., "Profile 1", "Default") inside a chrome user data directory. Only works with --autoConnect or when launching Chrome via the Chrome DevTools MCP server.', alias: 'profile-dir', conflicts: ['browserUrl', 'wsEndpoint'], }, From 08f98e40d2de4e2f38bf0af10ec36fdfa82c02cb Mon Sep 17 00:00:00 2001 From: Yasindu20 Date: Tue, 13 Jan 2026 12:04:29 +0530 Subject: [PATCH 3/6] added a validation step to detect a profile mismatch --- src/browser.ts | 38 ++++++++++++++++++++++++++++++++++++++ src/cli.ts | 4 ++-- src/main.ts | 1 + 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/browser.ts b/src/browser.ts index 0fa9cce6c..347d6f7e6 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -43,6 +43,13 @@ function makeTargetFilter() { }; } +//Extracts the profile directory name from a user data dir path. +function getProfileNameFromUserDataDir(userDataDir: string): string { + const normalized = userDataDir.replace(/\\/g, '/'); + const parts = normalized.split('/'); + return parts[parts.length - 1] || 'Default'; +} + export async function ensureBrowserConnected(options: { browserURL?: string; wsEndpoint?: string; @@ -50,6 +57,7 @@ export async function ensureBrowserConnected(options: { devtools: boolean; channel?: Channel; userDataDir?: string; + profileDirectory?: string; }) { const {channel} = options; if (browser?.connected) { @@ -126,6 +134,36 @@ export async function ensureBrowserConnected(options: { }, ); } + + if (options.profileDirectory && options.userDataDir) { + try { + const portPath = path.join(options.userDataDir, 'DevToolsActivatePort'); + const fileContent = await fs.promises.readFile(portPath, 'utf8'); + const lines = fileContent.split('\n').map(line => line.trim()).filter(line => line); + + if (lines.length >= 2) { + const browserPath = lines[1]; + const actualProfile = getProfileNameFromUserDataDir(browserPath); + const requestedProfile = options.profileDirectory; + + if (actualProfile !== requestedProfile) { + await browser.disconnect(); + throw new Error( + `Profile mismatch: Requested profile "${requestedProfile}" but Chrome is running with profile "${actualProfile}". ` + + `Please close Chrome and restart with the correct profile, or remove the --profile-directory flag.` + ); + } + + logger(`Successfully validated profile: ${actualProfile}`); + } + } catch (error) { + if ((error as Error).message.includes('Profile mismatch')) { + throw error; + } + + logger('Could not validate profile directory: ', error); + } + } logger('Connected Puppeteer'); return browser; } diff --git a/src/cli.ts b/src/cli.ts index 1533f75d1..0c12774bd 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -200,10 +200,10 @@ export const cliOptions = { }, profileDirectory: { type: 'string', - description: - 'Specify which Chrome profile to use by specifying its directory name (e.g., "Profile 1", "Default") inside a chrome user data directory. Only works with --autoConnect or when launching Chrome via the Chrome DevTools MCP server.', + description: 'Specify which Chrome profile to use by specifying its directory name (e.g., "Profile 1", "Default") inside a chrome user data directory. Only works with --autoConnect or when launching Chrome via the Chrome DevTools MCP server.', alias: 'profile-dir', conflicts: ['browserUrl', 'wsEndpoint'], + }, usageStatistics: { type: 'boolean', // Marked as `false` until the feature is ready to be enabled by default. diff --git a/src/main.ts b/src/main.ts index 4344b18ca..5821e11bd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -72,6 +72,7 @@ async function getContext(): Promise { channel: args.autoConnect ? (args.channel as Channel) : undefined, userDataDir: args.userDataDir, devtools, + profileDirectory: args.profileDirectory, }) : await ensureBrowserLaunched({ headless: args.headless, From 3d5da8bbc518a6d8e70b31b3df4d840e1d7d2abc Mon Sep 17 00:00:00 2001 From: Yasindu20 Date: Tue, 13 Jan 2026 12:08:50 +0530 Subject: [PATCH 4/6] Run the npm run docs to up to date the documentation --- README.md | 2 +- src/browser.ts | 9 ++++++--- src/cli.ts | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0d6669b20..2e1ce1203 100644 --- a/README.md +++ b/README.md @@ -448,7 +448,7 @@ The Chrome DevTools MCP server supports the following configuration option: - **Default:** `true` - **`--profileDirectory`/ `--profile-directory`, `-profile-dir`** - Specify which Chrome profile to use by its directory name (e.g., "Profile 1", "Default"). Only works with --autoConnect or when launching Chrome. + Specify which Chrome profile to use by specifying its directory name (e.g., "Profile 1", "Default") inside a chrome user data directory. Only works with --autoConnect or when launching Chrome via the Chrome DevTools MCP server. - **Type:** string diff --git a/src/browser.ts b/src/browser.ts index 347d6f7e6..41451b632 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -43,7 +43,7 @@ function makeTargetFilter() { }; } -//Extracts the profile directory name from a user data dir path. +//Extracts the profile directory name from a user data dir path. function getProfileNameFromUserDataDir(userDataDir: string): string { const normalized = userDataDir.replace(/\\/g, '/'); const parts = normalized.split('/'); @@ -139,7 +139,10 @@ export async function ensureBrowserConnected(options: { try { const portPath = path.join(options.userDataDir, 'DevToolsActivatePort'); const fileContent = await fs.promises.readFile(portPath, 'utf8'); - const lines = fileContent.split('\n').map(line => line.trim()).filter(line => line); + const lines = fileContent + .split('\n') + .map(line => line.trim()) + .filter(line => line); if (lines.length >= 2) { const browserPath = lines[1]; @@ -150,7 +153,7 @@ export async function ensureBrowserConnected(options: { await browser.disconnect(); throw new Error( `Profile mismatch: Requested profile "${requestedProfile}" but Chrome is running with profile "${actualProfile}". ` + - `Please close Chrome and restart with the correct profile, or remove the --profile-directory flag.` + `Please close Chrome and restart with the correct profile, or remove the --profile-directory flag.`, ); } diff --git a/src/cli.ts b/src/cli.ts index 0c12774bd..a2ed4d693 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -200,7 +200,8 @@ export const cliOptions = { }, profileDirectory: { type: 'string', - description: 'Specify which Chrome profile to use by specifying its directory name (e.g., "Profile 1", "Default") inside a chrome user data directory. Only works with --autoConnect or when launching Chrome via the Chrome DevTools MCP server.', + description: + 'Specify which Chrome profile to use by specifying its directory name (e.g., "Profile 1", "Default") inside a chrome user data directory. Only works with --autoConnect or when launching Chrome via the Chrome DevTools MCP server.', alias: 'profile-dir', conflicts: ['browserUrl', 'wsEndpoint'], }, From 276f81a94abe6aa8009ef00adf83bc7158926996 Mon Sep 17 00:00:00 2001 From: Yasindu20 Date: Tue, 13 Jan 2026 20:54:23 +0530 Subject: [PATCH 5/6] fixed the DevToolsActivePort, removed the profile-related checks from the connection path, switched to referencing browser.browserContexts() --- src/browser.ts | 93 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 31 deletions(-) diff --git a/src/browser.ts b/src/browser.ts index 41451b632..f11e17d1f 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -11,6 +11,7 @@ import path from 'node:path'; import {logger} from './logger.js'; import type { Browser, + BrowserContext, ChromeReleaseChannel, LaunchOptions, Target, @@ -50,6 +51,30 @@ function getProfileNameFromUserDataDir(userDataDir: string): string { return parts[parts.length - 1] || 'Default'; } +async function getBrowserContextForProfile( + browser: Browser, + profileDirectory?: string, +): Promise { + if (!profileDirectory) { + return browser.defaultBrowserContext(); + } + + try { + const contexts = browser.browserContexts(); + logger(`Found ${contexts.length} browser context(s)`); + + logger( + `Profile directory "${profileDirectory}" specified. ` + + `Using default browser context. Full profile support will be added in a future update.`, + ); + + return browser.defaultBrowserContext(); + } catch (error) { + logger('Error getting browser contexts: ', error); + return browser.defaultBrowserContext(); + } +} + export async function ensureBrowserConnected(options: { browserURL?: string; wsEndpoint?: string; @@ -135,39 +160,12 @@ export async function ensureBrowserConnected(options: { ); } - if (options.profileDirectory && options.userDataDir) { - try { - const portPath = path.join(options.userDataDir, 'DevToolsActivatePort'); - const fileContent = await fs.promises.readFile(portPath, 'utf8'); - const lines = fileContent - .split('\n') - .map(line => line.trim()) - .filter(line => line); - - if (lines.length >= 2) { - const browserPath = lines[1]; - const actualProfile = getProfileNameFromUserDataDir(browserPath); - const requestedProfile = options.profileDirectory; - - if (actualProfile !== requestedProfile) { - await browser.disconnect(); - throw new Error( - `Profile mismatch: Requested profile "${requestedProfile}" but Chrome is running with profile "${actualProfile}". ` + - `Please close Chrome and restart with the correct profile, or remove the --profile-directory flag.`, - ); - } - - logger(`Successfully validated profile: ${actualProfile}`); - } - } catch (error) { - if ((error as Error).message.includes('Profile mismatch')) { - throw error; - } + logger('Connected Puppeteer'); - logger('Could not validate profile directory: ', error); - } + if (options.profileDirectory) { + await getBrowserContextForProfile(browser, options.profileDirectory); + logger(`Using browser context for profile: ${options.profileDirectory}`); } - logger('Connected Puppeteer'); return browser; } @@ -215,6 +213,9 @@ export async function launch(options: McpLaunchOptions): Promise { ]; if (options.profileDirectory) { args.push(`--profile-directory=${options.profileDirectory}`); + logger( + `Launcing Chrome with profile directory: ${options.profileDirectory}`, + ); } const ignoreDefaultArgs: LaunchOptions['ignoreDefaultArgs'] = options.ignoreDefaultChromeArgs ?? false; @@ -260,6 +261,36 @@ export async function launch(options: McpLaunchOptions): Promise { contentHeight: options.viewport.height, }); } + + if (options.profileDirectory && userDataDir) { + try { + await new Promise(resolve => setTimeout(resolve, 500)); + + const portPath = path.join(userDataDir, 'DevToolsActivePort'); + const fileContent = await fs.promises.readFile(portPath, 'utf8'); + const lines = fileContent + .split('\n') + .map(line => line.trim()) + .filter(line => line); + + if (lines.length >= 2) { + const browserPath = lines[1]; + const actualProfile = getProfileNameFromUserDataDir(browserPath); + const requestedProfile = options.profileDirectory; + + if (actualProfile !== requestedProfile) { + logger( + `Warning: Requested profile "${requestedProfile}" but Chrome may be using profile "${actualProfile}". ` + + `This could happen if Chrome is managing profiles differently.`, + ); + } else { + logger(`Successfully validated profile: ${actualProfile}`); + } + } + } catch (error) { + logger('Could not validate profile directory after launch: ', error); + } + } return browser; } catch (error) { if ( From 6744657d89061c7d950a6653aef0d42fc2ed4959 Mon Sep 17 00:00:00 2001 From: Yasindu20 Date: Tue, 13 Jan 2026 22:08:45 +0530 Subject: [PATCH 6/6] Probe each BrowserContext --- src/browser.ts | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/browser.ts b/src/browser.ts index f11e17d1f..6ec90ae1a 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -63,6 +63,53 @@ async function getBrowserContextForProfile( const contexts = browser.browserContexts(); logger(`Found ${contexts.length} browser context(s)`); + for (const context of contexts) { + let page; + try { + page = await context.newPage(); + + await page.goto('chrome://version', { + waitUntil: 'domcontentloaded', + timeout: 3_000, + }); + + const profilePath: string | null = await page.evaluate(() => { + const body = document.querySelector('body'); + if (!body) return null; + const text = (body.innerText || ''); + const match = text.match(/Profile Path:\s*(.+)/i); + return match ? match[1].trim() : null; + }); + + try { + await page.close(); + } catch { + //ignore close errors + } + + if (!profilePath) { + continue; + } + + const actualProfile = getProfileNameFromUserDataDir(profilePath); + logger(`Probed context: profilePath=${profilePath} => profileName=${actualProfile}`); + + if (actualProfile === profileDirectory) { + logger(`Matched profile directory "${profileDirectory}" to a browser context`); + return context; + } + } catch (error) { + logger('Error probing a browser context for profile: ', error); + try { + if (page && !page.isClosed()) { + await page.close(); + } + } catch { + // ignore + } + } + } + logger( `Profile directory "${profileDirectory}" specified. ` + `Using default browser context. Full profile support will be added in a future update.`,