From 0031d17f3289b7df81955048d3c997204da1a411 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Mon, 12 Jan 2026 21:01:48 -0700 Subject: [PATCH] feat: add runtime permission granting tools and --grant-all-permissions flag New tools for managing browser permissions at runtime (no restart needed): - browser_grant_permissions: Grant specific or ALL permissions at runtime - Supports `all: true` to grant all common permissions at once - browser_clear_permissions: Revoke all granted permissions - browser_set_geolocation: Set geolocation coordinates at runtime New CLI flag: - --grant-all-permissions: Start with all permissions pre-granted - PLAYWRIGHT_MCP_GRANT_ALL_PERMISSIONS env var support Permissions granted with `all: true` or --grant-all-permissions: - geolocation, notifications, camera, microphone - clipboard-read, clipboard-write - accelerometer, gyroscope, magnetometer - midi, background-sync, ambient-light-sensor - accessibility-events --- README.md | 55 ++++++++++++++ src/config.ts | 21 ++++++ src/program.ts | 1 + src/tools/notifications.ts | 148 +++++++++++++++++++++++++++++++++++++ 4 files changed, 225 insertions(+) diff --git a/README.md b/README.md index b033b6b..d8f888d 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,9 @@ Playwright MCP server supports following arguments. They can be provided in the --no-isolated use a persistent browser profile. Enables features like Push API that require non-incognito mode. + --grant-all-permissions grant all browser permissions (geolocation, + camera, microphone, clipboard, etc.) at + startup. --image-responses whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow". --no-snapshots disable automatic page snapshots after @@ -556,6 +559,14 @@ http.createServer(async (req, res) => { +- **browser_clear_permissions** + - Title: Clear all browser permissions + - Description: Revoke all previously granted permissions for the current browser context. Sites will need to request permissions again. + - Parameters: None + - Read-only: **false** + + + - **browser_clear_requests** - Title: Clear captured requests - Description: Clear all captured HTTP request data from memory. Useful for freeing up memory during long sessions or when starting fresh analysis. @@ -835,6 +846,39 @@ This is the FIRST conversational browser automation MCP server! +- **browser_grant_permissions** + - Title: Grant browser permissions at runtime + - Description: Grant browser permissions at runtime without restarting the browser. This is faster than using browser_configure which requires a browser restart. + +**Quick option:** Use `all: true` to grant all common permissions at once! + +**Available permissions:** +- geolocation - Access user location +- notifications - Show browser notifications +- camera - Access camera/webcam +- microphone - Access microphone +- clipboard-read - Read from clipboard +- clipboard-write - Write to clipboard +- accelerometer - Access motion sensors +- gyroscope - Access orientation sensors +- magnetometer - Access compass +- accessibility-events - Accessibility automation +- midi - MIDI device access +- midi-sysex - MIDI system exclusive messages +- background-sync - Background sync API +- ambient-light-sensor - Light sensor access +- payment-handler - Payment request API +- storage-access - Storage access API + +**Note:** Some permissions may require user interaction (like camera/microphone device selection) even after being granted. + - Parameters: + - `permissions` (array, optional): List of permissions to grant (e.g., ["geolocation", "camera", "microphone"]) + - `all` (boolean, optional): Grant ALL common permissions at once (geolocation, notifications, camera, microphone, clipboard, sensors, midi) + - `origin` (string, optional): Origin to grant permissions for (e.g., "https://example.com"). If not specified, grants for all origins. + - Read-only: **false** + + + - **browser_handle_dialog** - Title: Handle a dialog - Description: Handle a dialog. Returns page snapshot after handling dialog (configurable via browser_configure_snapshots). @@ -1101,6 +1145,17 @@ Full API: See MODEL-COLLABORATION-API.md +- **browser_set_geolocation** + - Title: Set geolocation at runtime + - Description: Set the browser's geolocation at runtime without restarting. Automatically grants geolocation permission. + - Parameters: + - `latitude` (number): Latitude coordinate (-90 to 90) + - `longitude` (number): Longitude coordinate (-180 to 180) + - `accuracy` (number, optional): Accuracy in meters (default: 100) + - Read-only: **false** + + + - **browser_set_offline** - Title: Set browser offline mode - Description: Toggle browser offline mode on/off (equivalent to DevTools offline checkbox) diff --git a/src/config.ts b/src/config.ts index 77cffdd..7a2db10 100644 --- a/src/config.ts +++ b/src/config.ts @@ -34,6 +34,7 @@ export type CLIOptions = { consoleOutputFile?: string; device?: string; executablePath?: string; + grantAllPermissions?: boolean; headless?: boolean; host?: string; ignoreHttpsErrors?: boolean; @@ -191,6 +192,25 @@ export function configFromCLIOptions(cliOptions: CLIOptions): Config { if (cliOptions.blockServiceWorkers) contextOptions.serviceWorkers = 'block'; + // Grant all permissions if requested + if (cliOptions.grantAllPermissions) { + contextOptions.permissions = [ + 'geolocation', + 'notifications', + 'camera', + 'microphone', + 'clipboard-read', + 'clipboard-write', + 'accelerometer', + 'gyroscope', + 'magnetometer', + 'midi', + 'background-sync', + 'ambient-light-sensor', + 'accessibility-events', + ]; + } + const result: Config = { browser: { browserName, @@ -236,6 +256,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.grantAllPermissions = envToBoolean(process.env.PLAYWRIGHT_MCP_GRANT_ALL_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/src/program.ts b/src/program.ts index d52ad3c..bb4bc27 100644 --- a/src/program.ts +++ b/src/program.ts @@ -46,6 +46,7 @@ program .option('--ignore-https-errors', 'ignore https errors') .option('--isolated', 'keep the browser profile in memory, do not save it to disk. This is the default.') .option('--no-isolated', 'use a persistent browser profile. Enables features like Push API that require non-incognito mode.') + .option('--grant-all-permissions', 'grant all browser permissions (geolocation, camera, microphone, clipboard, etc.) at startup.') .option('--image-responses ', 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".') .option('--no-snapshots', 'disable automatic page snapshots after interactive operations like clicks. Use browser_snapshot tool for explicit snapshots.') .option('--max-snapshot-tokens ', 'maximum number of tokens allowed in page snapshots before truncation. Use 0 to disable truncation. Default is 10000.', parseInt) diff --git a/src/tools/notifications.ts b/src/tools/notifications.ts index 27e0ea6..24c9e39 100644 --- a/src/tools/notifications.ts +++ b/src/tools/notifications.ts @@ -235,10 +235,158 @@ const clearNotifications = defineTool({ }, }); +// All commonly-used permissions that can be granted +const ALL_PERMISSIONS = [ + 'geolocation', + 'notifications', + 'camera', + 'microphone', + 'clipboard-read', + 'clipboard-write', + 'accelerometer', + 'gyroscope', + 'magnetometer', + 'midi', + 'background-sync', + 'ambient-light-sensor', + 'accessibility-events', +]; + +/** + * Grant permissions at runtime without restarting the browser. + * More flexible than browser_configure which requires a restart. + */ +const grantPermissions = defineTool({ + capability: 'core', + + schema: { + name: 'browser_grant_permissions', + title: 'Grant browser permissions at runtime', + description: `Grant browser permissions at runtime without restarting the browser. This is faster than using browser_configure which requires a browser restart. + +**Quick option:** Use \`all: true\` to grant all common permissions at once! + +**Available permissions:** +- geolocation - Access user location +- notifications - Show browser notifications +- camera - Access camera/webcam +- microphone - Access microphone +- clipboard-read - Read from clipboard +- clipboard-write - Write to clipboard +- accelerometer - Access motion sensors +- gyroscope - Access orientation sensors +- magnetometer - Access compass +- accessibility-events - Accessibility automation +- midi - MIDI device access +- midi-sysex - MIDI system exclusive messages +- background-sync - Background sync API +- ambient-light-sensor - Light sensor access +- payment-handler - Payment request API +- storage-access - Storage access API + +**Note:** Some permissions may require user interaction (like camera/microphone device selection) even after being granted.`, + inputSchema: z.object({ + permissions: z.array(z.string()).optional().describe('List of permissions to grant (e.g., ["geolocation", "camera", "microphone"])'), + all: z.boolean().optional().describe('Grant ALL common permissions at once (geolocation, notifications, camera, microphone, clipboard, sensors, midi)'), + origin: z.string().optional().describe('Origin to grant permissions for (e.g., "https://example.com"). If not specified, grants for all origins.'), + }), + type: 'destructive', + }, + + handle: async (context, params, response) => { + const browserContext = await context.existingBrowserContext(); + if (!browserContext) + throw new Error('No browser context available. Navigate to a page first.'); + + // Determine which permissions to grant + let permissionsToGrant: string[]; + if (params.all) { + permissionsToGrant = ALL_PERMISSIONS; + } else if (params.permissions && params.permissions.length > 0) { + permissionsToGrant = params.permissions; + } else { + throw new Error('Either specify "permissions" array or set "all: true" to grant all permissions.'); + } + + const grantOptions = params.origin ? { origin: params.origin } : undefined; + + await browserContext.grantPermissions(permissionsToGrant, grantOptions); + + const scope = params.origin ? `for ${params.origin}` : 'for all origins'; + const header = params.all ? '✅ Granted ALL permissions' : '✅ Granted permissions'; + response.addResult(`${header} ${scope}:\n${permissionsToGrant.map(p => ` • ${p}`).join('\n')}`); + }, +}); + +/** + * Clear all granted permissions. + */ +const clearPermissions = defineTool({ + capability: 'core', + + schema: { + name: 'browser_clear_permissions', + title: 'Clear all browser permissions', + description: 'Revoke all previously granted permissions for the current browser context. Sites will need to request permissions again.', + inputSchema: z.object({}), + type: 'destructive', + }, + + handle: async (context, _params, response) => { + const browserContext = await context.existingBrowserContext(); + if (!browserContext) + throw new Error('No browser context available. Navigate to a page first.'); + + await browserContext.clearPermissions(); + + response.addResult('✅ All permissions have been cleared. Sites will need to request permissions again.'); + }, +}); + +/** + * Set geolocation at runtime. + */ +const setGeolocation = defineTool({ + capability: 'core', + + schema: { + name: 'browser_set_geolocation', + title: 'Set geolocation at runtime', + description: 'Set the browser\'s geolocation at runtime without restarting. Automatically grants geolocation permission.', + inputSchema: z.object({ + latitude: z.number().min(-90).max(90).describe('Latitude coordinate (-90 to 90)'), + longitude: z.number().min(-180).max(180).describe('Longitude coordinate (-180 to 180)'), + accuracy: z.number().optional().describe('Accuracy in meters (default: 100)'), + }), + type: 'destructive', + }, + + handle: async (context, params, response) => { + const browserContext = await context.existingBrowserContext(); + if (!browserContext) + throw new Error('No browser context available. Navigate to a page first.'); + + // Grant geolocation permission first + await browserContext.grantPermissions(['geolocation']); + + // Set the geolocation + await browserContext.setGeolocation({ + latitude: params.latitude, + longitude: params.longitude, + accuracy: params.accuracy || 100, + }); + + response.addResult(`✅ Geolocation set to:\n • Latitude: ${params.latitude}\n • Longitude: ${params.longitude}\n • Accuracy: ${params.accuracy || 100}m\n\nGeolocation permission has been automatically granted.`); + }, +}); + export default [ configureNotifications, listNotifications, handleNotification, waitForNotification, clearNotifications, + grantPermissions, + clearPermissions, + setGeolocation, ];