real-browser-mcp-server 1.1.1 → 1.1.2

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/lib/esm/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
- import playwright from "playwright-ghost/patchright";
2
- import recommended from "playwright-ghost/plugins/recommended";
3
- const { chromium } = playwright;
1
+ import { chromium } from "patchright";
2
+ import { createCursor } from "ghost-cursor-patchright";
4
3
  import { PlaywrightBlocker } from "@ghostery/adblocker-playwright";
5
4
  import { pageController } from "./module/pageController.mjs";
6
5
  import { fileURLToPath } from 'url';
7
6
  import * as fs from 'fs';
8
7
  import * as path from 'path';
8
+ import { execSync } from 'child_process';
9
9
 
10
10
  const __filename = fileURLToPath(import.meta.url);
11
11
  const __dirname = path.dirname(__filename);
@@ -71,13 +71,28 @@ function loadEnvFile() {
71
71
  loadEnvFile();
72
72
 
73
73
  function getDefaultHeadless() {
74
- const envHeadless = (process.env.HEADLESS || '').toLowerCase();
75
- return envHeadless === 'true';
74
+ const envHeadless = process.env.HEADLESS;
75
+ if (envHeadless !== undefined && envHeadless !== null && envHeadless !== '') {
76
+ const value = envHeadless.toLowerCase().trim();
77
+ return value === 'true' || value === '1' || value === 'yes';
78
+ }
79
+ // Auto-detect CI environments
80
+ if (process.env.CI || process.env.GITHUB_ACTIONS || process.env.TRAVIS || process.env.CIRCLECI) {
81
+ return true;
82
+ }
83
+ // Auto-detect headless Linux environments without X11 or Wayland
84
+ if (process.platform === 'linux') {
85
+ const hasDisplay = process.env.DISPLAY || process.env.WAYLAND_DISPLAY;
86
+ if (!hasDisplay) {
87
+ return true;
88
+ }
89
+ }
90
+ return false;
76
91
  }
77
92
 
78
- function applyPuppeteerShims(browser, page) {
79
- if (page._shimsApplied) return page;
80
- page._shimsApplied = true;
93
+ function setupRealPage(browser, page) {
94
+ if (page._setupApplied) return page;
95
+ page._setupApplied = true;
81
96
 
82
97
  // Enable ad blocker
83
98
  if (adBlockerInstance) {
@@ -90,93 +105,164 @@ function applyPuppeteerShims(browser, page) {
90
105
  });
91
106
  }
92
107
 
93
- // Cookie shims (Puppeteer Playwright context)
94
- if (!page.cookies) {
95
- page.cookies = async (urls) => {
96
- return await page.context().cookies(urls ? (Array.isArray(urls) ? urls : [urls]) : []);
97
- };
98
- }
99
-
100
- if (!page.setCookie) {
101
- page.setCookie = async (...cookies) => {
102
- const formatted = cookies.map(c => ({
103
- name: c.name,
104
- value: c.value,
105
- domain: c.domain || new URL(page.url()).hostname,
106
- path: c.path || '/',
107
- expires: c.expires || undefined,
108
- httpOnly: c.httpOnly,
109
- secure: c.secure,
110
- sameSite: c.sameSite
111
- }));
112
- await page.context().addCookies(formatted);
113
- };
114
- }
108
+ // Human-like smooth scrolling with 60FPS Cubic Ease-Out physics
109
+ page.realScroll = async (deltaY, duration = 600) => {
110
+ try {
111
+ const stepDelay = 15; // ~60 FPS
112
+ const steps = Math.max(10, Math.floor(duration / stepDelay));
113
+ const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
114
+ let currentScroll = 0;
115
+ for (let i = 1; i <= steps; i++) {
116
+ const t = i / steps;
117
+ const targetScroll = deltaY * easeOutCubic(t);
118
+ const diff = targetScroll - currentScroll;
119
+ await page.mouse.wheel(0, diff);
120
+ currentScroll = targetScroll;
121
+ await new Promise(r => setTimeout(r, stepDelay));
122
+ }
123
+ } catch (e) {
124
+ // Fallback to native window scroll in case of wheel errors
125
+ try {
126
+ await page.evaluate((y) => window.scrollBy({ top: y, behavior: 'smooth' }), deltaY);
127
+ } catch (_) {}
128
+ }
129
+ };
115
130
 
116
- if (!page.deleteCookie) {
117
- page.deleteCookie = async (...cookies) => {
118
- await page.context().clearCookies();
131
+ // Ghost Cursor integration - Bézier curve human-like mouse movement
132
+ try {
133
+ const cursor = createCursor(page);
134
+ page.realCursor = {
135
+ move: async (selector, options = {}) => {
136
+ try {
137
+ await cursor.actions.move(selector, options);
138
+ } catch (e) {
139
+ // Fallback to native hover if ghost-cursor fails
140
+ try { await page.hover(selector); } catch (_) {}
141
+ }
142
+ }
119
143
  };
120
- }
121
-
122
- // Navigation shims
123
- if (!page.waitForNetworkIdle) {
124
- page.waitForNetworkIdle = async (options = {}) => {
144
+ page.realClick = async (selector, options = {}) => {
125
145
  try {
126
- await page.waitForLoadState('networkidle', { timeout: options.timeout });
146
+ await cursor.actions.click({ target: selector, ...options });
127
147
  } catch (e) {
128
- // Ignore networkidle timeouts if they are non-fatal
148
+ // Fallback to native click if ghost-cursor fails
149
+ await page.click(selector, options);
129
150
  }
130
151
  };
152
+ } catch (e) {
153
+ // Fallback if ghost-cursor-patchright fails to initialize
154
+ if (!page.realClick) {
155
+ page.realClick = async (selector, options) => {
156
+ await page.click(selector, options);
157
+ };
158
+ }
159
+ if (!page.realCursor) {
160
+ page.realCursor = {
161
+ move: async (selector) => {
162
+ try { await page.hover(selector); } catch (_) {}
163
+ }
164
+ };
165
+ }
131
166
  }
132
167
 
133
- if (!page.evaluateOnNewDocument) {
134
- page.evaluateOnNewDocument = async (fn, ...args) => {
135
- return await page.addInitScript(fn, ...args);
136
- };
168
+ return page;
169
+
170
+ }
171
+
172
+ function getBraveExecutablePath() {
173
+ if (process.env.BRAVE_PATH && fs.existsSync(process.env.BRAVE_PATH)) {
174
+ return process.env.BRAVE_PATH;
137
175
  }
138
176
 
139
- // Ghost Cursor integration via playwright-ghost auto-hooking
140
- page.realCursor = {
141
- move: async (selector, options = {}) => {
177
+ const platform = process.platform;
178
+
179
+ // Try automatic scanning via CLI / registry query
180
+ if (platform === 'win32') {
181
+ const regQueries = [
182
+ 'reg query "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\brave.exe" /ve',
183
+ 'reg query "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\brave.exe" /ve',
184
+ 'reg query "HKEY_LOCAL_MACHINE\\SOFTWARE\\Clients\\StartMenuInternet\\Brave-Browser\\shell\\open\\command" /ve'
185
+ ];
186
+
187
+ for (const cmd of regQueries) {
142
188
  try {
143
- await page.hover(selector, options);
144
- } catch (e) {
145
- // Silently fallback if element is not found
146
- }
189
+ const output = execSync(cmd, { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
190
+ const match = output.match(/REG_SZ\s+(.*)/);
191
+ if (match && match[1]) {
192
+ let p = match[1].trim().replace(/^"|"$/g, '');
193
+ if (!p.toLowerCase().endsWith('.exe')) {
194
+ const exeIndex = p.toLowerCase().indexOf('.exe');
195
+ if (exeIndex !== -1) {
196
+ p = p.substring(0, exeIndex + 4).replace(/^"|"$/g, '');
197
+ }
198
+ }
199
+ if (fs.existsSync(p)) return p;
200
+ }
201
+ } catch (e) {}
147
202
  }
148
- };
149
- page.realClick = async (selector, options = {}) => {
150
- await page.click(selector, options);
151
- };
152
203
 
153
- // Mouse wheel shim (Puppeteer object → Playwright positional args)
154
- if (page.mouse && page.mouse.wheel) {
155
- const originalWheel = page.mouse.wheel.bind(page.mouse);
156
- page.mouse.wheel = async (x, y) => {
157
- if (typeof x === 'object' && x !== null) {
158
- const deltaX = x.deltaX || 0;
159
- const deltaY = x.deltaY || 0;
160
- return await originalWheel(deltaX, deltaY);
204
+ try {
205
+ const output = execSync('where brave.exe', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim().split('\r\n')[0];
206
+ if (output && fs.existsSync(output)) return output;
207
+ } catch (e) {}
208
+ } else if (platform === 'darwin') {
209
+ try {
210
+ const output = execSync('mdfind "kMDItemCFBundleIdentifier == \'com.brave.Browser\'"', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim().split('\n')[0];
211
+ if (output) {
212
+ const p = path.join(output, 'Contents', 'MacOS', 'Brave Browser');
213
+ if (fs.existsSync(p)) return p;
161
214
  }
162
- return await originalWheel(x, y);
163
- };
215
+ } catch (e) {}
216
+ } else {
217
+ try {
218
+ const output = execSync('which brave-browser || which brave', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
219
+ if (output && fs.existsSync(output)) return output;
220
+ } catch (e) {}
164
221
  }
165
222
 
166
- // Browser shims
167
- if (!browser.process) {
168
- browser.process = () => ({
169
- pid: browser._childProcess?.pid || 1337
170
- });
223
+ // Fallback to hardcoded common paths
224
+ let paths = [];
225
+ if (platform === 'win32') {
226
+ paths = [
227
+ path.join(process.env.PROGRAMFILES || 'C:\\Program Files', 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
228
+ path.join(process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)', 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
229
+ path.join(process.env.LOCALAPPDATA || '', 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe')
230
+ ].filter(p => p);
231
+ } else if (platform === 'darwin') {
232
+ paths = [
233
+ '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser'
234
+ ];
235
+ } else {
236
+ paths = [
237
+ '/usr/bin/brave-browser',
238
+ '/usr/bin/brave',
239
+ '/usr/bin/brave-browser-stable',
240
+ '/usr/bin/brave-browser-beta',
241
+ '/usr/bin/brave-browser-nightly',
242
+ '/usr/local/bin/brave-browser',
243
+ '/usr/local/bin/brave'
244
+ ];
171
245
  }
172
246
 
173
- if (!browser.pages) {
174
- browser.pages = async () => {
175
- return browser.contexts().flatMap(c => c.pages());
176
- };
247
+ for (const p of paths) {
248
+ if (p && fs.existsSync(p)) {
249
+ return p;
250
+ }
177
251
  }
178
252
 
179
- return page;
253
+ return null;
254
+ }
255
+
256
+ async function applyUserAgentOverride(page, userAgent, userAgentMetadata) {
257
+ try {
258
+ const client = await page.context().newCDPSession(page);
259
+ await client.send('Emulation.setUserAgentOverride', {
260
+ userAgent: userAgent,
261
+ userAgentMetadata: userAgentMetadata
262
+ });
263
+ } catch (e) {
264
+ // Ignore errors
265
+ }
180
266
  }
181
267
 
182
268
  export async function connect({
@@ -184,6 +270,7 @@ export async function connect({
184
270
  headless = getDefaultHeadless(),
185
271
  proxy = {},
186
272
  turnstile = false,
273
+ executablePath = undefined,
187
274
  } = {}) {
188
275
  let playwrightProxy = undefined;
189
276
  if (proxy && proxy.host && proxy.port) {
@@ -196,18 +283,81 @@ export async function connect({
196
283
  }
197
284
  }
198
285
 
286
+ // 1. Launch a temporary browser to retrieve the native user agent and properties
287
+ const tempBrowser = await chromium.launch({
288
+ headless: true,
289
+ args: ['--no-sandbox', '--disable-setuid-sandbox'],
290
+ ...(executablePath ? { executablePath } : {}),
291
+ });
292
+ const tempContext = await tempBrowser.newContext();
293
+ const tempPage = await tempContext.newPage();
294
+ let nativeUa = '';
295
+ let isBrave = false;
296
+ try {
297
+ nativeUa = await tempPage.evaluate(() => navigator.userAgent);
298
+ isBrave = await tempPage.evaluate(() => typeof navigator.brave !== 'undefined');
299
+ } catch (e) {
300
+ nativeUa = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36';
301
+ isBrave = executablePath && executablePath.toLowerCase().includes('brave');
302
+ }
303
+ await tempBrowser.close();
304
+
305
+ let modifiedUa = nativeUa.replace(/HeadlessChrome\//g, 'Chrome/');
306
+ const chromeVersionMatch = modifiedUa.match(/Chrome\/([\d.]+)/);
307
+ const chromeVersion = chromeVersionMatch ? chromeVersionMatch[1] : '148.0.0.0';
308
+ const majorVersion = chromeVersion.split('.')[0];
309
+
310
+ const brands = [
311
+ { brand: 'Chromium', version: majorVersion },
312
+ { brand: 'Not/A)Brand', version: '99' }
313
+ ];
314
+ if (isBrave) {
315
+ brands.unshift({ brand: 'Brave', version: majorVersion });
316
+ } else {
317
+ brands.unshift({ brand: 'Google Chrome', version: majorVersion });
318
+ }
319
+
320
+ let platformName = 'Windows';
321
+ if (nativeUa.includes('Macintosh') || nativeUa.includes('Mac OS X')) {
322
+ platformName = 'macOS';
323
+ } else if (nativeUa.includes('Linux')) {
324
+ platformName = 'Linux';
325
+ }
326
+
327
+ const userAgentMetadata = {
328
+ brands: brands,
329
+ mobile: false,
330
+ platform: platformName,
331
+ platformVersion: platformName === 'macOS' ? '14.0.0' : platformName === 'Linux' ? '6.0.0' : '10.0.0',
332
+ architecture: 'x86',
333
+ model: '',
334
+ bitness: '64',
335
+ wow64: false
336
+ };
337
+
199
338
  const chromiumArgs = [
339
+ `--user-agent=${modifiedUa}`,
200
340
  '--disable-blink-features=AutomationControlled',
201
341
  '--no-sandbox',
202
342
  '--disable-setuid-sandbox',
203
343
  ...args
204
344
  ];
205
345
 
346
+ // If headless is true, we run with headless: false but pass '--headless=new' to args.
347
+ // This triggers Chromium's modern undetected headless mode instead of Playwright's default old headless shell.
348
+ let launchHeadless = headless;
349
+ if (headless === true) {
350
+ launchHeadless = false;
351
+ if (!chromiumArgs.includes('--headless=new')) {
352
+ chromiumArgs.push('--headless=new');
353
+ }
354
+ }
355
+
206
356
  const browser = await chromium.launch({
207
- headless,
357
+ headless: launchHeadless,
208
358
  args: chromiumArgs,
209
359
  proxy: playwrightProxy,
210
- plugins: [recommended()]
360
+ ...(executablePath ? { executablePath } : {}),
211
361
  });
212
362
 
213
363
  // Ensure ad blocker is ready
@@ -219,7 +369,9 @@ export async function connect({
219
369
 
220
370
  let page = await context.newPage();
221
371
 
222
- applyPuppeteerShims(browser, page);
372
+ await applyUserAgentOverride(page, modifiedUa, userAgentMetadata);
373
+
374
+ setupRealPage(browser, page);
223
375
 
224
376
  page = await pageController({
225
377
  browser,
@@ -229,7 +381,8 @@ export async function connect({
229
381
  });
230
382
 
231
383
  context.on('page', async (newPage) => {
232
- applyPuppeteerShims(browser, newPage);
384
+ await applyUserAgentOverride(newPage, modifiedUa, userAgentMetadata);
385
+ setupRealPage(browser, newPage);
233
386
  await pageController({
234
387
  browser,
235
388
  page: newPage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "real-browser-mcp-server",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "MCP Server for Real Browser - Patchright (undetected Playwright fork) with Stealth Mode, Ad Blocker, and Turnstile Auto-Solver for undetectable web automation.",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/esm/index.mjs",
@@ -26,9 +26,8 @@
26
26
  "mcp:verbose": "node src/index.js mcp --verbose",
27
27
  "list": "node src/index.js --list",
28
28
  "test": "npm run cjs_test && npm run esm_test",
29
- "esm_test": "node ./test/esm/test.js",
30
- "cjs_test": "node ./test/cjs/test.js",
31
- "build": "echo 'No build step required for this package.'"
29
+ "esm_test": "node ./test/esm/test.mjs",
30
+ "cjs_test": "node ./test/cjs/test.js"
32
31
  },
33
32
  "keywords": [
34
33
  "mcp-server",
@@ -44,7 +43,7 @@
44
43
  "dependencies": {
45
44
  "@ghostery/adblocker-playwright": "latest",
46
45
  "@modelcontextprotocol/sdk": "latest",
47
- "patchright": "latest",
48
- "playwright-ghost": "latest"
46
+ "ghost-cursor-patchright": "^1.0.2",
47
+ "patchright": "latest"
49
48
  }
50
49
  }
package/src/ai/core.js CHANGED
@@ -90,7 +90,7 @@ class AICore {
90
90
  if (element) {
91
91
  if (humanLike) {
92
92
  try {
93
- const { createCursor } = require('ghost-cursor');
93
+ const { createCursor } = require('ghost-cursor-patchright');
94
94
  const cursor = createCursor(page);
95
95
  await cursor.click(selector);
96
96
  } catch {
package/src/index.js CHANGED
@@ -97,7 +97,14 @@ async function main() {
97
97
  process.exit(0);
98
98
  }
99
99
 
100
- console.error('🚀 Real Browser MCP Server starting...');
100
+ console.error('');
101
+ console.error(`${colors.bright}${colors.cyan}╔════════════════════════════════════════════════════════════╗${colors.reset}`);
102
+ console.error(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.bright}${colors.magenta}🦁 Real Browser - Unified Server${colors.reset} ${colors.cyan}║${colors.reset}`);
103
+ console.error(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.dim}MCP (AI Agents) Server running on STDIO${colors.reset} ${colors.cyan}║${colors.reset}`);
104
+ console.error(`${colors.bright}${colors.cyan}╚════════════════════════════════════════════════════════════╝${colors.reset}`);
105
+ console.error('');
106
+
107
+ console.error(`${colors.bright}${colors.blue}🚀 Starting MCP Server...${colors.reset}`);
101
108
 
102
109
  // Import and run MCP server
103
110
  require('./mcp/index.js');
@@ -68,13 +68,25 @@ function notifyProgress(toolName, status, message, data = {}) {
68
68
  function getHeadlessFromEnv() {
69
69
  const envHeadless = process.env.HEADLESS;
70
70
 
71
- if (envHeadless === undefined || envHeadless === null || envHeadless === '') {
72
- return false; // Default: GUI mode
71
+ if (envHeadless !== undefined && envHeadless !== null && envHeadless !== '') {
72
+ const value = envHeadless.toLowerCase().trim();
73
+ return value === 'true' || value === '1' || value === 'yes';
73
74
  }
74
75
 
75
- // Parse string to boolean
76
- const value = envHeadless.toLowerCase().trim();
77
- return value === 'true' || value === '1' || value === 'yes';
76
+ // Auto-detect CI environments
77
+ if (process.env.CI || process.env.GITHUB_ACTIONS || process.env.TRAVIS || process.env.CIRCLECI) {
78
+ return true;
79
+ }
80
+
81
+ // Auto-detect headless Linux environments without X11 or Wayland
82
+ if (process.platform === 'linux') {
83
+ const hasDisplay = process.env.DISPLAY || process.env.WAYLAND_DISPLAY;
84
+ if (!hasDisplay) {
85
+ return true;
86
+ }
87
+ }
88
+
89
+ return false;
78
90
  }
79
91
 
80
92
  /**
@@ -595,9 +607,9 @@ const handlers = {
595
607
  // ═══════════════════════════════════════════════════════════════
596
608
  // INJECTED SCRIPT - Silent Handling of Popups
597
609
  // Override window.confirm/alert to handle them inside the page context
598
- // Note: Using evaluateOnNewDocument (Puppeteer) instead of addInitScript (Playwright)
610
+ // Note: Using addInitScript (Playwright) to intercept popups early
599
611
  // ═══════════════════════════════════════════════════════════════
600
- await pageInstance.evaluateOnNewDocument(() => {
612
+ await pageInstance.addInitScript(() => {
601
613
  window.originalConfirm = window.confirm;
602
614
  window.originalAlert = window.alert;
603
615
 
@@ -1239,7 +1251,7 @@ const handlers = {
1239
1251
  notifyProgress('click', 'progress', 'Used force click (JS)');
1240
1252
  } else if (humanLike) {
1241
1253
  try {
1242
- const { createCursor } = require('ghost-cursor');
1254
+ const { createCursor } = require('ghost-cursor-patchright');
1243
1255
  const cursor = createCursor(page);
1244
1256
 
1245
1257
  if (context !== page) {
@@ -2002,7 +2014,7 @@ const handlers = {
2002
2014
 
2003
2015
  // Click submit button with human-like behavior
2004
2016
  try {
2005
- const { createCursor } = require('ghost-cursor');
2017
+ const { createCursor } = require('ghost-cursor-patchright');
2006
2018
  const cursor = createCursor(page);
2007
2019
  await cursor.click(submitSelector);
2008
2020
  } catch (e) {
@@ -2049,10 +2061,14 @@ const handlers = {
2049
2061
 
2050
2062
  notifyProgress('random_scroll', 'started', `Scrolling ${scrollDirection} ${scrollAmount}px`);
2051
2063
 
2052
- await page.evaluate(({ scrollAmount, scrollDirection, smooth }) => {
2053
- const y = scrollDirection === 'down' ? scrollAmount : -scrollAmount;
2054
- window.scrollBy({ top: y, behavior: smooth ? 'smooth' : 'auto' });
2055
- }, { scrollAmount, scrollDirection, smooth });
2064
+ const y = scrollDirection === 'down' ? scrollAmount : -scrollAmount;
2065
+ if (smooth && page.realScroll) {
2066
+ await page.realScroll(y, 600);
2067
+ } else {
2068
+ await page.evaluate(({ y, smooth }) => {
2069
+ window.scrollBy({ top: y, behavior: smooth ? 'smooth' : 'auto' });
2070
+ }, { y, smooth });
2071
+ }
2056
2072
 
2057
2073
  notifyProgress('random_scroll', 'completed', `Scrolled ${scrollDirection} ${scrollAmount}px`, { direction: scrollDirection, amount: scrollAmount });
2058
2074
 
@@ -2448,7 +2464,7 @@ const handlers = {
2448
2464
  // ====== FEATURE 2: Pre-page-load Runtime API Interception ======
2449
2465
  // Inject BEFORE any JS runs — catches calls from obfuscated/webpack code
2450
2466
  try {
2451
- await page.evaluateOnNewDocument(() => {
2467
+ await page.addInitScript(() => {
2452
2468
  window.__interceptedApis = [];
2453
2469
  window.__wsMessages = [];
2454
2470
 
@@ -2552,7 +2568,7 @@ const handlers = {
2552
2568
  window.WebSocket.CLOSING = OrigWS.CLOSING;
2553
2569
  window.WebSocket.CLOSED = OrigWS.CLOSED;
2554
2570
  });
2555
- } catch (e) { /* evaluateOnNewDocument may fail on already-loaded pages, that's OK */ }
2571
+ } catch (e) { /* addInitScript may fail on already-loaded pages, that's OK */ }
2556
2572
 
2557
2573
  // Request handler
2558
2574
  page.on('request', req => {
@@ -4768,7 +4784,7 @@ const handlers = {
4768
4784
  }, identity, String(value));
4769
4785
  } else {
4770
4786
  // Smart Type
4771
- const { createCursor } = require('ghost-cursor');
4787
+ const { createCursor } = require('ghost-cursor-patchright');
4772
4788
  const cursor = createCursor(page);
4773
4789
 
4774
4790
  // Click center of element
@@ -4835,7 +4851,7 @@ const handlers = {
4835
4851
  });
4836
4852
 
4837
4853
  if (submitSelector) {
4838
- const { createCursor } = require('ghost-cursor');
4854
+ const { createCursor } = require('ghost-cursor-patchright');
4839
4855
  const cursor = createCursor(page);
4840
4856
  await cursor.click(submitSelector);
4841
4857
 
package/src/mcp/server.js CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  // CRITICAL: Redirect ALL console.log to STDERR before ANY imports
9
9
  // MCP uses STDIO transport — STDOUT must contain ONLY JSON-RPC messages.
10
- // Any console.log from this code or ANY dependency (puppeteer, blocker, etc.)
10
+ // Any console.log from this code or ANY dependency (playwright, blocker, etc.)
11
11
  // will corrupt the JSON-RPC stream and cause parsing errors.
12
12
  const _originalConsoleLog = console.log;
13
13
  console.log = function (...args) {