playwright-stealth-mcp-server 0.0.3 → 0.0.4

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 CHANGED
@@ -60,6 +60,25 @@ Add to your Claude Desktop config file:
60
60
  }
61
61
  ```
62
62
 
63
+ **With Proxy** (e.g., BrightData Residential Proxy):
64
+
65
+ ```json
66
+ {
67
+ "mcpServers": {
68
+ "playwright-proxy": {
69
+ "command": "npx",
70
+ "args": ["-y", "playwright-stealth-mcp-server"],
71
+ "env": {
72
+ "STEALTH_MODE": "true",
73
+ "PROXY_URL": "http://brd.superproxy.io:22225",
74
+ "PROXY_USERNAME": "brd-customer-XXXXX-zone-residential",
75
+ "PROXY_PASSWORD": "your-password"
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
63
82
  ### Environment Variables
64
83
 
65
84
  | Variable | Description | Default |
@@ -69,6 +88,10 @@ Add to your Claude Desktop config file:
69
88
  | `TIMEOUT` | Default timeout for Playwright actions (click, fill, etc.) in milliseconds | `30000` |
70
89
  | `NAVIGATION_TIMEOUT` | Default timeout for page navigation (goto, reload, etc.) in milliseconds | `60000` |
71
90
  | `SCREENSHOT_STORAGE_PATH` | Directory for storing screenshots | `/tmp/playwright-screenshots` |
91
+ | `PROXY_URL` | Proxy server URL (e.g., `http://proxy.example.com:8080`) | - |
92
+ | `PROXY_USERNAME` | Proxy authentication username | - |
93
+ | `PROXY_PASSWORD` | Proxy authentication password | - |
94
+ | `PROXY_BYPASS` | Comma-separated list of hosts to bypass proxy | - |
72
95
 
73
96
  ## Available Tools
74
97
 
@@ -201,6 +224,24 @@ Stealth mode includes:
201
224
  - Plugin/mime type spoofing
202
225
  - Navigator property patching
203
226
 
227
+ ## When to Use Proxy
228
+
229
+ Configure proxy settings when:
230
+
231
+ - Scraping sites that rate-limit by IP address
232
+ - Accessing geo-restricted content
233
+ - Avoiding IP-based blocks or bans
234
+ - Rotating IPs for large-scale data collection
235
+
236
+ The server supports HTTP/HTTPS proxies with optional authentication, making it compatible with:
237
+
238
+ - **BrightData** (Residential, Datacenter, ISP proxies)
239
+ - **Oxylabs**, **Smartproxy**, and other residential proxy providers
240
+ - Self-hosted proxy servers
241
+ - Corporate HTTP proxies
242
+
243
+ **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.
244
+
204
245
  ## Development
205
246
 
206
247
  ```bash
package/build/index.js CHANGED
@@ -1,7 +1,66 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { createMCPServer } from '../shared/index.js';
4
- import { logServerStart, logError, logWarning } from '../shared/logging.js';
4
+ import { logServerStart, logError, logWarning, logInfo } from '../shared/logging.js';
5
+ // =============================================================================
6
+ // PROXY CONFIGURATION
7
+ // =============================================================================
8
+ /**
9
+ * Build proxy configuration from environment variables
10
+ * @returns ProxyConfig if proxy is configured, undefined otherwise
11
+ */
12
+ function buildProxyConfig() {
13
+ const proxyUrl = process.env.PROXY_URL;
14
+ if (!proxyUrl) {
15
+ return undefined;
16
+ }
17
+ return {
18
+ server: proxyUrl,
19
+ username: process.env.PROXY_USERNAME,
20
+ password: process.env.PROXY_PASSWORD,
21
+ bypass: process.env.PROXY_BYPASS,
22
+ };
23
+ }
24
+ /**
25
+ * Health check the proxy connection by making a test request
26
+ * @param proxy The proxy configuration to test
27
+ */
28
+ async function healthCheckProxy(proxy) {
29
+ logInfo('proxy', 'Performing proxy health check...');
30
+ // Use Node.js built-in fetch with proxy agent
31
+ // We'll use a simple approach: launch a quick browser with the proxy to test
32
+ const { chromium } = await import('playwright');
33
+ let browser;
34
+ try {
35
+ browser = await chromium.launch({
36
+ headless: true,
37
+ proxy: {
38
+ server: proxy.server,
39
+ username: proxy.username,
40
+ password: proxy.password,
41
+ bypass: proxy.bypass,
42
+ },
43
+ });
44
+ // Ignore HTTPS errors for residential proxies that perform HTTPS inspection
45
+ const context = await browser.newContext({ ignoreHTTPSErrors: true });
46
+ const page = await context.newPage();
47
+ // Try to fetch a reliable endpoint to verify proxy works
48
+ const response = await page.goto('https://httpbin.org/ip', {
49
+ timeout: 30000,
50
+ waitUntil: 'domcontentloaded',
51
+ });
52
+ if (!response || !response.ok()) {
53
+ throw new Error(`Proxy health check failed: HTTP ${response?.status() ?? 'unknown'}`);
54
+ }
55
+ const body = await page.textContent('body');
56
+ logInfo('proxy', `Proxy health check passed. Response: ${body?.trim()}`);
57
+ }
58
+ finally {
59
+ if (browser) {
60
+ await browser.close();
61
+ }
62
+ }
63
+ }
5
64
  // =============================================================================
6
65
  // ENVIRONMENT VALIDATION
7
66
  // =============================================================================
@@ -22,11 +81,32 @@ function validateEnvironment() {
22
81
  description: 'Default execution timeout in milliseconds',
23
82
  defaultValue: '30000',
24
83
  },
84
+ {
85
+ name: 'PROXY_URL',
86
+ description: 'Proxy server URL (e.g., http://proxy.example.com:8080)',
87
+ defaultValue: undefined,
88
+ },
89
+ {
90
+ name: 'PROXY_USERNAME',
91
+ description: 'Proxy authentication username',
92
+ defaultValue: undefined,
93
+ },
94
+ {
95
+ name: 'PROXY_PASSWORD',
96
+ description: 'Proxy authentication password',
97
+ defaultValue: undefined,
98
+ },
99
+ {
100
+ name: 'PROXY_BYPASS',
101
+ description: 'Comma-separated list of hosts to bypass proxy',
102
+ defaultValue: undefined,
103
+ },
25
104
  ];
26
105
  // Log configuration
27
106
  const stealthMode = process.env.STEALTH_MODE === 'true';
28
107
  const headless = process.env.HEADLESS !== 'false';
29
108
  const timeout = process.env.TIMEOUT || '30000';
109
+ const proxyUrl = process.env.PROXY_URL;
30
110
  if (stealthMode) {
31
111
  logWarning('config', 'Stealth mode enabled - using anti-detection measures');
32
112
  }
@@ -36,13 +116,29 @@ function validateEnvironment() {
36
116
  if (process.env.TIMEOUT) {
37
117
  logWarning('config', `Custom timeout configured: ${timeout}ms`);
38
118
  }
119
+ if (proxyUrl) {
120
+ // Sanitize proxy URL to prevent credential leaks (in case URL contains embedded credentials)
121
+ const sanitizedUrl = proxyUrl.replace(/\/\/[^@]+@/, '//*****@');
122
+ logInfo('config', `Proxy configured: ${sanitizedUrl}`);
123
+ if (process.env.PROXY_USERNAME) {
124
+ logInfo('config', 'Proxy authentication enabled');
125
+ }
126
+ }
39
127
  // Show optional configuration if DEBUG is set
40
128
  if (process.env.DEBUG) {
41
129
  console.error('\nOptional environment variables:');
42
130
  optional.forEach(({ name, description, defaultValue }) => {
43
- const current = process.env[name] || defaultValue;
44
- console.error(` - ${name}: ${description}`);
45
- console.error(` Current: ${current}`);
131
+ // Don't log proxy password
132
+ if (name === 'PROXY_PASSWORD') {
133
+ const hasPassword = !!process.env[name];
134
+ console.error(` - ${name}: ${description}`);
135
+ console.error(` Current: ${hasPassword ? '***' : '(not set)'}`);
136
+ }
137
+ else {
138
+ const current = process.env[name] || defaultValue;
139
+ console.error(` - ${name}: ${description}`);
140
+ console.error(` Current: ${current || '(not set)'}`);
141
+ }
46
142
  });
47
143
  console.error('');
48
144
  }
@@ -53,11 +149,24 @@ function validateEnvironment() {
53
149
  async function main() {
54
150
  // Step 1: Validate environment variables
55
151
  validateEnvironment();
56
- // Step 2: Create server using factory
57
- const { server, registerHandlers, cleanup } = createMCPServer();
58
- // Step 3: Register all handlers (tools)
152
+ // Step 2: Build proxy configuration if provided
153
+ const proxyConfig = buildProxyConfig();
154
+ // Step 3: If proxy is configured, perform health check
155
+ if (proxyConfig) {
156
+ try {
157
+ await healthCheckProxy(proxyConfig);
158
+ }
159
+ catch (error) {
160
+ logError('proxy', `Proxy health check failed: ${error instanceof Error ? error.message : String(error)}`);
161
+ logError('proxy', 'Please verify your proxy configuration and try again.');
162
+ process.exit(1);
163
+ }
164
+ }
165
+ // Step 4: Create server using factory, passing proxy config
166
+ const { server, registerHandlers, cleanup } = createMCPServer(proxyConfig);
167
+ // Step 5: Register all handlers (tools)
59
168
  await registerHandlers(server);
60
- // Step 4: Set up graceful shutdown
169
+ // Step 6: Set up graceful shutdown
61
170
  const handleShutdown = async () => {
62
171
  logWarning('shutdown', 'Received shutdown signal, closing browser...');
63
172
  await cleanup();
@@ -65,11 +174,12 @@ async function main() {
65
174
  };
66
175
  process.on('SIGINT', handleShutdown);
67
176
  process.on('SIGTERM', handleShutdown);
68
- // Step 5: Start server with stdio transport
177
+ // Step 7: Start server with stdio transport
69
178
  const transport = new StdioServerTransport();
70
179
  await server.connect(transport);
71
180
  const stealthMode = process.env.STEALTH_MODE === 'true';
72
- logServerStart(`Playwright${stealthMode ? ' (Stealth)' : ''}`);
181
+ const proxyEnabled = !!proxyConfig;
182
+ logServerStart(`Playwright${stealthMode ? ' (Stealth)' : ''}${proxyEnabled ? ' (Proxy)' : ''}`);
73
183
  }
74
184
  // Run the server
75
185
  main().catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright-stealth-mcp-server",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Local implementation of Playwright Stealth MCP server",
5
5
  "mcpName": "com.pulsemcp.servers/playwright-stealth",
6
6
  "main": "build/index.js",
@@ -13,6 +13,10 @@ export declare function logError(context: string, error: unknown): void;
13
13
  * Log a warning
14
14
  */
15
15
  export declare function logWarning(context: string, message: string): void;
16
+ /**
17
+ * Log an informational message
18
+ */
19
+ export declare function logInfo(context: string, message: string): void;
16
20
  /**
17
21
  * Log debug information (only in development)
18
22
  */
package/shared/logging.js CHANGED
@@ -24,6 +24,12 @@ export function logError(context, error) {
24
24
  export function logWarning(context, message) {
25
25
  console.error(`[WARN] ${context}: ${message}`);
26
26
  }
27
+ /**
28
+ * Log an informational message
29
+ */
30
+ export function logInfo(context, message) {
31
+ console.error(`[INFO] ${context}: ${message}`);
32
+ }
27
33
  /**
28
34
  * Log debug information (only in development)
29
35
  */
@@ -1,5 +1,5 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import type { ExecuteResult, BrowserState, PlaywrightConfig } from './types.js';
2
+ import type { ExecuteResult, BrowserState, PlaywrightConfig, ProxyConfig } from './types.js';
3
3
  /**
4
4
  * Playwright client interface
5
5
  * Defines all methods for browser automation
@@ -52,7 +52,7 @@ export declare class PlaywrightClient implements IPlaywrightClient {
52
52
  getConfig(): PlaywrightConfig;
53
53
  }
54
54
  export type ClientFactory = () => IPlaywrightClient;
55
- export declare function createMCPServer(): {
55
+ export declare function createMCPServer(proxyConfig?: ProxyConfig): {
56
56
  server: Server<{
57
57
  method: string;
58
58
  params?: {
package/shared/server.js CHANGED
@@ -17,6 +17,15 @@ export class PlaywrightClient {
17
17
  if (this.page) {
18
18
  return this.page;
19
19
  }
20
+ // Build proxy options for Playwright if configured
21
+ const proxyOptions = this.config.proxy
22
+ ? {
23
+ server: this.config.proxy.server,
24
+ username: this.config.proxy.username,
25
+ password: this.config.proxy.password,
26
+ bypass: this.config.proxy.bypass,
27
+ }
28
+ : undefined;
20
29
  if (this.config.stealthMode) {
21
30
  // Use playwright-extra with stealth plugin
22
31
  const { chromium } = await import('playwright-extra');
@@ -29,6 +38,7 @@ export class PlaywrightClient {
29
38
  '--disable-dev-shm-usage',
30
39
  '--no-sandbox',
31
40
  ],
41
+ proxy: proxyOptions,
32
42
  });
33
43
  }
34
44
  else {
@@ -36,6 +46,7 @@ export class PlaywrightClient {
36
46
  const { chromium } = await import('playwright');
37
47
  this.browser = await chromium.launch({
38
48
  headless: this.config.headless,
49
+ proxy: proxyOptions,
39
50
  });
40
51
  }
41
52
  this.context = await this.browser.newContext({
@@ -43,6 +54,9 @@ export class PlaywrightClient {
43
54
  userAgent: this.config.stealthMode
44
55
  ? 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
45
56
  : undefined,
57
+ // Ignore HTTPS errors when using proxy (required for residential proxies like BrightData
58
+ // which perform HTTPS inspection and may re-sign certificates)
59
+ ignoreHTTPSErrors: !!this.config.proxy,
46
60
  });
47
61
  this.page = await this.context.newPage();
48
62
  // Apply timeout configuration to Playwright
@@ -125,11 +139,11 @@ export class PlaywrightClient {
125
139
  return this.config;
126
140
  }
127
141
  }
128
- export function createMCPServer() {
142
+ export function createMCPServer(proxyConfig) {
129
143
  const stealthMode = process.env.STEALTH_MODE === 'true';
130
144
  const server = new Server({
131
145
  name: 'playwright-stealth-mcp-server',
132
- version: '0.0.3',
146
+ version: '0.0.4',
133
147
  }, {
134
148
  capabilities: {
135
149
  tools: {},
@@ -150,6 +164,7 @@ export function createMCPServer() {
150
164
  headless,
151
165
  timeout,
152
166
  navigationTimeout,
167
+ proxy: proxyConfig,
153
168
  });
154
169
  return activeClient;
155
170
  });
package/shared/tools.js CHANGED
@@ -264,6 +264,7 @@ export function createRegisterTools(clientFactory) {
264
264
  ...state,
265
265
  stealthMode: config.stealthMode,
266
266
  headless: config.headless,
267
+ proxyEnabled: !!config.proxy,
267
268
  }, null, 2),
268
269
  },
269
270
  ],
package/shared/types.d.ts CHANGED
@@ -1,11 +1,27 @@
1
1
  /**
2
2
  * Types for Playwright Stealth MCP server
3
3
  */
4
+ /**
5
+ * Proxy configuration for browser connections
6
+ * Compatible with BrightData Residential Proxies and other HTTP/HTTPS proxies
7
+ */
8
+ export interface ProxyConfig {
9
+ /** Proxy server URL (e.g., "http://proxy.example.com:8080") */
10
+ server: string;
11
+ /** Optional username for proxy authentication */
12
+ username?: string;
13
+ /** Optional password for proxy authentication */
14
+ password?: string;
15
+ /** Optional comma-separated list of hosts to bypass proxy */
16
+ bypass?: string;
17
+ }
4
18
  export interface PlaywrightConfig {
5
19
  stealthMode: boolean;
6
20
  headless: boolean;
7
21
  timeout: number;
8
22
  navigationTimeout: number;
23
+ /** Optional proxy configuration */
24
+ proxy?: ProxyConfig;
9
25
  }
10
26
  export interface ExecuteResult {
11
27
  success: boolean;
@@ -17,5 +33,11 @@ export interface BrowserState {
17
33
  currentUrl?: string;
18
34
  title?: string;
19
35
  isOpen: boolean;
36
+ /** Whether stealth mode is enabled */
37
+ stealthMode?: boolean;
38
+ /** Whether browser is running in headless mode */
39
+ headless?: boolean;
40
+ /** Whether proxy is enabled for browser connections */
41
+ proxyEnabled?: boolean;
20
42
  }
21
43
  //# sourceMappingURL=types.d.ts.map