Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ The Chrome DevTools MCP server supports the following configuration option:
Specify a different Chrome channel that should be used. The default is the stable channel version.
- **Type:** string
- **Choices:** `canary`, `dev`, `beta`, `stable`
- **Default:** `stable`

- **`--proxyServer`/ `--proxy-server`**
Proxy server configuration for Chrome passed as --proxy-server when launching the browser. See https://www.chromium.org/developers/design-documents/network-settings/ for details.
Expand Down Expand Up @@ -199,6 +200,7 @@ The Chrome DevTools MCP server supports the following configuration option:
Override the default output format used by take_screenshot when the caller does not specify one. JPEG and WebP are ~3-5x smaller than PNG, which reduces transfer and storage size. To reduce context size use --screenshotMaxWidth / --screenshotMaxHeight, since image tokens scale with dimensions rather than encoded bytes. Unset preserves the existing default ("png").
- **Type:** string
- **Choices:** `jpeg`, `png`, `webp`
- **Default:** `png`

- **`--screenshotQuality`/ `--screenshot-quality`**
Override the default compression quality (0-100) used by take_screenshot for JPEG and WebP when the caller does not specify one. Lower values mean smaller files. Ignored for PNG. Unset preserves the Puppeteer default.
Expand Down
14 changes: 5 additions & 9 deletions src/ToolHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import type {ParsedArguments} from './config/mcp-options.js';
import type {McpContext} from './McpContext.js';
import type {McpPage} from './McpPage.js';
import type {DataFormat} from './McpResponse.js';
import {McpResponse} from './McpResponse.js';
import {SlimMcpResponse} from './SlimMcpResponse.js';
import {ClearcutLogger} from './telemetry/ClearcutLogger.js';
Expand Down Expand Up @@ -243,14 +242,11 @@ export class ToolHandler {
}
devToolsData = await context.getDevToolsData(page);
pageUrl = context.getSelectedMcpPageUrl(page);
// Resolve data format: --experimentalDataFormat takes precedence, fall back to legacy --experimentalToonFormat
let dataFormat: DataFormat = 'default';
if (this.serverArgs.experimentalDataFormat) {
dataFormat = this.serverArgs.experimentalDataFormat as DataFormat;
} else if (this.serverArgs.experimentalToonFormat) {
dataFormat = 'toon';
}

