Skip to content

Commit 03b59f0

Browse files
authored
fix: do not set channel if executablePath is provided (#150)
Fixes #148
1 parent b2e1e39 commit 03b59f0

File tree

4 files changed

+150
-81
lines changed

4 files changed

+150
-81
lines changed

scripts/generate-docs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {Client} from '@modelcontextprotocol/sdk/client/index.js';
1010
import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js';
1111
import type {Tool} from '@modelcontextprotocol/sdk/types.js';
1212

13-
import {cliOptions} from '../build/src/main.js';
13+
import {cliOptions} from '../build/src/cli.js';
1414
import {ToolCategories} from '../build/src/tools/categories.js';
1515

1616
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
@@ -12,11 +12,10 @@ import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
1212
import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
1313
import type {CallToolResult} from '@modelcontextprotocol/sdk/types.js';
1414
import {SetLevelRequestSchema} from '@modelcontextprotocol/sdk/types.js';
15-
import yargs from 'yargs';
16-
import {hideBin} from 'yargs/helpers';
1715

1816
import type {Channel} from './browser.js';
1917
import {resolveBrowser} from './browser.js';
18+
import {parseArguments} from './cli.js';
2019
import {logger, saveLogsToFile} from './logger.js';
2120
import {McpContext} from './McpContext.js';
2221
import {McpResponse} from './McpResponse.js';
@@ -32,55 +31,6 @@ import * as scriptTools from './tools/script.js';
3231
import * as snapshotTools from './tools/snapshot.js';
3332
import type {ToolDefinition} from './tools/ToolDefinition.js';
3433

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

9949
const version = readPackageJson().version ?? 'unknown';
10050

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

13153
const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;
13254

tests/cli.test.ts

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

0 commit comments

Comments
 (0)