Skip to content
Open
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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,11 @@ The Chrome DevTools MCP server supports the following configuration option:
- **Type:** string
- **Default:** `false`

- **`--experimentalScreencastFps`/ `--experimental-screencast-fps`**
Frames per second to use for screencast recording. Lower values can reduce memory pressure on pages that produce frames faster than ffmpeg can encode them.
- **Type:** number
- **Default:** Uses Puppeteer's screencast default.

- **`--categoryExperimentalWebmcp`/ `--category-experimental-webmcp`**
Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCP,DevToolsWebMCPSupport`
- **Type:** boolean
Expand Down
17 changes: 17 additions & 0 deletions src/bin/chrome-devtools-mcp-cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,23 @@ export const cliOptions = {
describe: 'Path to ffmpeg executable for screencast recording.',
implies: 'experimentalScreencast',
},
experimentalScreencastFps: {
type: 'number',
describe:
'Frames per second to use for screencast recording. Lower values can reduce memory pressure on pages that produce frames faster than ffmpeg can encode them.',
implies: 'experimentalScreencast',
coerce: (value: number | undefined) => {
if (value === undefined) {
return;
}
if (!Number.isInteger(value) || value <= 0) {
throw new Error(
`Invalid experimentalScreencastFps ${value}. Expected a positive integer.`,
);
}
return value;
},
},
categoryExperimentalWebmcp: {
type: 'boolean',
describe:
Expand Down
4 changes: 4 additions & 0 deletions src/telemetry/flag_usage_metrics.json
Original file line number Diff line number Diff line change
Expand Up @@ -371,5 +371,9 @@
{
"name": "allow_unrestricted_paths",
"flagType": "boolean"
},
{
"name": "experimental_screencast_fps_present",
"flagType": "boolean"
}
]
1 change: 1 addition & 0 deletions src/tools/screencast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export const startScreencast = definePageTool(args => ({
path: resolvedPath,
format: format,
ffmpegPath: args?.experimentalFfmpegPath,
fps: args?.experimentalScreencastFps,
});
} catch (err) {
// If we generated a temporary directory for this recording, remove it so
Expand Down
20 changes: 19 additions & 1 deletion tests/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
import assert from 'node:assert';
import {describe, it} from 'node:test';

import {parseArguments} from '../src/bin/chrome-devtools-mcp-cli-options.js';
import {
cliOptions,
parseArguments,
} from '../src/bin/chrome-devtools-mcp-cli-options.js';

describe('cli args parsing', () => {
const defaultArgs = {
Expand Down Expand Up @@ -278,6 +281,21 @@ describe('cli args parsing', () => {
});
});

it('rejects invalid screencast fps values', async () => {
const coerce = cliOptions.experimentalScreencastFps.coerce;
assert.ok(coerce);

assert.strictEqual(coerce(undefined), undefined);
assert.strictEqual(coerce(10), 10);

for (const value of [0, -1, 10.5, Number.NaN]) {
assert.throws(
() => coerce(value),
/Invalid experimentalScreencastFps .* Expected a positive integer\./,
);
}
});

it('parses usage statistics flag', async () => {
// Test default (should be true).
const defaultArgs = parseArguments('1.0.0', ['node', 'main.js'], {});
Expand Down
38 changes: 34 additions & 4 deletions tests/tools/screencast.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,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 {parseArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js';
import {startScreencast, stopScreencast} from '../../src/tools/screencast.js';
import {withMcpContext} from '../utils.js';

Expand Down Expand Up @@ -209,9 +209,13 @@ describe('screencast', () => {
.resolves(mockRecorder as never);

const experimentalFfmpegPath = '/custom/path/to/ffmpeg';
await startScreencast({
experimentalFfmpegPath,
} as ParsedArguments).handler(
const args = parseArguments('test', [
'node',
'test',
'--experimental-screencast',
`--experimental-ffmpeg-path=${experimentalFfmpegPath}`,
]);
await startScreencast(args).handler(
{params: {}, page: context.getSelectedMcpPage()},
response,
context,
Expand All @@ -222,6 +226,32 @@ describe('screencast', () => {
assert.strictEqual(callArgs?.ffmpegPath, experimentalFfmpegPath);
});
});

it('passes screencast fps from args to puppeteer', async () => {
await withMcpContext(async (response, context) => {
const mockRecorder = createMockRecorder();
const selectedPage = context.getSelectedMcpPage().pptrPage;
const screencastStub = sinon
.stub(selectedPage, 'screencast')
.resolves(mockRecorder as never);

const args = parseArguments('test', [
'node',
'test',
'--experimental-screencast',
'--experimental-screencast-fps=10',
]);
await startScreencast(args).handler(
{params: {}, page: context.getSelectedMcpPage()},
response,
context,
);

sinon.assert.calledOnce(screencastStub);
const callArgs = screencastStub.firstCall.args[0];
assert.strictEqual(callArgs?.fps, 10);
});
});
});

describe('screencast_stop', () => {
Expand Down