diff --git a/packages/playwright/src/mcp/browser/config.ts b/packages/playwright/src/mcp/browser/config.ts index f0c98c62539c2..1f9f0aa48e191 100644 --- a/packages/playwright/src/mcp/browser/config.ts +++ b/packages/playwright/src/mcp/browser/config.ts @@ -34,6 +34,7 @@ export type CLIOptions = { config?: string; device?: string; executablePath?: string; + grantPermissions?: string[]; headless?: boolean; host?: string; ignoreHttpsErrors?: boolean; @@ -182,6 +183,9 @@ export function configFromCLIOptions(cliOptions: CLIOptions): Config { if (cliOptions.blockServiceWorkers) contextOptions.serviceWorkers = 'block'; + if (cliOptions.grantPermissions) + contextOptions.permissions = cliOptions.grantPermissions; + const result: Config = { browser: { browserName, @@ -227,6 +231,7 @@ function configFromEnv(): Config { options.config = envToString(process.env.PLAYWRIGHT_MCP_CONFIG); options.device = envToString(process.env.PLAYWRIGHT_MCP_DEVICE); options.executablePath = envToString(process.env.PLAYWRIGHT_MCP_EXECUTABLE_PATH); + options.grantPermissions = commaSeparatedList(process.env.PLAYWRIGHT_MCP_GRANT_PERMISSIONS); options.headless = envToBoolean(process.env.PLAYWRIGHT_MCP_HEADLESS); options.host = envToString(process.env.PLAYWRIGHT_MCP_HOST); options.ignoreHttpsErrors = envToBoolean(process.env.PLAYWRIGHT_MCP_IGNORE_HTTPS_ERRORS); diff --git a/packages/playwright/src/mcp/program.ts b/packages/playwright/src/mcp/program.ts index 4a81b873efc1f..acc349ba00756 100644 --- a/packages/playwright/src/mcp/program.ts +++ b/packages/playwright/src/mcp/program.ts @@ -39,6 +39,7 @@ export function decorateCommand(command: Command, version: string) { .option('--device ', 'device to emulate, for example: "iPhone 15"') .option('--executable-path ', 'path to the browser executable.') .option('--extension', 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.') + .option('--grant-permissions ', 'List of permissions to grant to the browser context, for example "geolocation", "clipboard-read", "clipboard-write".', commaSeparatedList) .option('--headless', 'run browser in headless mode, headed by default') .option('--host ', 'host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.') .option('--ignore-https-errors', 'ignore https errors') diff --git a/tests/mcp/clipboard.spec.ts b/tests/mcp/clipboard.spec.ts new file mode 100644 index 0000000000000..264948b924474 --- /dev/null +++ b/tests/mcp/clipboard.spec.ts @@ -0,0 +1,48 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { test, expect } from './fixtures'; + +test('clipboard write without permission dialog', async ({ startClient, server, mcpBrowser }) => { + test.skip(mcpBrowser === 'firefox' || mcpBrowser === 'webkit', 'Clipboard permissions are fully supported only in Chromium'); + const { client } = await startClient({ + args: [`--grant-permissions=clipboard-read,clipboard-write`] + }); + await client.callTool({ + name: 'browser_navigate', + arguments: { url: server.HELLO_WORLD }, + }); + const writeResult = await client.callTool({ + name: 'browser_evaluate', + arguments: { + function: `() => navigator.clipboard.writeText('Hello from Playwright!').then( + () => 'Write successful', + e => 'Write failed: ' + e.message)`, + }, + }); + expect(writeResult).toHaveResponse({ + result: '"Write successful"', + }); + const readResult = await client.callTool({ + name: 'browser_evaluate', + arguments: { + function: `() => navigator.clipboard.readText()`, + }, + }); + expect(readResult).toHaveResponse({ + result: '"Hello from Playwright!"', + }); +});