Skip to content

Commit fe63c3a

Browse files
committed
fix: do not set channel if executablePath is provided
1 parent 4c909bb commit fe63c3a

File tree

4 files changed

+149
-81
lines changed

4 files changed

+149
-81
lines changed

scripts/generate-docs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {Client} from '@modelcontextprotocol/sdk/client/index.js';
88
import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js';
99
import type {Tool} from '@modelcontextprotocol/sdk/types.js';
1010
import {ToolCategories} from '../build/src/tools/categories.js';
11-
import {cliOptions} from '../build/src/main.js';
11+
import {cliOptions} from '../build/src/cli.js';
1212
import fs from 'fs';
1313

1414
const MCP_SERVER_PATH = 'build/src/index.js';

src/cli.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import yargs from 'yargs';
8+
import {hideBin} from 'yargs/helpers';
9+
10+
export const cliOptions = {
11+
browserUrl: {
12+
type: 'string' as const,
13+
description:
14+
'Connect to a running Chrome instance using port forwarding. For more details see: https://developer.chrome.com/docs/devtools/remote-debugging/local-server.',
15+
alias: 'u',
16+
coerce: (url: string) => {
17+
new URL(url);
18+
return url;
19+
},
20+
},
21+
headless: {
22+
type: 'boolean' as const,
23+
description: 'Whether to run in headless (no UI) mode.',
24+
default: false,
25+
},
26+
executablePath: {
27+
type: 'string' as const,
28+
description: 'Path to custom Chrome executable.',
29+
conflicts: 'browserUrl',
30+
alias: 'e',
31+
},
32+
isolated: {
33+
type: 'boolean' as const,
34+
description:
35+
'If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed.',
36+
default: false,
37+
},
38+
customDevtools: {
39+
type: 'string' as const,
40+
description: 'Path to custom DevTools.',
41+
hidden: true,
42+
conflicts: 'browserUrl',
43+
alias: 'd',
44+
},
45+
channel: {
46+
type: 'string' as const,
47+
description:
48+
'Specify a different Chrome channel that should be used. The default is the stable channel version.',
49+
choices: ['stable', 'canary', 'beta', 'dev'] as const,
50+
conflicts: ['browserUrl', 'executablePath'],
51+
},
52+
logFile: {
53+
type: 'string' as const,
54+
describe: 'Save the logs to file.',
55+
hidden: true,
56+
},
57+
};
58+
59+
export function parseArguments(version: string, argv = process.argv) {
60+
const yargsInstance = yargs(hideBin(argv))
61+
.scriptName('npx chrome-devtools-mcp@latest')
62+
.options(cliOptions)
63+
.check(args => {
64+
// We can't set default in the options else
65+
// Yargs will complain
66+
if (!args.channel && !args.browserUrl && !args.executablePath) {
67+
args.channel = 'stable';
68+
}
69+
return true;
70+
})
71+
.example([
72+
[
73+
'$0 --browserUrl http://127.0.0.1:9222',
74+
'Connect to an existing browser instance',
75+
],
76+
['$0 --channel beta', 'Use Chrome Beta installed on this system'],
77+
['$0 --channel canary', 'Use Chrome Canary installed on this system'],
78+
['$0 --channel dev', 'Use Chrome Dev installed on this system'],
79+
['$0 --channel stable', 'Use stable Chrome installed on this system'],
80+
['$0 --logFile /tmp/log.txt', 'Save logs to a file'],
81+
['$0 --help', 'Print CLI options'],
82+
]);
83+
84+
return yargsInstance
85+
.wrap(Math.min(120, yargsInstance.terminalWidth()))
86+
.help()
87+
.version(version)
88+
.parseSync();
89+
}

src/main.ts

Lines changed: 2 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import {
1010
CallToolResult,
1111
SetLevelRequestSchema,
1212
} from '@modelcontextprotocol/sdk/types.js';
13-
import yargs from 'yargs';
14-
import {hideBin} from 'yargs/helpers';
1513

