From b15b446ab661fe0f72fba01ecd1f45e9266ff0c4 Mon Sep 17 00:00:00 2001 From: Clawdbot Date: Mon, 30 Mar 2026 04:36:13 +0200 Subject: [PATCH 1/4] 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/4] 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/4] 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.'); } From f23b59b4dd56145ebd8db5a6306811bd5d6bf1a7 Mon Sep 17 00:00:00 2001 From: zerone0x <39543393+zerone0x@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:27:42 +0000 Subject: [PATCH 4/4] fix: address upload file review feedback --- README.md | 4 --- src/bin/chrome-devtools-cli-options.ts | 10 ++------ src/bin/chrome-devtools-mcp-cli-options.ts | 14 ----------- src/bin/cliDefinitions.ts | 10 ++------ src/browser.ts | 4 --- src/index.ts | 2 -- src/tools/input.ts | 29 +++++++--------------- 7 files changed, 13 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 7c2f7e0d2..438024c3e 100644 --- a/README.md +++ b/README.md @@ -501,10 +501,6 @@ 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-cli-options.ts b/src/bin/chrome-devtools-cli-options.ts index cae5d63ec..4f0f1243f 100644 --- a/src/bin/chrome-devtools-cli-options.ts +++ b/src/bin/chrome-devtools-cli-options.ts @@ -711,14 +711,8 @@ export const commands: Commands = { name: 'filePath', type: 'string', description: - 'The local path of a file to upload. For multiple files, pass a comma-separated list, or use filePaths.', - required: false, - }, - filePaths: { - name: 'filePaths', - type: 'array', - description: 'One or more local file paths to upload in a single call.', - required: false, + 'The local path of the file to upload. For multiple files, pass a comma-separated list.', + required: true, }, includeSnapshot: { name: 'includeSnapshot', diff --git a/src/bin/chrome-devtools-mcp-cli-options.ts b/src/bin/chrome-devtools-mcp-cli-options.ts index 0d520fd93..b4792629c 100644 --- a/src/bin/chrome-devtools-mcp-cli-options.ts +++ b/src/bin/chrome-devtools-mcp-cli-options.ts @@ -87,20 +87,6 @@ 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/bin/cliDefinitions.ts b/src/bin/cliDefinitions.ts index 3b02d5686..e3f2485c0 100644 --- a/src/bin/cliDefinitions.ts +++ b/src/bin/cliDefinitions.ts @@ -692,14 +692,8 @@ export const commands: Commands = { name: 'filePath', type: 'string', description: - 'The local path of a file to upload. For multiple files, pass a comma-separated list, or use filePaths.', - required: false, - }, - filePaths: { - name: 'filePaths', - type: 'array', - description: 'One or more local file paths to upload in a single call.', - required: false, + 'The local path of the file to upload. For multiple files, pass a comma-separated list.', + required: true, }, includeSnapshot: { name: 'includeSnapshot', diff --git a/src/browser.ts b/src/browser.ts index dd5ad51d1..7deea75b4 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -47,7 +47,6 @@ export async function ensureBrowserConnected(options: { browserURL?: string; wsEndpoint?: string; wsHeaders?: Record; - protocolTimeout?: number; devtools: boolean; channel?: Channel; userDataDir?: string; @@ -62,7 +61,6 @@ export async function ensureBrowserConnected(options: { targetFilter: makeTargetFilter(enableExtensions), defaultViewport: null, handleDevToolsAsPage: true, - protocolTimeout: options.protocolTimeout, }; let autoConnect = false; @@ -140,7 +138,6 @@ interface McpLaunchOptions { executablePath?: string; channel?: Channel; userDataDir?: string; - protocolTimeout?: number; headless: boolean; isolated: boolean; logFile?: fs.WriteStream; @@ -232,7 +229,6 @@ 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 e54d59a48..2689e34a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -80,7 +80,6 @@ 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) @@ -94,7 +93,6 @@ 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, diff --git a/src/tools/input.ts b/src/tools/input.ts index 57779b6cf..ec9b796cd 100644 --- a/src/tools/input.ts +++ b/src/tools/input.ts @@ -363,31 +363,20 @@ export const uploadFile = definePageTool({ ), 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(), + .describe( + 'The local path of the file to upload. For multiple files, pass a comma-separated list.', + ), includeSnapshot: includeSnapshotSchema, }, handler: async (request, response) => { - const {uid} = request.params; - - const filePathsFromFilePath = request.params.filePath - ? request.params.filePath - .split(',') - .map((p) => p.trim()) - .filter(Boolean) - : []; - - const filePaths = [ - ...(request.params.filePaths ?? []), - ...filePathsFromFilePath, - ]; + const {uid, filePath} = request.params; + const filePaths = filePath + .split(',') + .map(path => path.trim()) + .filter(Boolean); if (!filePaths.length) { - throw new Error('Provide filePath or filePaths to upload.'); + throw new Error('Provide filePath to upload.'); } const handle = (await request.page.getElementByUid( uid,