Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,10 @@ The Chrome DevTools MCP server supports the following configuration option:
Custom headers for WebSocket connection in JSON format (e.g., '{"Authorization":"Bearer token"}'). Only works with --wsEndpoint.
- **Type:** string

- **`--protocolTimeout`/ `--protocol-timeout`**
Timeout in milliseconds for Chrome DevTools Protocol commands. Passed to Puppeteer as protocolTimeout.
- **Type:** number

- **`--headless`**
Whether to run in headless (no UI) mode.
- **Type:** boolean
Expand Down
11 changes: 9 additions & 2 deletions src/bin/chrome-devtools-cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,8 +710,15 @@ export const commands: Commands = {
filePath: {
name: 'filePath',
type: 'string',
description: 'The local path of the file to upload',
required: true,
description:
'The local path of a file to upload. Use filePaths for multiple files.',
required: false,
},
filePaths: {
name: 'filePaths',
type: 'array',
description: 'One or more local file paths to upload in a single call.',
required: false,
},
includeSnapshot: {
name: 'includeSnapshot',
Expand Down
14 changes: 14 additions & 0 deletions src/bin/chrome-devtools-mcp-cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,20 @@ export const cliOptions = {
}
},
},
protocolTimeout: {
type: 'number',
description:
'Timeout in milliseconds for Chrome DevTools Protocol commands. Passed to Puppeteer as protocolTimeout.',
coerce: (value: number | undefined) => {
if (value === undefined) {
return;
}
if (!Number.isFinite(value) || value <= 0) {
throw new Error('protocolTimeout must be a positive number of ms.');
}
return value;
},
},
headless: {
type: 'boolean',
description: 'Whether to run in headless (no UI) mode.',
Expand Down
11 changes: 9 additions & 2 deletions src/bin/cliDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -691,8 +691,15 @@ export const commands: Commands = {
filePath: {
name: 'filePath',
type: 'string',
description: 'The local path of the file to upload',
required: true,
description:
'The local path of a file to upload. Use filePaths for multiple files.',
required: false,
},
filePaths: {
name: 'filePaths',
type: 'array',
description: 'One or more local file paths to upload in a single call.',
required: false,
},
includeSnapshot: {
name: 'includeSnapshot',
Expand Down
4 changes: 4 additions & 0 deletions src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export async function ensureBrowserConnected(options: {
browserURL?: string;
wsEndpoint?: string;
wsHeaders?: Record<string, string>;
protocolTimeout?: number;
devtools: boolean;
channel?: Channel;
userDataDir?: string;
Expand All @@ -61,6 +62,7 @@ export async function ensureBrowserConnected(options: {
targetFilter: makeTargetFilter(enableExtensions),
defaultViewport: null,
handleDevToolsAsPage: true,
protocolTimeout: options.protocolTimeout,
};

let autoConnect = false;
Expand Down Expand Up @@ -138,6 +140,7 @@ interface McpLaunchOptions {
executablePath?: string;
channel?: Channel;
userDataDir?: string;
protocolTimeout?: number;
headless: boolean;
isolated: boolean;
logFile?: fs.WriteStream;
Expand Down Expand Up @@ -229,6 +232,7 @@ export async function launch(options: McpLaunchOptions): Promise<Browser> {
acceptInsecureCerts: options.acceptInsecureCerts,
handleDevToolsAsPage: true,
enableExtensions: options.enableExtensions,
protocolTimeout: options.protocolTimeout,
});
if (options.logFile) {
// FIXME: we are probably subscribing too late to catch startup logs. We
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export async function createMcpServer(
browserURL: serverArgs.browserUrl,
wsEndpoint: serverArgs.wsEndpoint,
wsHeaders: serverArgs.wsHeaders,
protocolTimeout: serverArgs.protocolTimeout,
// Important: only pass channel, if autoConnect is true.
channel: serverArgs.autoConnect
? (serverArgs.channel as Channel)
Expand All @@ -93,6 +94,7 @@ export async function createMcpServer(
channel: serverArgs.channel as Channel,
isolated: serverArgs.isolated ?? false,
userDataDir: serverArgs.userDataDir,
protocolTimeout: serverArgs.protocolTimeout,
logFile: options.logFile,
viewport: serverArgs.viewport,
chromeArgs,
Expand Down
27 changes: 22 additions & 5 deletions src/tools/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,17 +361,30 @@ export const uploadFile = definePageTool({
.describe(
'The uid of the file input element or an element that will open file chooser on the page from the page content snapshot',
),
filePath: zod.string().describe('The local path of the file to upload'),
filePath: zod
Comment thread
zerone0x marked this conversation as resolved.
.string()
.describe('The local path of a file to upload. Use filePaths for multiple files.')
.optional(),
filePaths: zod
.array(zod.string())
.describe('One or more local file paths to upload in a single operation.')
.optional(),
includeSnapshot: includeSnapshotSchema,
},
handler: async (request, response) => {
const {uid, filePath} = request.params;
const {uid} = request.params;
const filePaths =
request.params.filePaths ??
(request.params.filePath ? [request.params.filePath] : []);
if (!filePaths.length) {
throw new Error('Provide filePath or filePaths to upload.');
}
const handle = (await request.page.getElementByUid(
uid,
)) as ElementHandle<HTMLInputElement>;
try {
try {
await handle.uploadFile(filePath);
await handle.uploadFile(...filePaths);
} catch {
// Some sites use a proxy element to trigger file upload instead of
// a type=file element. In this case, we want to default to
Expand All @@ -381,7 +394,7 @@ export const uploadFile = definePageTool({
request.page.pptrPage.waitForFileChooser({timeout: 3000}),
handle.asLocator().click(),
]);
await fileChooser.accept([filePath]);
await fileChooser.accept(filePaths);
} catch {
throw new Error(
`Failed to upload file. The element could not accept the file directly, and clicking it did not trigger a file chooser.`,
Expand All @@ -391,7 +404,11 @@ export const uploadFile = definePageTool({
if (request.params.includeSnapshot) {
response.includeSnapshot();
}
response.appendResponseLine(`File uploaded from ${filePath}.`);
response.appendResponseLine(
filePaths.length === 1
? `File uploaded from ${filePaths[0]}.`
: `Files uploaded from ${filePaths.join(', ')}.`,
);
} finally {
void handle.dispose();
}
Expand Down