Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,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 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

<!-- END AUTO GENERATED OPTIONS -->

Pass them via the `args` property in the JSON configuration. For example:
Expand Down
45 changes: 45 additions & 0 deletions src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,21 @@ 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;
wsHeaders?: Record<string, string>;
devtools: boolean;
channel?: Channel;
userDataDir?: string;
profileDirectory?: string;
}) {
const {channel} = options;
if (browser?.connected) {
Expand Down Expand Up @@ -126,6 +134,39 @@ export async function ensureBrowserConnected(options: {
},
);
}

if (options.profileDirectory && options.userDataDir) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think this check should be in the connection code branch.

try {
const portPath = path.join(options.userDataDir, 'DevToolsActivatePort');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

such file does not exist, did you mean DevToolsPort. A single user data dir hosts multiple profiles so I am not sure if the check handles it correctly.

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;
}
Expand All @@ -145,6 +186,7 @@ interface McpLaunchOptions {
chromeArgs?: string[];
ignoreDefaultChromeArgs?: string[];
devtools: boolean;
profileDirectory?: string;
}

export async function launch(options: McpLaunchOptions): Promise<Browser> {
Expand All @@ -171,6 +213,9 @@ export async function launch(options: McpLaunchOptions): Promise<Browser> {
...(options.chromeArgs ?? []),
'--hide-crash-restore-bubble',
];
if (options.profileDirectory) {
args.push(`--profile-directory=${options.profileDirectory}`);
}
const ignoreDefaultArgs: LaunchOptions['ignoreDefaultArgs'] =
options.ignoreDefaultChromeArgs ?? false;

Expand Down
15 changes: 15 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@ export const cliOptions = {
default: true,
describe: 'Set to false to exclude tools related to network.',
},
profileDirectory: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we probably should be using https://pptr.dev/api/puppeteer.browser.browsercontexts to locate the browser context for the profile (currently the default browser context is used)

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.',
alias: 'profile-dir',
conflicts: ['browserUrl', 'wsEndpoint'],
},
usageStatistics: {
type: 'boolean',
// Marked as `false` until the feature is ready to be enabled by default.
Expand Down Expand Up @@ -273,6 +280,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
Expand Down
2 changes: 2 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ async function getContext(): Promise<McpContext> {
channel: args.autoConnect ? (args.channel as Channel) : undefined,
userDataDir: args.userDataDir,
devtools,
profileDirectory: args.profileDirectory,
})
: await ensureBrowserLaunched({
headless: args.headless,
Expand All @@ -85,6 +86,7 @@ async function getContext(): Promise<McpContext> {
ignoreDefaultChromeArgs,
acceptInsecureCerts: args.acceptInsecureCerts,
devtools,
profileDirectory: args.profileDirectory,
});

if (context?.browser !== browser) {
Expand Down