diff --git a/docs/cli.md b/docs/cli.md index f9c009b16..46b2239d0 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -17,7 +17,7 @@ The CLI acts as a client to a background `chrome-devtools-mcp` daemon (uses Unix - **Automatic Start**: The first time you call a tool (e.g., `list_pages`), the CLI automatically starts the MCP server and the browser in the background if they aren't already running. - **Persistence**: The same background instance is reused for subsequent commands, preserving the browser state (open pages, cookies, etc.). -- **Manual Control**: You can explicitly manage the background process using `start`, `stop`, and `status`. The `start` command forwards all subsequent arguments to the underlying MCP server (e.g., `--headless`, `--userDataDir`). +- **Manual Control**: You can explicitly manage the background process using `start`, `stop`, and `status`. The `start` command forwards all subsequent arguments to the underlying MCP server (e.g., `--headless`, `--userDataDir`) but not all args are supported. Run `chrome-devtools start --help` for supported args. Headless and isolated are enabled by default. ```sh # Check if the daemon is running diff --git a/src/bin/chrome-devtools.ts b/src/bin/chrome-devtools.ts index d8dfbdf1e..17e9c99aa 100644 --- a/src/bin/chrome-devtools.ts +++ b/src/bin/chrome-devtools.ts @@ -32,6 +32,35 @@ async function start(args: string[]) { const defaultArgs = ['--viaCli', '--experimentalStructuredContent']; +const startCliOptions = { + ...cliOptions, +} as Partial; + +// Not supported in CLI on purpose. +delete startCliOptions.autoConnect; +// Missing CLI serialization. +delete startCliOptions.viewport; +// CLI is generated based on the default tool definitions. To enable conditional +// tools, they needs to be enabled during CLI generation. +delete startCliOptions.experimentalPageIdRouting; +delete startCliOptions.experimentalVision; +delete startCliOptions.experimentalInteropTools; +delete startCliOptions.experimentalScreencast; +delete startCliOptions.categoryEmulation; +delete startCliOptions.categoryPerformance; +delete startCliOptions.categoryNetwork; +delete startCliOptions.categoryExtensions; +// Always on in CLI. +delete startCliOptions.experimentalStructuredContent; +// Change the defaults. +if (!('default' in cliOptions.headless)) { + throw new Error('headless cli option unexpectedly does not have a default'); +} +if ('default' in cliOptions.isolated) { + throw new Error('headless cli option unexpectedly does not have a default'); +} +startCliOptions.headless!.default = true; + const y = yargs(hideBin(process.argv)) .scriptName('chrome-devtools') .showHelpOnFail(true) @@ -50,7 +79,7 @@ y.command( 'Start or restart chrome-devtools-mcp', y => y - .options(cliOptions) + .options(startCliOptions) .example( '$0 start --browserUrl http://localhost:9222', 'Start the server connecting to an existing browser', @@ -60,6 +89,13 @@ y.command( if (isDaemonRunning()) { await stopDaemon(); } + // Defaults but we do not want to affect the yargs conflict resolution. + if (argv.isolated === undefined) { + argv.isolated = true; + } + if (argv.headless === undefined) { + argv.headless = true; + } const args = serializeArgs(cliOptions, argv); await start(args); process.exit(0); diff --git a/src/browser.ts b/src/browser.ts index 1b8cc1645..899a19a68 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -149,6 +149,7 @@ interface McpLaunchOptions { ignoreDefaultChromeArgs?: string[]; devtools: boolean; enableExtensions?: boolean; + viaCli?: boolean; } export function detectDisplay(): void { @@ -181,7 +182,7 @@ export async function launch(options: McpLaunchOptions): Promise { userDataDir = path.join( os.homedir(), '.cache', - 'chrome-devtools-mcp', + options.viaCli ? 'chrome-devtools-mcp-cli' : 'chrome-devtools-mcp', profileDirName, ); await fs.promises.mkdir(userDataDir, { diff --git a/src/daemon/client.ts b/src/daemon/client.ts index 4bb87824e..0e231fe49 100644 --- a/src/daemon/client.ts +++ b/src/daemon/client.ts @@ -79,7 +79,7 @@ export async function startDaemon(mcpArgs: string[] = []) { fs.unlinkSync(pidFilePath); } - logger('Starting daemon...'); + logger('Starting daemon...', ...mcpArgs); const child = spawn(process.execPath, [DAEMON_SCRIPT_PATH, ...mcpArgs], { detached: true, stdio: 'ignore', diff --git a/src/daemon/utils.ts b/src/daemon/utils.ts index db414def0..1b73a1549 100644 --- a/src/daemon/utils.ts +++ b/src/daemon/utils.ts @@ -9,7 +9,6 @@ import os from 'node:os'; import path from 'node:path'; import process from 'node:process'; -import type {ParsedArguments} from '../cli.js'; import {logger} from '../logger.js'; import type {YargsOptions} from '../third_party/index.js'; @@ -102,7 +101,7 @@ export function isDaemonRunning(pid = getDaemonPid()): pid is number { export function serializeArgs( options: Record, - argv: ParsedArguments, + argv: Record, ): string[] { const args: string[] = []; for (const key of Object.keys(options)) { diff --git a/src/server.ts b/src/server.ts index 91690a57a..991c2d292 100644 --- a/src/server.ts +++ b/src/server.ts @@ -93,6 +93,7 @@ export async function createMcpServer( acceptInsecureCerts: serverArgs.acceptInsecureCerts, devtools, enableExtensions: serverArgs.categoryExtensions, + viaCli: serverArgs.viaCli, }); if (context?.browser !== browser) { diff --git a/tests/e2e/chrome-devtools.test.ts b/tests/e2e/chrome-devtools.test.ts index 18fc2fe09..c0dd619fd 100644 --- a/tests/e2e/chrome-devtools.test.ts +++ b/tests/e2e/chrome-devtools.test.ts @@ -12,8 +12,6 @@ import {describe, it, afterEach, beforeEach} from 'node:test'; const CLI_PATH = path.resolve('build/src/bin/chrome-devtools.js'); describe('chrome-devtools', () => { - const START_ARGS = ['--headless', '--isolated']; - function assertDaemonIsNotRunning() { const result = spawnSync('node', [CLI_PATH, 'status']); assert.strictEqual( @@ -45,7 +43,7 @@ describe('chrome-devtools', () => { it('reports daemon status correctly', () => { assertDaemonIsNotRunning(); - const startResult = spawnSync('node', [CLI_PATH, 'start', ...START_ARGS]); + const startResult = spawnSync('node', [CLI_PATH, 'start']); assert.strictEqual( startResult.status, 0, @@ -58,7 +56,7 @@ describe('chrome-devtools', () => { it('can start and stop the daemon', () => { assertDaemonIsNotRunning(); - const startResult = spawnSync('node', [CLI_PATH, 'start', ...START_ARGS]); + const startResult = spawnSync('node', [CLI_PATH, 'start']); assert.strictEqual( startResult.status, 0, @@ -80,7 +78,7 @@ describe('chrome-devtools', () => { it('can invoke list_pages', async () => { assertDaemonIsNotRunning(); - const startResult = spawnSync('node', [CLI_PATH, 'start', ...START_ARGS]); + const startResult = spawnSync('node', [CLI_PATH, 'start']); assert.strictEqual( startResult.status, 0, @@ -102,7 +100,7 @@ describe('chrome-devtools', () => { }); it('can take screenshot', async () => { - const startResult = spawnSync('node', [CLI_PATH, 'start', ...START_ARGS]); + const startResult = spawnSync('node', [CLI_PATH, 'start']); assert.strictEqual( startResult.status, 0, @@ -122,7 +120,7 @@ describe('chrome-devtools', () => { }); it('forwards disclaimers to stderr on start', () => { - const result = spawnSync('node', [CLI_PATH, 'start', ...START_ARGS]); + const result = spawnSync('node', [CLI_PATH, 'start']); assert.strictEqual( result.status, 0,