1614
import {McpResponse} from './McpResponse.js';
1715
import {McpContext} from './McpContext.js';
@@ -33,55 +31,7 @@ import path from 'node:path';
3331
import fs from 'node:fs';
3432
import assert from 'node:assert';
3533
import {Mutex} from './Mutex.js';
36-
37-
export const cliOptions = {
38-
browserUrl: {
39-
type: 'string' as const,
40-
description:
41-
'Connect to a running Chrome instance using port forwarding. For more details see: https://developer.chrome.com/docs/devtools/remote-debugging/local-server.',
42-
alias: 'u',
43-
coerce: (url: string) => {
44-
new URL(url);
45-
return url;
46-
},
47-
},
48-
headless: {
49-
type: 'boolean' as const,
50-
description: 'Whether to run in headless (no UI) mode.',
51-
default: false,
52-
},
53-
executablePath: {
54-
type: 'string' as const,
55-
description: 'Path to custom Chrome executable.',
56-
conflicts: 'browserUrl',
57-
alias: 'e',
58-
},
59-
isolated: {
60-
type: 'boolean' as const,
61-
description:
62-
'If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed.',
63-
default: false,
64-
},
65-
customDevtools: {
66-
type: 'string' as const,
67-
description: 'Path to custom DevTools.',
68-
hidden: true,
69-
conflicts: 'browserUrl',
70-
alias: 'd',
71-
},
72-
channel: {
73-
type: 'string' as const,
74-
description:
75-
'Specify a different Chrome channel that should be used. The default is the stable channel version.',
76-
choices: ['stable', 'canary', 'beta', 'dev'] as const,
77-
conflicts: ['browserUrl', 'executablePath'],
78-
},
79-
logFile: {
80-
type: 'string' as const,
81-
describe: 'Save the logs to file.',
82-
hidden: true,
83-
},
84-
};
34+
import {parseArguments} from './cli.js';
8535

8636
function readPackageJson(): {version?: string} {
8737
const currentDir = import.meta.dirname;
@@ -100,35 +50,7 @@ function readPackageJson(): {version?: string} {
10050

10151
const version = readPackageJson().version ?? 'unknown';
10252

103-
const yargsInstance = yargs(hideBin(process.argv))
104-
.scriptName('npx chrome-devtools-mcp@latest')
105-
.options(cliOptions)
106-
.check(args => {
107-
// We can't set default in the options else
108-
// Yargs will complain
109-
if (!args.channel && !args.browserUrl) {
110-
args.channel = 'stable';
111-
}
112-
return true;
113-
})
114-
.example([
115-
[
116-
'$0 --browserUrl http://127.0.0.1:9222',
117-
'Connect to an existing browser instance',
118-
],
119-
['$0 --channel beta', 'Use Chrome Beta installed on this system'],
120-
['$0 --channel canary', 'Use Chrome Canary installed on this system'],
121-
['$0 --channel dev', 'Use Chrome Dev installed on this system'],
122-
['$0 --channel stable', 'Use stable Chrome installed on this system'],
123-
['$0 --logFile /tmp/log.txt', 'Save logs to a file'],
124-
['$0 --help', 'Print CLI options'],
125-
]);
126-
127-
export const args = yargsInstance
128-
.wrap(Math.min(120, yargsInstance.terminalWidth()))
129-
.help()
130-
.version(version)
131-
.parseSync();
53+
export const args = parseArguments(version);
13254

13355
const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;
13456

tests/cli.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
import assert from 'node:assert';
7+
import {describe, it} from 'node:test';
8+
import {parseArguments} from '../src/cli.js';
9+
10+
describe('browser', () => {
11+
it('parses with default args', async () => {
12+
const args = parseArguments('1.0.0', ['node', 'main.js']);
13+
assert.deepStrictEqual(args, {
14+
_: [],
15+
headless: false,
16+
isolated: false,
17+
$0: 'npx chrome-devtools-mcp@latest',
18+
channel: 'stable',
19+
});
20+
});
21+
22+
it('parses with browser url', async () => {
23+
const args = parseArguments('1.0.0', [
24+
'node',
25+
'main.js',
26+
'--browserUrl',
27+
'http://localhost:3000',
28+
]);
29+
assert.deepStrictEqual(args, {
30+
_: [],
31+
headless: false,
32+
isolated: false,
33+
$0: 'npx chrome-devtools-mcp@latest',
34+
'browser-url': 'http://localhost:3000',
35+
browserUrl: 'http://localhost:3000',
36+
u: 'http://localhost:3000',
37+
});
38+
});
39+
40+
it('parses with executable path', async () => {
41+
const args = parseArguments('1.0.0', [
42+
'node',
43+
'main.js',
44+
'--executablePath',
45+
'/tmp/test 123/chrome',
46+
]);
47+
assert.deepStrictEqual(args, {
48+
_: [],
49+
headless: false,
50+
isolated: false,
51+
$0: 'npx chrome-devtools-mcp@latest',
52+
'executable-path': '/tmp/test 123/chrome',
53+
e: '/tmp/test 123/chrome',
54+
executablePath: '/tmp/test 123/chrome',
55+
});
56+
});
57+
});

0 commit comments

Comments
 (0)