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
This commit is contained in:
Ryan Malloy 2026-01-12 21:01:48 -07:00
parent 5e5123d1ac
commit 0031d17f32
4 changed files with 225 additions and 0 deletions

View File

@ -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 <mode> 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) => {
<!-- NOTE: This has been generated via update-readme.js -->
- **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**
<!-- NOTE: This has been generated via update-readme.js -->
- **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!
<!-- NOTE: This has been generated via update-readme.js -->
- **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**
<!-- NOTE: This has been generated via update-readme.js -->
- **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
<!-- NOTE: This has been generated via update-readme.js -->
- **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**
<!-- NOTE: This has been generated via update-readme.js -->
- **browser_set_offline**
- Title: Set browser offline mode
- Description: Toggle browser offline mode on/off (equivalent to DevTools offline checkbox)

View File

@ -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);

View File

@ -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 <mode>', '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 <tokens>', 'maximum number of tokens allowed in page snapshots before truncation. Use 0 to disable truncation. Default is 10000.', parseInt)

View File

@ -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,
];