From b15b446ab661fe0f72fba01ecd1f45e9266ff0c4 Mon Sep 17 00:00:00 2001 From: Clawdbot Date: Mon, 30 Mar 2026 04:36:13 +0200 Subject: [PATCH 1/3] feat: add protocolTimeout option --- README.md | 4 ++++ src/bin/chrome-devtools-mcp-cli-options.ts | 14 ++++++++++++++ src/browser.ts | 4 ++++ src/index.ts | 2 ++ 4 files changed, 24 insertions(+) diff --git a/README.md b/README.md index 438024c3e..7c2f7e0d2 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/bin/chrome-devtools-mcp-cli-options.ts b/src/bin/chrome-devtools-mcp-cli-options.ts index b4792629c..0d520fd93 100644 --- a/src/bin/chrome-devtools-mcp-cli-options.ts +++ b/src/bin/chrome-devtools-mcp-cli-options.ts @@ -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.', diff --git a/src/browser.ts b/src/browser.ts index 7deea75b4..dd5ad51d1 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -47,6 +47,7 @@ export async function ensureBrowserConnected(options: { browserURL?: string; wsEndpoint?: string; wsHeaders?: Record; + protocolTimeout?: number; devtools: boolean; channel?: Channel; userDataDir?: string; @@ -61,6 +62,7 @@ export async function ensureBrowserConnected(options: { targetFilter: makeTargetFilter(enableExtensions), defaultViewport: null, handleDevToolsAsPage: true, + protocolTimeout: options.protocolTimeout, }; let autoConnect = false; @@ -138,6 +140,7 @@ interface McpLaunchOptions { executablePath?: string; channel?: Channel; userDataDir?: string; + protocolTimeout?: number; headless: boolean; isolated: boolean; logFile?: fs.WriteStream; @@ -229,6 +232,7 @@ export async function launch(options: McpLaunchOptions): Promise { 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 diff --git a/src/index.ts b/src/index.ts index 2689e34a6..e54d59a48 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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) @@ -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, From 9dcc5465a7c19409cc4a886c837ffea425564d83 Mon Sep 17 00:00:00 2001 From: Clawdbot Date: Mon, 30 Mar 2026 04:38:30 +0200 Subject: [PATCH 2/3] fix: support multi-file uploads --- src/bin/chrome-devtools-cli-options.ts | 11 +++++++++-- src/bin/cliDefinitions.ts | 11 +++++++++-- src/tools/input.ts | 27 +++++++++++++++++++++----- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/bin/chrome-devtools-cli-options.ts b/src/bin/chrome-devtools-cli-options.ts index 6e317a333..24e67d9ea 100644 --- a/src/bin/chrome-devtools-cli-options.ts +++ b/src/bin/chrome-devtools-cli-options.ts @@ -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', diff --git a/src/bin/cliDefinitions.ts b/src/bin/cliDefinitions.ts index d32705617..38b272450 100644 --- a/src/bin/cliDefinitions.ts +++ b/src/bin/cliDefinitions.ts @@ -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', diff --git a/src/tools/input.ts b/src/tools/input.ts index 492d55378..cf1e1c287 100644 --- a/src/tools/input.ts +++ b/src/tools/input.ts @@ -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 + .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; 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 @@ -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.`, @@ -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(); } From 0e3862c35dc4ee53ab2977b2966b64fb62350b75 Mon Sep 17 00:00:00 2001 From: zerone0x Date: Fri, 17 Apr 2026 15:21:57 +0200 Subject: [PATCH 3/3] fix: support comma-separated filePath for upload_file --- src/bin/chrome-devtools-cli-options.ts | 2 +- src/bin/cliDefinitions.ts | 2 +- src/tools/input.ts | 16 +++++++++++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/bin/chrome-devtools-cli-options.ts b/src/bin/chrome-devtools-cli-options.ts index 24e67d9ea..cae5d63ec 100644 --- a/src/bin/chrome-devtools-cli-options.ts +++ b/src/bin/chrome-devtools-cli-options.ts @@ -711,7 +711,7 @@ export const commands: Commands = { name: 'filePath', type: 'string', description: - 'The local path of a file to upload. Use filePaths for multiple files.', + 'The local path of a file to upload. For multiple files, pass a comma-separated list, or use filePaths.', required: false, }, filePaths: { diff --git a/src/bin/cliDefinitions.ts b/src/bin/cliDefinitions.ts index 38b272450..3b02d5686 100644 --- a/src/bin/cliDefinitions.ts +++ b/src/bin/cliDefinitions.ts @@ -692,7 +692,7 @@ export const commands: Commands = { name: 'filePath', type: 'string', description: - 'The local path of a file to upload. Use filePaths for multiple files.', + 'The local path of a file to upload. For multiple files, pass a comma-separated list, or use filePaths.', required: false, }, filePaths: { diff --git a/src/tools/input.ts b/src/tools/input.ts index cf1e1c287..57779b6cf 100644 --- a/src/tools/input.ts +++ b/src/tools/input.ts @@ -373,9 +373,19 @@ export const uploadFile = definePageTool({ }, handler: async (request, response) => { const {uid} = request.params; - const filePaths = - request.params.filePaths ?? - (request.params.filePath ? [request.params.filePath] : []); + + const filePathsFromFilePath = request.params.filePath + ? request.params.filePath + .split(',') + .map((p) => p.trim()) + .filter(Boolean) + : []; + + const filePaths = [ + ...(request.params.filePaths ?? []), + ...filePathsFromFilePath, + ]; + if (!filePaths.length) { throw new Error('Provide filePath or filePaths to upload.'); }