playwright-stealth-mcp-server 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/build/index.js +58 -6
- package/package.json +1 -1
- package/shared/index.d.ts +3 -2
- package/shared/index.js +3 -2
- package/shared/server.d.ts +11 -2
- package/shared/server.js +20 -3
- package/shared/types.d.ts +13 -0
- package/shared/types.js +22 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ A Model Context Protocol (MCP) server for browser automation using Playwright wi
|
|
|
9
9
|
- **Persistent Sessions**: Browser session persists across tool calls for multi-step automation
|
|
10
10
|
- **Screenshot Support**: Capture page screenshots for visual verification
|
|
11
11
|
- **Code Execution**: Run arbitrary Playwright code with access to the `page` object
|
|
12
|
+
- **Full Permissions**: All browser permissions (notifications, geolocation, camera, etc.) granted by default for testing web apps
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
@@ -95,6 +96,7 @@ Add to your Claude Desktop config file:
|
|
|
95
96
|
| `PROXY_USERNAME` | Proxy authentication username | - |
|
|
96
97
|
| `PROXY_PASSWORD` | Proxy authentication password | - |
|
|
97
98
|
| `PROXY_BYPASS` | Comma-separated list of hosts to bypass proxy | - |
|
|
99
|
+
| `BROWSER_PERMISSIONS` | Comma-separated list of browser permissions to grant (see below) | All permissions |
|
|
98
100
|
|
|
99
101
|
## Available Tools
|
|
100
102
|
|
|
@@ -275,6 +277,65 @@ The server supports HTTP/HTTPS proxies with optional authentication, making it c
|
|
|
275
277
|
|
|
276
278
|
**Note:** When proxy is configured, the server performs a health check on startup to verify the proxy connection works. If the health check fails, the server will exit with an error.
|
|
277
279
|
|
|
280
|
+
## Browser Permissions
|
|
281
|
+
|
|
282
|
+
By default, the server grants **all** browser permissions to the browser context. This enables testing of features that require permissions such as:
|
|
283
|
+
|
|
284
|
+
- **Web Push Notifications** (`notifications`)
|
|
285
|
+
- **Geolocation** (`geolocation`)
|
|
286
|
+
- **Camera/Microphone** (`camera`, `microphone`)
|
|
287
|
+
- **Clipboard** (`clipboard-read`, `clipboard-write`)
|
|
288
|
+
- **MIDI devices** (`midi`, `midi-sysex`)
|
|
289
|
+
- **Sensors** (`accelerometer`, `gyroscope`, `magnetometer`, `ambient-light-sensor`)
|
|
290
|
+
- **Other** (`background-sync`, `local-fonts`, `payment-handler`, `storage-access`)
|
|
291
|
+
|
|
292
|
+
### Constraining Permissions
|
|
293
|
+
|
|
294
|
+
If you need to restrict permissions (e.g., for security testing or to simulate a user who denies permissions), use the `BROWSER_PERMISSIONS` environment variable:
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{
|
|
298
|
+
"mcpServers": {
|
|
299
|
+
"playwright": {
|
|
300
|
+
"command": "npx",
|
|
301
|
+
"args": ["-y", "playwright-stealth-mcp-server"],
|
|
302
|
+
"env": {
|
|
303
|
+
"BROWSER_PERMISSIONS": "notifications,clipboard-read,clipboard-write"
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
This will grant only the specified permissions. All other permission requests will be denied.
|
|
311
|
+
|
|
312
|
+
**Note:** Invalid permission names are silently ignored with a warning in the server logs. If a permission is not recognized, it will be skipped.
|
|
313
|
+
|
|
314
|
+
**Browser Compatibility:** Permission support varies by browser. This server uses Chromium by default, where all listed permissions are supported. If using Firefox or WebKit, some permissions may not work.
|
|
315
|
+
|
|
316
|
+
### Testing Web Push Notifications
|
|
317
|
+
|
|
318
|
+
With the default configuration (all permissions granted), you can test web push notifications:
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
// Check if notifications are supported and granted
|
|
322
|
+
const permission = await page.evaluate(() => Notification.permission);
|
|
323
|
+
console.log('Notification permission:', permission); // Should be "granted"
|
|
324
|
+
|
|
325
|
+
// Request notification permission (will auto-grant)
|
|
326
|
+
await page.evaluate(async () => {
|
|
327
|
+
const result = await Notification.requestPermission();
|
|
328
|
+
console.log('Permission result:', result);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Create a test notification
|
|
332
|
+
await page.evaluate(() => {
|
|
333
|
+
new Notification('Test Notification', {
|
|
334
|
+
body: 'This is a test notification from Playwright',
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
278
339
|
## Development
|
|
279
340
|
|
|
280
341
|
```bash
|
package/build/index.js
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { createMCPServer } from '../shared/index.js';
|
|
4
4
|
import { logServerStart, logError, logWarning, logInfo } from '../shared/logging.js';
|
|
5
|
+
import { ALL_BROWSER_PERMISSIONS } from '../shared/types.js';
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// PERMISSIONS CONFIGURATION
|
|
8
|
+
// =============================================================================
|
|
9
|
+
/**
|
|
10
|
+
* Parse browser permissions from environment variable.
|
|
11
|
+
* If BROWSER_PERMISSIONS is not set, returns undefined (which means grant all permissions).
|
|
12
|
+
* If BROWSER_PERMISSIONS is set, returns the parsed list of permissions.
|
|
13
|
+
* Invalid permissions are logged as warnings and skipped.
|
|
14
|
+
*/
|
|
15
|
+
function parseBrowserPermissions() {
|
|
16
|
+
const permissionsEnv = process.env.BROWSER_PERMISSIONS;
|
|
17
|
+
if (!permissionsEnv) {
|
|
18
|
+
// No constraint specified - will grant all permissions by default
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const requestedPermissions = permissionsEnv.split(',').map((p) => p.trim().toLowerCase());
|
|
22
|
+
const validPermissions = [];
|
|
23
|
+
const invalidPermissions = [];
|
|
24
|
+
for (const perm of requestedPermissions) {
|
|
25
|
+
if (perm === '')
|
|
26
|
+
continue;
|
|
27
|
+
if (ALL_BROWSER_PERMISSIONS.includes(perm)) {
|
|
28
|
+
validPermissions.push(perm);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
invalidPermissions.push(perm);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (invalidPermissions.length > 0) {
|
|
35
|
+
logWarning('config', `Unknown browser permissions ignored: ${invalidPermissions.join(', ')}. ` +
|
|
36
|
+
`Valid permissions: ${ALL_BROWSER_PERMISSIONS.join(', ')}`);
|
|
37
|
+
}
|
|
38
|
+
return validPermissions;
|
|
39
|
+
}
|
|
5
40
|
// =============================================================================
|
|
6
41
|
// PROXY CONFIGURATION
|
|
7
42
|
// =============================================================================
|
|
@@ -116,12 +151,18 @@ function validateEnvironment() {
|
|
|
116
151
|
description: 'Comma-separated list of hosts to bypass proxy',
|
|
117
152
|
defaultValue: undefined,
|
|
118
153
|
},
|
|
154
|
+
{
|
|
155
|
+
name: 'BROWSER_PERMISSIONS',
|
|
156
|
+
description: 'Comma-separated list of browser permissions to grant. If not set, ALL permissions are granted.',
|
|
157
|
+
defaultValue: 'all (notifications, geolocation, camera, microphone, clipboard-read, etc.)',
|
|
158
|
+
},
|
|
119
159
|
];
|
|
120
160
|
// Log configuration
|
|
121
161
|
const stealthMode = process.env.STEALTH_MODE === 'true';
|
|
122
162
|
const headless = process.env.HEADLESS !== 'false';
|
|
123
163
|
const timeout = process.env.TIMEOUT || '30000';
|
|
124
164
|
const proxyUrl = process.env.PROXY_URL;
|
|
165
|
+
const browserPermissions = process.env.BROWSER_PERMISSIONS;
|
|
125
166
|
if (stealthMode) {
|
|
126
167
|
logWarning('config', 'Stealth mode enabled - using anti-detection measures');
|
|
127
168
|
// Log stealth-specific configuration
|
|
@@ -149,6 +190,12 @@ function validateEnvironment() {
|
|
|
149
190
|
logInfo('config', 'Proxy authentication enabled');
|
|
150
191
|
}
|
|
151
192
|
}
|
|
193
|
+
if (browserPermissions) {
|
|
194
|
+
logInfo('config', `Browser permissions constrained to: ${browserPermissions}`);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
logInfo('config', 'Browser permissions: all (default)');
|
|
198
|
+
}
|
|
152
199
|
// Show optional configuration if DEBUG is set
|
|
153
200
|
if (process.env.DEBUG) {
|
|
154
201
|
console.error('\nOptional environment variables:');
|
|
@@ -176,7 +223,9 @@ async function main() {
|
|
|
176
223
|
validateEnvironment();
|
|
177
224
|
// Step 2: Build proxy configuration if provided
|
|
178
225
|
const proxyConfig = buildProxyConfig();
|
|
179
|
-
// Step 3:
|
|
226
|
+
// Step 3: Parse browser permissions (undefined = all permissions granted)
|
|
227
|
+
const browserPermissions = parseBrowserPermissions();
|
|
228
|
+
// Step 4: If proxy is configured, perform health check
|
|
180
229
|
if (proxyConfig) {
|
|
181
230
|
try {
|
|
182
231
|
await healthCheckProxy(proxyConfig);
|
|
@@ -187,11 +236,14 @@ async function main() {
|
|
|
187
236
|
process.exit(1);
|
|
188
237
|
}
|
|
189
238
|
}
|
|
190
|
-
// Step
|
|
191
|
-
const { server, registerHandlers, cleanup } = createMCPServer(
|
|
192
|
-
|
|
239
|
+
// Step 5: Create server using factory, passing proxy config and permissions
|
|
240
|
+
const { server, registerHandlers, cleanup } = createMCPServer({
|
|
241
|
+
proxy: proxyConfig,
|
|
242
|
+
permissions: browserPermissions,
|
|
243
|
+
});
|
|
244
|
+
// Step 6: Register all handlers (tools)
|
|
193
245
|
await registerHandlers(server);
|
|
194
|
-
// Step
|
|
246
|
+
// Step 7: Set up graceful shutdown
|
|
195
247
|
const handleShutdown = async () => {
|
|
196
248
|
logWarning('shutdown', 'Received shutdown signal, closing browser...');
|
|
197
249
|
await cleanup();
|
|
@@ -199,7 +251,7 @@ async function main() {
|
|
|
199
251
|
};
|
|
200
252
|
process.on('SIGINT', handleShutdown);
|
|
201
253
|
process.on('SIGTERM', handleShutdown);
|
|
202
|
-
// Step
|
|
254
|
+
// Step 8: Start server with stdio transport
|
|
203
255
|
const transport = new StdioServerTransport();
|
|
204
256
|
await server.connect(transport);
|
|
205
257
|
const stealthMode = process.env.STEALTH_MODE === 'true';
|
package/package.json
CHANGED
package/shared/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export { createMCPServer, type IPlaywrightClient, type ClientFactory } from './server.js';
|
|
1
|
+
export { createMCPServer, type IPlaywrightClient, type ClientFactory, type CreateMCPServerOptions, } from './server.js';
|
|
2
2
|
export { createRegisterTools } from './tools.js';
|
|
3
3
|
export { registerResources } from './resources.js';
|
|
4
|
-
export { logServerStart, logError, logWarning, logDebug } from './logging.js';
|
|
4
|
+
export { logServerStart, logError, logWarning, logDebug, logInfo } from './logging.js';
|
|
5
5
|
export * from './storage/index.js';
|
|
6
|
+
export * from './types.js';
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
package/shared/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export { createMCPServer } from './server.js';
|
|
1
|
+
export { createMCPServer, } from './server.js';
|
|
2
2
|
export { createRegisterTools } from './tools.js';
|
|
3
3
|
export { registerResources } from './resources.js';
|
|
4
|
-
export { logServerStart, logError, logWarning, logDebug } from './logging.js';
|
|
4
|
+
export { logServerStart, logError, logWarning, logDebug, logInfo } from './logging.js';
|
|
5
5
|
export * from './storage/index.js';
|
|
6
|
+
export * from './types.js';
|
package/shared/server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import type { ExecuteResult, BrowserState, PlaywrightConfig, ProxyConfig } from './types.js';
|
|
2
|
+
import type { ExecuteResult, BrowserState, PlaywrightConfig, ProxyConfig, BrowserPermission } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Maximum allowed dimension for screenshots in pixels.
|
|
5
5
|
* Claude's API rejects images where either dimension exceeds 8000 pixels.
|
|
@@ -70,7 +70,16 @@ export declare class PlaywrightClient implements IPlaywrightClient {
|
|
|
70
70
|
getConfig(): PlaywrightConfig;
|
|
71
71
|
}
|
|
72
72
|
export type ClientFactory = () => IPlaywrightClient;
|
|
73
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Options for creating the MCP server
|
|
75
|
+
*/
|
|
76
|
+
export interface CreateMCPServerOptions {
|
|
77
|
+
/** Proxy configuration for browser connections */
|
|
78
|
+
proxy?: ProxyConfig;
|
|
79
|
+
/** Browser permissions to grant. If undefined, all permissions are granted. */
|
|
80
|
+
permissions?: BrowserPermission[];
|
|
81
|
+
}
|
|
82
|
+
export declare function createMCPServer(options?: CreateMCPServerOptions): {
|
|
74
83
|
server: Server<{
|
|
75
84
|
method: string;
|
|
76
85
|
params?: {
|
package/shared/server.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import { createRegisterTools } from './tools.js';
|
|
3
3
|
import { registerResources } from './resources.js';
|
|
4
|
+
import { logWarning } from './logging.js';
|
|
5
|
+
import { ALL_BROWSER_PERMISSIONS } from './types.js';
|
|
4
6
|
/**
|
|
5
7
|
* Maximum allowed dimension for screenshots in pixels.
|
|
6
8
|
* Claude's API rejects images where either dimension exceeds 8000 pixels.
|
|
@@ -74,6 +76,19 @@ export class PlaywrightClient {
|
|
|
74
76
|
// which perform HTTPS inspection and may re-sign certificates)
|
|
75
77
|
ignoreHTTPSErrors: !!this.config.proxy,
|
|
76
78
|
});
|
|
79
|
+
// Grant browser permissions (defaults to all permissions if not specified)
|
|
80
|
+
const permissionsToGrant = this.config.permissions ?? [...ALL_BROWSER_PERMISSIONS];
|
|
81
|
+
if (permissionsToGrant.length > 0) {
|
|
82
|
+
try {
|
|
83
|
+
await this.context.grantPermissions(permissionsToGrant);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
// Some permissions may not be supported in all browsers/versions
|
|
87
|
+
// Log a warning but continue - the browser will still function
|
|
88
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
89
|
+
logWarning('permissions', `Failed to grant some permissions: ${message}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
77
92
|
this.page = await this.context.newPage();
|
|
78
93
|
// Apply timeout configuration to Playwright
|
|
79
94
|
this.page.setDefaultTimeout(this.config.timeout);
|
|
@@ -172,6 +187,7 @@ export class PlaywrightClient {
|
|
|
172
187
|
currentUrl: this.page.url(),
|
|
173
188
|
title: await this.page.title(),
|
|
174
189
|
isOpen: true,
|
|
190
|
+
permissions: this.config.permissions ?? [...ALL_BROWSER_PERMISSIONS],
|
|
175
191
|
};
|
|
176
192
|
}
|
|
177
193
|
catch {
|
|
@@ -191,11 +207,11 @@ export class PlaywrightClient {
|
|
|
191
207
|
return this.config;
|
|
192
208
|
}
|
|
193
209
|
}
|
|
194
|
-
export function createMCPServer(
|
|
210
|
+
export function createMCPServer(options) {
|
|
195
211
|
const stealthMode = process.env.STEALTH_MODE === 'true';
|
|
196
212
|
const server = new Server({
|
|
197
213
|
name: 'playwright-stealth-mcp-server',
|
|
198
|
-
version: '0.0.
|
|
214
|
+
version: '0.0.7',
|
|
199
215
|
}, {
|
|
200
216
|
capabilities: {
|
|
201
217
|
tools: {},
|
|
@@ -221,10 +237,11 @@ export function createMCPServer(proxyConfig) {
|
|
|
221
237
|
headless,
|
|
222
238
|
timeout,
|
|
223
239
|
navigationTimeout,
|
|
224
|
-
proxy:
|
|
240
|
+
proxy: options?.proxy,
|
|
225
241
|
stealthUserAgent,
|
|
226
242
|
stealthMaskLinux,
|
|
227
243
|
stealthLocale,
|
|
244
|
+
permissions: options?.permissions,
|
|
228
245
|
});
|
|
229
246
|
return activeClient;
|
|
230
247
|
});
|
package/shared/types.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for Playwright Stealth MCP server
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* All permissions that Playwright supports granting to browser contexts.
|
|
6
|
+
* Note: Not all permissions work in all browsers or browser versions.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ALL_BROWSER_PERMISSIONS: readonly ["accelerometer", "ambient-light-sensor", "background-sync", "camera", "clipboard-read", "clipboard-write", "geolocation", "gyroscope", "local-fonts", "magnetometer", "microphone", "midi", "midi-sysex", "notifications", "payment-handler", "storage-access"];
|
|
9
|
+
export type BrowserPermission = (typeof ALL_BROWSER_PERMISSIONS)[number];
|
|
4
10
|
/**
|
|
5
11
|
* Proxy configuration for browser connections
|
|
6
12
|
* Compatible with BrightData Residential Proxies and other HTTP/HTTPS proxies
|
|
@@ -28,6 +34,11 @@ export interface PlaywrightConfig {
|
|
|
28
34
|
stealthMaskLinux?: boolean;
|
|
29
35
|
/** Custom locale for stealth mode (default: 'en-US,en') */
|
|
30
36
|
stealthLocale?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Browser permissions to grant. Defaults to ALL_BROWSER_PERMISSIONS if not specified.
|
|
39
|
+
* Use BROWSER_PERMISSIONS env var to constrain permissions (comma-separated list).
|
|
40
|
+
*/
|
|
41
|
+
permissions?: BrowserPermission[];
|
|
31
42
|
}
|
|
32
43
|
export interface ExecuteResult {
|
|
33
44
|
success: boolean;
|
|
@@ -45,5 +56,7 @@ export interface BrowserState {
|
|
|
45
56
|
headless?: boolean;
|
|
46
57
|
/** Whether proxy is enabled for browser connections */
|
|
47
58
|
proxyEnabled?: boolean;
|
|
59
|
+
/** Permissions granted to the browser context */
|
|
60
|
+
permissions?: BrowserPermission[];
|
|
48
61
|
}
|
|
49
62
|
//# sourceMappingURL=types.d.ts.map
|
package/shared/types.js
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for Playwright Stealth MCP server
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* All permissions that Playwright supports granting to browser contexts.
|
|
6
|
+
* Note: Not all permissions work in all browsers or browser versions.
|
|
7
|
+
*/
|
|
8
|
+
export const ALL_BROWSER_PERMISSIONS = [
|
|
9
|
+
'accelerometer',
|
|
10
|
+
'ambient-light-sensor',
|
|
11
|
+
'background-sync',
|
|
12
|
+
'camera',
|
|
13
|
+
'clipboard-read',
|
|
14
|
+
'clipboard-write',
|
|
15
|
+
'geolocation',
|
|
16
|
+
'gyroscope',
|
|
17
|
+
'local-fonts',
|
|
18
|
+
'magnetometer',
|
|
19
|
+
'microphone',
|
|
20
|
+
'midi',
|
|
21
|
+
'midi-sysex',
|
|
22
|
+
'notifications',
|
|
23
|
+
'payment-handler',
|
|
24
|
+
'storage-access',
|
|
25
|
+
];
|