diff --git a/README.md b/README.md index 5a68ff7e9..9d18f1ab5 100644 --- a/README.md +++ b/README.md @@ -591,6 +591,10 @@ The Chrome DevTools MCP server supports the following configuration option: Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH. - **Type:** boolean +- **`--experimentalFfmpegPath`/ `--experimental-ffmpeg-path`** + Path to ffmpeg executable for screencast recording. + - **Type:** string + - **`--experimentalWebmcp`/ `--experimental-webmcp`** Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCPTesting,DevToolsWebMCPSupport` - **Type:** boolean diff --git a/src/bin/chrome-devtools-mcp-cli-options.ts b/src/bin/chrome-devtools-mcp-cli-options.ts index 74ae8e130..513d83ab6 100644 --- a/src/bin/chrome-devtools-mcp-cli-options.ts +++ b/src/bin/chrome-devtools-mcp-cli-options.ts @@ -190,6 +190,11 @@ export const cliOptions = { 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.', }, + experimentalFfmpegPath: { + type: 'string', + describe: 'Path to ffmpeg executable for screencast recording.', + implies: 'experimentalScreencast', + }, experimentalWebmcp: { type: 'boolean', describe: diff --git a/src/telemetry/flag_usage_metrics.json b/src/telemetry/flag_usage_metrics.json index 98ea0642f..3a0989999 100644 --- a/src/telemetry/flag_usage_metrics.json +++ b/src/telemetry/flag_usage_metrics.json @@ -106,6 +106,10 @@ "name": "experimental_devtools_present", "flagType": "boolean" }, + { + "name": "experimental_ffmpeg_path_present", + "flagType": "boolean" + }, { "name": "experimental_include_all_pages", "flagType": "boolean" diff --git a/src/tools/screencast.ts b/src/tools/screencast.ts index 9f6f6a5f8..0523556a1 100644 --- a/src/tools/screencast.ts +++ b/src/tools/screencast.ts @@ -20,7 +20,7 @@ async function generateTempFilePath(): Promise { return path.join(dir, `screencast.mp4`); } -export const startScreencast = definePageTool({ +export const startScreencast = definePageTool(args => ({ name: 'screencast_start', description: 'Starts recording a screencast (video) of the selected page in mp4 format.', @@ -56,6 +56,7 @@ export const startScreencast = definePageTool({ recorder = await page.pptrPage.screencast({ path: resolvedPath as `${string}.mp4`, format: 'mp4' as const, + ffmpegPath: args?.experimentalFfmpegPath, }); } catch (err) { const message = err instanceof Error ? err.message : String(err); @@ -74,7 +75,7 @@ export const startScreencast = definePageTool({ `Screencast recording started. The recording will be saved to ${resolvedPath}. Use ${stopScreencast.name} to stop recording.`, ); }, -}); +})); export const stopScreencast = definePageTool({ name: 'screencast_stop', diff --git a/tests/tools/screencast.test.ts b/tests/tools/screencast.test.ts index b6e7fd959..b9250889e 100644 --- a/tests/tools/screencast.test.ts +++ b/tests/tools/screencast.test.ts @@ -9,6 +9,7 @@ import {describe, it, afterEach} from 'node:test'; import sinon from 'sinon'; +import type {ParsedArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js'; import {startScreencast, stopScreencast} from '../../src/tools/screencast.js'; import {withMcpContext} from '../utils.js'; @@ -32,7 +33,7 @@ describe('screencast', () => { .stub(selectedPage, 'screencast') .resolves(mockRecorder as never); - await startScreencast.handler( + await startScreencast().handler( { params: {path: '/tmp/test-recording.mp4'}, page: context.getSelectedMcpPage(), @@ -63,7 +64,7 @@ describe('screencast', () => { .stub(selectedPage, 'screencast') .resolves(mockRecorder as never); - await startScreencast.handler( + await startScreencast().handler( {params: {}, page: context.getSelectedMcpPage()}, response, context, @@ -88,7 +89,7 @@ describe('screencast', () => { const selectedPage = context.getSelectedPptrPage(); const screencastStub = sinon.stub(selectedPage, 'screencast'); - await startScreencast.handler( + await startScreencast().handler( {params: {}, page: context.getSelectedMcpPage()}, response, context, @@ -110,7 +111,7 @@ describe('screencast', () => { sinon.stub(selectedPage, 'screencast').rejects(error); await assert.rejects( - startScreencast.handler( + startScreencast().handler( { params: {path: '/tmp/test.mp4'}, page: context.getSelectedMcpPage(), @@ -124,6 +125,29 @@ describe('screencast', () => { assert.strictEqual(context.getScreenRecorder(), null); }); }); + + it('passes ffmpegPath from args to puppeteer', async () => { + await withMcpContext(async (response, context) => { + const mockRecorder = createMockRecorder(); + const selectedPage = context.getSelectedPptrPage(); + const screencastStub = sinon + .stub(selectedPage, 'screencast') + .resolves(mockRecorder as never); + + const experimentalFfmpegPath = '/custom/path/to/ffmpeg'; + await startScreencast({ + experimentalFfmpegPath, + } as ParsedArguments).handler( + {params: {}, page: context.getSelectedMcpPage()}, + response, + context, + ); + + sinon.assert.calledOnce(screencastStub); + const callArgs = screencastStub.firstCall.args[0]; + assert.strictEqual(callArgs?.ffmpegPath, experimentalFfmpegPath); + }); + }); }); describe('screencast_stop', () => {