// --experimentalDataFormat takes precedence over the legacy
// --experimentalToonFormat.
const dataFormat =
this.serverArgs.experimentalDataFormat ??
(this.serverArgs.experimentalToonFormat ? 'toon' : 'default');
const {content, structuredContent} = await response.handle(
context,
dataFormat,
Expand Down
14 changes: 2 additions & 12 deletions src/config/browser-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,13 @@ export const browserOptions = {
type: 'boolean',
description:
'If specified, automatically connects to a browser (Chrome 144+) running locally from the user data directory identified by the channel param (default channel is stable). Requires the remote debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging.',
conflicts: ['isolated', 'executablePath'],
default: false,
coerce: (value: boolean | undefined) => {
if (!value) {
return;
}
return value;
},
},
browserUrl: {
type: 'string',
description:
'Connect to a running, debuggable Chrome instance (e.g. `http://127.0.0.1:9222`). For more details see: https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/docs/advanced-usage.md#connecting-to-a-running-chrome-instance.',
alias: 'u',
conflicts: ['wsEndpoint'],
coerce: (url: string | undefined) => {
if (!url) {
return;
Expand All @@ -43,7 +35,6 @@ export const browserOptions = {
description:
'WebSocket endpoint to connect to a running Chrome instance (e.g., ws://127.0.0.1:9222/devtools/browser/<id>). Alternative to --browserUrl.',
alias: 'w',
conflicts: ['browserUrl'],
coerce: (url: string | undefined) => {
if (!url) {
return;
Expand Down Expand Up @@ -94,26 +85,25 @@ export const browserOptions = {
executablePath: {
type: 'string',
description: 'Path to custom Chrome executable.',
conflicts: ['browserUrl', 'wsEndpoint'],
alias: 'e',
},
isolated: {
type: 'boolean',
description:
'If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed. Defaults to false.',
defaultDescription: 'false',
},
userDataDir: {
type: 'string',
description:
'Path to the user data directory for Chrome. Default is $HOME/.cache/chrome-devtools-mcp/chrome-profile$CHANNEL_SUFFIX_IF_NON_STABLE',
conflicts: ['browserUrl', 'wsEndpoint', 'isolated'],
},
channel: {
type: 'string',
description:
'Specify a different Chrome channel that should be used. The default is the stable channel version.',
choices: ['canary', 'dev', 'beta', 'stable'] as const,
conflicts: ['browserUrl', 'wsEndpoint', 'executablePath'],
defaultDescription: 'stable',
},
proxyServer: {
type: 'string',
Expand Down
9 changes: 5 additions & 4 deletions src/config/category-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export interface CategoryOption {
type: 'boolean';
describe: string;
default?: boolean;
defaultDescription?: string;
hidden?: boolean;
conflicts?: string[];
}

export type CategoryFlagName<T extends ToolCategory = ToolCategory> =
Expand All @@ -25,8 +25,7 @@ const categoryOverrides: Record<
ToolCategory,
{
describe?: string;
hidden?: true;
conflicts?: string[];
hidden?: boolean;
offByDefault?: boolean;
}
> = {
Expand Down Expand Up @@ -55,7 +54,6 @@ const categoryOverrides: Record<
[ToolCategory.PWA]: {
describe:
'Set to true to include tools for automating Progressive Web Apps (install, launch, uninstall, and OS state). This feature is only supported with a pipe connection; autoConnect, browserUrl, and wsEndpoint are not supported.',
conflicts: ['autoConnect', 'browserUrl', 'wsEndpoint'],
offByDefault: true,
},
};
Expand All @@ -70,6 +68,9 @@ function createOption(category: ToolCategory): CategoryOption {
type: 'boolean',
describe,
...overrides,
// Off-by-default categories have no default so that they stay unset unless
// passed explicitly. This keeps them out of conflict checks and lets
// --viaCli apply its own categoryExtensions default.
...(overrides.offByDefault ? {} : {default: true}),
};
}
Expand Down
75 changes: 64 additions & 11 deletions src/config/mcp-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const mcpOptions = {
},
acceptInsecureCerts: {
type: 'boolean',
default: false,
description: `If enabled, ignores errors relative to self-signed and expired certificates. Use with caution.`,
},
pageIdRouting: {
Expand All @@ -62,10 +63,12 @@ export const mcpOptions = {
},
experimentalDevtools: {
type: 'boolean',
default: false,
describe: 'Whether to enable automation over DevTools targets',
},
experimentalVision: {
type: 'boolean',
default: false,
describe:
'Whether to enable coordinate-based tools such as click_at(x,y). Usually requires a computer-use model able to produce accurate coordinates by looking at screenshots.',
},
Expand All @@ -82,29 +85,34 @@ export const mcpOptions = {
},
experimentalToonFormat: {
type: 'boolean',
default: false,
describe:
'Deprecated: use --experimentalDataFormat=toon instead. Whether to format structured data using TOON (requires @toon-format/toon).',
hidden: true,
},
experimentalDataFormat: {
type: 'string',
defaultDescription: 'default',
describe:
'Override format for structured data in text responses. Default uses built-in formatters. "toon" (requires @toon-format/toon) or "gcf" (requires @blackwell-systems/gcf) replace structured content with the specified encoding.',
choices: ['default', 'toon', 'gcf'] as const,
hidden: true,
},
experimentalIncludeAllPages: {
type: 'boolean',
default: false,
describe:
'Whether to include all kinds of pages such as webviews or background pages as pages.',
},
experimentalInteropTools: {
type: 'boolean',
default: false,
describe: 'Whether to enable interoperability tools',
hidden: true,
},
experimentalScreencast: {
type: 'boolean',
default: false,
describe:
'Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.',
},
Expand Down Expand Up @@ -135,7 +143,6 @@ export const mcpOptions = {
string: true,
describe:
"Restricts browser's network access by blocking specified URL patterns (uses https://urlpattern.spec.whatwg.org/). Silently detaches from targets with blocked URLs upon connection, and blocks runtime requests (including navigations and subresources). Accepts an array of patterns. A pattern that uses a regexp group in any component (for example `(127\\.\\d+\\.\\d+\\.\\d+)` in the hostname) is rejected, because it is not enforced on redirects or subresources; use an exact value or a `*`/`:name` wildcard instead.",
conflicts: ['allowedUrlPattern'],
coerce: (arg: string[] | undefined) => {
if (arg === undefined) {
return undefined;
Expand All @@ -154,7 +161,6 @@ export const mcpOptions = {
string: true,
describe:
"Restricts browser's network access by allowing only specified URL patterns (uses https://urlpattern.spec.whatwg.org/). Requires Chrome 149+. Silently detaches from targets with unallowed URLs upon connection, and blocks runtime requests (including navigations and subresources). Accepts an array of patterns. A pattern that uses a regexp group in any component (for example `(127\\.\\d+\\.\\d+\\.\\d+)` in the hostname) is rejected, because it is not enforced on redirects or subresources; use an exact value or a `*`/`:name` wildcard instead.",
conflicts: ['blockedUrlPattern'],
coerce: (arg: string[] | undefined) => {
if (arg === undefined) {
return undefined;
Expand Down Expand Up @@ -204,11 +210,13 @@ export const mcpOptions = {
},
clearcutIncludePidHeader: {
type: 'boolean',
default: false,
hidden: true,
describe: 'Include watchdog PID in Clearcut request headers (for testing).',
},
screenshotFormat: {
type: 'string',
default: 'png' as const,
description:
'Override the default output format used by take_screenshot when the caller does not specify one. JPEG and WebP are ~3-5x smaller than PNG, which reduces transfer and storage size. To reduce context size use --screenshotMaxWidth / --screenshotMaxHeight, since image tokens scale with dimensions rather than encoded bytes. Unset preserves the existing default ("png").',
choices: ['jpeg', 'png', 'webp'] as const,
Expand Down Expand Up @@ -263,11 +271,13 @@ export const mcpOptions = {
},
slim: {
type: 'boolean',
default: false,
describe:
'Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.',
},
viaCli: {
type: 'boolean',
default: false,
describe:
'Set by Chrome DevTools CLI if the MCP server is started via the CLI client (this arg exists for usage stats)',
hidden: true,
Expand Down Expand Up @@ -320,9 +330,6 @@ export function getMcpOptionsForViaCli(): typeof mcpOptions {
'experimentalStructuredContent cli option unexpectedly does not have a default',
);
}
if ('default' in mcpOptions.isolated) {
throw new Error('isolated cli option unexpectedly has a default');
}

return {
...mcpOptions,
Expand All @@ -336,7 +343,8 @@ export function getMcpOptionsForViaCli(): typeof mcpOptions {
},
categoryExtensions: {
...mcpOptions.categoryExtensions,
default: true,
defaultDescription:
'true unless autoConnect, browserUrl or wsEndpoint is set',
},
experimentalStructuredContent: {
...mcpOptions.experimentalStructuredContent,
Expand All @@ -346,6 +354,8 @@ export function getMcpOptionsForViaCli(): typeof mcpOptions {
...mcpOptions.isolated,
description:
'If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed. Defaults to true unless userDataDir is provided.',
defaultDescription:
'true unless userDataDir, autoConnect, browserUrl or wsEndpoint is set',
},
};
}
Expand Down Expand Up @@ -399,6 +409,42 @@ export function parser(
})
.options(options)
.showHelpOnFail(false, 'Specify --help for available options')
.check(args => {
const activeArgs = new Set<string>();

for (const [key, val] of Object.entries(args)) {
if (val !== undefined && val !== false) {
activeArgs.add(key);
}
}

const CONFLICTS: Array<Array<keyof typeof mcpOptions>> = [
['channel', 'executablePath', 'browserUrl', 'wsEndpoint'],
['userDataDir', 'browserUrl', 'wsEndpoint'],
['userDataDir', 'isolated'],
['autoConnect', 'isolated'],
['autoConnect', 'executablePath'],
['blockedUrlPattern', 'allowedUrlPattern'],
['categoryPwa', 'autoConnect'],
['categoryPwa', 'browserUrl', 'wsEndpoint'],
['categoryExtensions', 'autoConnect'],
['categoryExtensions', 'browserUrl', 'wsEndpoint'],
];

for (const group of CONFLICTS) {
// Find all active arguments within this conflict group
const activeInGroup = group.filter(arg => activeArgs.has(arg));

if (activeInGroup.length > 1) {
const [arg1, arg2] = activeInGroup;
throw new Error(
`Arguments ${arg1} and ${arg2} are mutually exclusive`,
);
}
}

return true;
})
.middleware(args => {
if (isViaCli) {
if (args.filesystemRoot === DEFAULT_FILESYSTEM_ROOT) {
Expand All @@ -410,18 +456,25 @@ export function parser(
cliFilesystemArgs.filesystemRoot = undefined;
}
// Defaults that cannot be set in options without affecting yargs conflict resolution.
const connectsToExistingBrowser =
args.autoConnect || args.browserUrl || args.wsEndpoint;
if (
args.isolated === undefined &&
args.userDataDir === undefined &&
!args.autoConnect &&
!args.browserUrl &&
!args.wsEndpoint
!connectsToExistingBrowser
) {
args.isolated = true;
}
if (
args.categoryExtensions === undefined &&
!connectsToExistingBrowser
) {
args.categoryExtensions = true;
}
}
// We can't set default in the options else
// Yargs will complain
// Only fall back to stable when Chrome is launched by channel. Leaving it
// unset otherwise keeps it out of telemetry (computeFlagUsage) for
// browserUrl, wsEndpoint and executablePath.
if (
!args.channel &&
!args.browserUrl &&
Expand Down
Loading
Loading