diff --git a/README.md b/README.md index 07f598be..afd7cdd8 100644 --- a/README.md +++ b/README.md @@ -383,6 +383,12 @@ The Chrome DevTools MCP server supports the following configuration option: - **Type:** boolean - **Default:** `true` +- **`--screenshot-format`** + Default image format for screenshots. Options: png, jpeg, webp. Default is png. + - **Type:** string + - **Choices:** `png`, `jpeg`, `webp` + - **Default:** `png` + Pass them via the `args` property in the JSON configuration. For example: @@ -424,7 +430,7 @@ You can connect directly to a Chrome WebSocket endpoint and include custom heade To get the WebSocket endpoint from a running Chrome instance, visit `http://127.0.0.1:9222/json/version` and look for the `webSocketDebuggerUrl` field. -You can also run `npx chrome-devtools-mcp@latest --help` to see all available configuration options. +You can run `npx chrome-devtools-mcp@latest --help` to see all available configuration options. ## Concepts diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 7fc62eac..d03509e9 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -38,3 +38,12 @@ ssh -N -L 127.0.0.1:9222:127.0.0.1:9222 @ Point the MCP connection inside the VM to `http://127.0.0.1:9222` and DevTools will reach the host browser without triggering the Host validation. + +### Screenshots not displaying correctly in Claude Code + +If you experience issues with screenshots not displaying correctly in Claude Code, you can work around this by: + +1. Setting the default format to JPEG in your configuration (recommended): Add `--screenshot-format=jpeg` to your MCP server args +2. Explicitly passing `jpeg` as the format parameter in all `take_screenshot()` calls: `take_screenshot(format="jpeg")` + +This workaround is needed until the issue is resolved on Claude Code's side. diff --git a/src/McpContext.ts b/src/McpContext.ts index 0bdf0663..7c30e7b3 100644 --- a/src/McpContext.ts +++ b/src/McpContext.ts @@ -53,6 +53,8 @@ interface McpContextOptions { experimentalDevToolsDebugging: boolean; // Whether all page-like targets are exposed as pages. experimentalIncludeAllPages?: boolean; + // Default screenshot format. + screenshotFormat?: 'png' | 'jpeg' | 'webp'; } const DEFAULT_TIMEOUT = 5_000; @@ -296,6 +298,10 @@ export class McpContext implements Context { this.#dialog = undefined; } + getDefaultScreenshotFormat(): 'png' | 'jpeg' | 'webp' { + return this.#options.screenshotFormat ?? 'png'; + } + getSelectedPage(): Page { const page = this.#selectedPage; if (!page) { diff --git a/src/cli.ts b/src/cli.ts index 5ce8673e..4f9543ec 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -160,6 +160,13 @@ export const cliOptions = { default: true, describe: 'Set to false to exclude tools related to network.', }, + screenshotFormat: { + type: 'string', + describe: + 'Default image format for screenshots taken by take_screenshot when no format parameter is provided. Options: png, jpeg, webp. Default is png.', + choices: ['png', 'jpeg', 'webp'] as const, + default: 'png', + }, } satisfies Record; export function parseArguments(version: string, argv = process.argv) { @@ -212,6 +219,10 @@ export function parseArguments(version: string, argv = process.argv) { 'Disable tools in the performance category', ], ['$0 --no-category-network', 'Disable tools in the network category'], + [ + '$0 --screenshot-format jpeg', + 'Set default screenshot format to JPEG (overrides PNG default)', + ], ]); return yargsInstance diff --git a/src/main.ts b/src/main.ts index 153b6f5d..77662726 100644 --- a/src/main.ts +++ b/src/main.ts @@ -86,6 +86,7 @@ async function getContext(): Promise { context = await McpContext.from(browser, logger, { experimentalDevToolsDebugging: devtools, experimentalIncludeAllPages: args.experimentalIncludeAllPages, + screenshotFormat: args.screenshotFormat as 'png' | 'jpeg' | 'webp', }); } return context; diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index 8acebb56..ad44a3ad 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -120,6 +120,10 @@ export type Context = Readonly<{ * Returns a reqid for a cdpRequestId. */ resolveCdpElementId(cdpBackendNodeId: number): string | undefined; + /** + * Returns the configured default screenshot format. + */ + getDefaultScreenshotFormat(): 'png' | 'jpeg' | 'webp'; }>; export function defineTool( diff --git a/src/tools/screenshot.ts b/src/tools/screenshot.ts index 4312c02a..c1c2247a 100644 --- a/src/tools/screenshot.ts +++ b/src/tools/screenshot.ts @@ -21,8 +21,10 @@ export const screenshot = defineTool({ schema: { format: zod .enum(['png', 'jpeg', 'webp']) - .default('png') - .describe('Type of format to save the screenshot as. Default is "png"'), + .optional() + .describe( + 'Type of format to save the screenshot as. If not specified, uses the configured default format.', + ), quality: zod .number() .min(0) @@ -62,13 +64,14 @@ export const screenshot = defineTool({ pageOrHandle = context.getSelectedPage(); } - const format = request.params.format; - const quality = format === 'png' ? undefined : request.params.quality; + // Use configured default format if not specified in request + const format = + request.params.format ?? context.getDefaultScreenshotFormat(); const screenshot = await pageOrHandle.screenshot({ type: format, fullPage: request.params.fullPage, - quality, + quality: request.params.quality, optimizeForSpeed: true, // Bonus: optimize encoding for speed }); @@ -92,12 +95,12 @@ export const screenshot = defineTool({ } else if (screenshot.length >= 2_000_000) { const {filename} = await context.saveTemporaryFile( screenshot, - `image/${request.params.format}`, + `image/${format}`, ); response.appendResponseLine(`Saved screenshot to ${filename}.`); } else { response.attachImage({ - mimeType: `image/${request.params.format}`, + mimeType: `image/${format}`, data: Buffer.from(screenshot).toString('base64'), }); } diff --git a/tests/cli.test.ts b/tests/cli.test.ts index 19502e28..01837eff 100644 --- a/tests/cli.test.ts +++ b/tests/cli.test.ts @@ -16,6 +16,8 @@ describe('cli args parsing', () => { categoryPerformance: true, 'category-network': true, categoryNetwork: true, + 'screenshot-format': 'png', + screenshotFormat: 'png', }; it('parses with default args', async () => {