豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit dc53fa3

Browse files
authored
chore: validate start args (#1113)
The `start` command accepts the MCP server CLI options. This PR adds yargs validation to the start command and re-uses the same yargs definitions.
1 parent 5538180 commit dc53fa3

File tree

3 files changed

+125
-11
lines changed

3 files changed

+125
-11
lines changed

src/bin/chrome-devtools.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import process from 'node:process';
1010

1111
import type {Options, PositionalOptions} from 'yargs';
1212

13-
import {parseArguments} from '../cli.js';
13+
import {cliOptions, parseArguments} from '../cli.js';
1414
import {
1515
startDaemon,
1616
stopDaemon,
1717
sendCommand,
1818
handleResponse,
1919
} from '../daemon/client.js';
20-
import {isDaemonRunning} from '../daemon/utils.js';
20+
import {isDaemonRunning, serializeArgs} from '../daemon/utils.js';
2121
import {logDisclaimers} from '../server.js';
2222
import {hideBin, yargs, type CallToolResult} from '../third_party/index.js';
2323
import {VERSION} from '../version.js';
@@ -26,7 +26,7 @@ import {commands} from './cliDefinitions.js';
2626

2727
async function start(args: string[]) {
2828
const combinedArgs = [...args, ...defaultArgs];
29-
await startDaemon([...args, ...defaultArgs]);
29+
await startDaemon(combinedArgs);
3030
logDisclaimers(parseArguments(VERSION, combinedArgs));
3131
}
3232

@@ -50,19 +50,17 @@ y.command(
5050
'Start or restart chrome-devtools-mcp',
5151
y =>
5252
y
53-
.help(false) // Disable help for start command to avoid parsing issues with passed args.
53+
.options(cliOptions)
5454
.example(
55-
'$0 start --port 8080 --url http://localhost:8080',
56-
'Start the server on port 8080 with a specific URL',
55+
'$0 start --browserUrl http://localhost:9222',
56+
'Start the server connecting to an existing browser',
5757
)
58-
.strict(false), // Don't validate arguments for start, as they are passed through to the daemon.
59-
async () => {
58+
.strict(),
59+
async argv => {
6060
if (isDaemonRunning()) {
6161
await stopDaemon();
6262
}
63-
// Extract args after 'start'
64-
const startIndex = process.argv.indexOf('start');
65-
const args = startIndex !== -1 ? process.argv.slice(startIndex + 1) : [];
63+
const args = serializeArgs(cliOptions, argv);
6664
await start(args);
6765
process.exit(0);
6866
},

src/daemon/utils.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import os from 'node:os';
99
import path from 'node:path';
1010
import process from 'node:process';
1111

12+
import type {ParsedArguments} from '../cli.js';
1213
import {logger} from '../logger.js';
14+
import type {YargsOptions} from '../third_party/index.js';
1315

1416
export const DAEMON_SCRIPT_PATH = path.join(import.meta.dirname, 'daemon.js');
1517
export const INDEX_SCRIPT_PATH = path.join(
@@ -97,3 +99,32 @@ export function isDaemonRunning(pid = getDaemonPid()): pid is number {
9799
}
98100
return false;
99101
}
102+
103+
export function serializeArgs(
104+
options: Record<string, YargsOptions>,
105+
argv: ParsedArguments,
106+
): string[] {
107+
const args: string[] = [];
108+
for (const key of Object.keys(options)) {
109+
if (argv[key] === undefined || argv[key] === null) {
110+
continue;
111+
}
112+
const value = argv[key];
113+
const kebabKey = key.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
114+
115+
if (typeof value === 'boolean') {
116+
if (value) {
117+
args.push(`--${kebabKey}`);
118+
} else {
119+
args.push(`--no-${kebabKey}`);
120+
}
121+
} else if (Array.isArray(value)) {
122+
for (const item of value) {
123+
args.push(`--${kebabKey}`, String(item));
124+
}
125+
} else {
126+
args.push(`--${kebabKey}`, String(value));
127+
}
128+
}
129+
return args;
130+
}

tests/daemon/utils.test.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import assert from 'node:assert';
8+
import {describe, it} from 'node:test';
9+
10+
import type {ParsedArguments} from '../../src/cli.js';
11+
import {serializeArgs} from '../../src/daemon/utils.js';
12+
import type {YargsOptions} from '../../src/third_party/index.js';
13+
14+
describe('serializeArgs', () => {
15+
it('should ignore undefined or null values', () => {
16+
const options: Record<string, YargsOptions> = {
17+
foo: {},
18+
bar: {},
19+
baz: {},
20+
};
21+
const argv = {
22+
foo: undefined,
23+
bar: null,
24+
baz: 'value',
25+
_: [],
26+
$0: 'test',
27+
} as unknown as ParsedArguments;
28+
const result = serializeArgs(options, argv);
29+
assert.deepStrictEqual(result, ['--baz', 'value']);
30+
});
31+
32+
it('should handle boolean values', () => {
33+
const options: Record<string, YargsOptions> = {foo: {}, bar: {}};
34+
const argv = {
35+
foo: true,
36+
bar: false,
37+
_: [],
38+
$0: 'test',
39+
} as unknown as ParsedArguments;
40+
const result = serializeArgs(options, argv);
41+
assert.deepStrictEqual(result, ['--foo', '--no-bar']);
42+
});
43+
44+
it('should handle array values', () => {
45+
const options: Record<string, YargsOptions> = {foo: {}};
46+
const argv = {
47+
foo: ['val1', 'val2'],
48+
_: [],
49+
$0: 'test',
50+
} as unknown as ParsedArguments;
51+
const result = serializeArgs(options, argv);
52+
assert.deepStrictEqual(result, ['--foo', 'val1', '--foo', 'val2']);
53+
});
54+
55+
it('should handle primitive values', () => {
56+
const options: Record<string, YargsOptions> = {foo: {}, bar: {}};
57+
const argv = {
58+
foo: 'string',
59+
bar: 42,
60+
_: [],
61+
$0: 'test',
62+
} as unknown as ParsedArguments;
63+
const result = serializeArgs(options, argv);
64+
assert.deepStrictEqual(result, ['--foo', 'string', '--bar', '42']);
65+
});
66+
67+
it('should convert camelCase keys to kebab-case', () => {
68+
const options: Record<string, YargsOptions> = {
69+
camelCaseKey: {},
70+
anotherKey: {},
71+
};
72+
const argv = {
73+
camelCaseKey: 'value1',
74+
anotherKey: true,
75+
_: [],
76+
$0: 'test',
77+
} as unknown as ParsedArguments;
78+
const result = serializeArgs(options, argv);
79+
assert.deepStrictEqual(result, [
80+
'--camel-case-key',
81+
'value1',
82+
'--another-key',
83+
]);
84+
});
85+
});

0 commit comments

Comments
 (0)