real-browser-mcp-server 1.2.0 → 1.2.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.
Files changed (120) hide show
  1. package/README.md +96 -9
  2. package/dist/lib/cjs/index.d.ts +2 -0
  3. package/dist/lib/cjs/index.d.ts.map +1 -0
  4. package/dist/lib/cjs/index.js +385 -0
  5. package/dist/lib/cjs/index.js.map +1 -0
  6. package/dist/lib/cjs/module/pageController.d.ts +2 -0
  7. package/dist/lib/cjs/module/pageController.d.ts.map +1 -0
  8. package/{lib → dist/lib}/cjs/module/pageController.js +28 -29
  9. package/dist/lib/cjs/module/pageController.js.map +1 -0
  10. package/dist/lib/cjs/module/turnstile.d.ts +2 -0
  11. package/dist/lib/cjs/module/turnstile.d.ts.map +1 -0
  12. package/{lib → dist/lib}/cjs/module/turnstile.js +24 -12
  13. package/dist/lib/cjs/module/turnstile.js.map +1 -0
  14. package/dist/src/index.d.ts +11 -0
  15. package/dist/src/index.d.ts.map +1 -0
  16. package/dist/src/index.js +118 -0
  17. package/dist/src/index.js.map +1 -0
  18. package/dist/src/mcp/handlers/browser.d.ts +30 -0
  19. package/dist/src/mcp/handlers/browser.d.ts.map +1 -0
  20. package/dist/src/mcp/handlers/browser.js +231 -0
  21. package/dist/src/mcp/handlers/browser.js.map +1 -0
  22. package/dist/src/mcp/handlers/dom.d.ts +134 -0
  23. package/dist/src/mcp/handlers/dom.d.ts.map +1 -0
  24. package/dist/src/mcp/handlers/dom.js +551 -0
  25. package/dist/src/mcp/handlers/dom.js.map +1 -0
  26. package/dist/src/mcp/handlers/extract.d.ts +59 -0
  27. package/dist/src/mcp/handlers/extract.d.ts.map +1 -0
  28. package/dist/src/mcp/handlers/extract.js +455 -0
  29. package/dist/src/mcp/handlers/extract.js.map +1 -0
  30. package/dist/src/mcp/handlers/form-handlers.d.ts +9 -0
  31. package/dist/src/mcp/handlers/form-handlers.d.ts.map +1 -0
  32. package/dist/src/mcp/handlers/form-handlers.js +56 -0
  33. package/dist/src/mcp/handlers/form-handlers.js.map +1 -0
  34. package/dist/src/mcp/handlers/helpers.d.ts +47 -0
  35. package/dist/src/mcp/handlers/helpers.d.ts.map +1 -0
  36. package/dist/src/mcp/handlers/helpers.js +515 -0
  37. package/dist/src/mcp/handlers/helpers.js.map +1 -0
  38. package/dist/src/mcp/handlers/index.d.ts +6 -0
  39. package/dist/src/mcp/handlers/index.d.ts.map +1 -0
  40. package/dist/src/mcp/handlers/index.js +61 -0
  41. package/dist/src/mcp/handlers/index.js.map +1 -0
  42. package/dist/src/mcp/handlers/media-handlers.d.ts +10 -0
  43. package/dist/src/mcp/handlers/media-handlers.d.ts.map +1 -0
  44. package/dist/src/mcp/handlers/media-handlers.js +535 -0
  45. package/dist/src/mcp/handlers/media-handlers.js.map +1 -0
  46. package/dist/src/mcp/handlers/network.d.ts +147 -0
  47. package/dist/src/mcp/handlers/network.d.ts.map +1 -0
  48. package/dist/src/mcp/handlers/network.js +1135 -0
  49. package/dist/src/mcp/handlers/network.js.map +1 -0
  50. package/dist/src/mcp/handlers/state.d.ts +34 -0
  51. package/dist/src/mcp/handlers/state.d.ts.map +1 -0
  52. package/dist/src/mcp/handlers/state.js +225 -0
  53. package/dist/src/mcp/handlers/state.js.map +1 -0
  54. package/dist/src/mcp/handlers/utility-handlers.d.ts +167 -0
  55. package/dist/src/mcp/handlers/utility-handlers.d.ts.map +1 -0
  56. package/dist/src/mcp/handlers/utility-handlers.js +280 -0
  57. package/dist/src/mcp/handlers/utility-handlers.js.map +1 -0
  58. package/dist/src/mcp/handlers/vision.d.ts +127 -0
  59. package/dist/src/mcp/handlers/vision.d.ts.map +1 -0
  60. package/dist/src/mcp/handlers/vision.js +483 -0
  61. package/dist/src/mcp/handlers/vision.js.map +1 -0
  62. package/dist/src/mcp/index.d.ts +3 -0
  63. package/dist/src/mcp/index.d.ts.map +1 -0
  64. package/dist/src/mcp/index.js +166 -0
  65. package/dist/src/mcp/index.js.map +1 -0
  66. package/dist/src/mcp/server.d.ts +2 -0
  67. package/dist/src/mcp/server.d.ts.map +1 -0
  68. package/dist/src/mcp/server.js +117 -0
  69. package/dist/src/mcp/server.js.map +1 -0
  70. package/dist/src/mcp/tools.d.ts +8 -0
  71. package/dist/src/mcp/tools.d.ts.map +1 -0
  72. package/{src → dist/src}/mcp/tools.js +12 -11
  73. package/dist/src/mcp/tools.js.map +1 -0
  74. package/dist/src/shared/cache-manager.d.ts +80 -0
  75. package/dist/src/shared/cache-manager.d.ts.map +1 -0
  76. package/dist/src/shared/cache-manager.js +221 -0
  77. package/dist/src/shared/cache-manager.js.map +1 -0
  78. package/dist/src/shared/tools.d.ts +2 -0
  79. package/dist/src/shared/tools.d.ts.map +1 -0
  80. package/dist/src/shared/tools.js +599 -0
  81. package/dist/src/shared/tools.js.map +1 -0
  82. package/dist/src/types.d.ts +365 -0
  83. package/dist/src/types.d.ts.map +1 -0
  84. package/dist/src/types.js +9 -0
  85. package/dist/src/types.js.map +1 -0
  86. package/dist/test/cjs/test.d.ts +11 -0
  87. package/dist/test/cjs/test.d.ts.map +1 -0
  88. package/dist/test/cjs/test.js +289 -0
  89. package/dist/test/cjs/test.js.map +1 -0
  90. package/dist/test/mcp/smoke-test.d.ts +29 -0
  91. package/dist/test/mcp/smoke-test.d.ts.map +1 -0
  92. package/dist/test/mcp/smoke-test.js +132 -0
  93. package/dist/test/mcp/smoke-test.js.map +1 -0
  94. package/lib/esm/index.mjs +232 -79
  95. package/lib/esm/module/pageController.mjs +21 -18
  96. package/lib/esm/module/turnstile.mjs +7 -0
  97. package/package.json +25 -15
  98. package/typings.d.ts +12 -6
  99. package/.github/ISSUE_TEMPLATE/general_issue.yaml +0 -58
  100. package/.github/SETUP.md +0 -111
  101. package/.github/workflows/publish.yml +0 -135
  102. package/Dockerfile +0 -79
  103. package/lib/cjs/adblocker.bin +0 -0
  104. package/lib/cjs/index.js +0 -249
  105. package/src/ai/action-parser.js +0 -274
  106. package/src/ai/core.js +0 -378
  107. package/src/ai/element-finder.js +0 -466
  108. package/src/ai/index.js +0 -82
  109. package/src/ai/page-analyzer.js +0 -304
  110. package/src/ai/selector-healer.js +0 -236
  111. package/src/index.js +0 -121
  112. package/src/mcp/handlers.js +0 -5071
  113. package/src/mcp/index.js +0 -190
  114. package/src/mcp/server.js +0 -144
  115. package/src/shared/tools.js +0 -618
  116. package/test/cjs/test.js +0 -259
  117. package/test/esm/package.json +0 -13
  118. package/test/esm/test.js +0 -226
  119. package/test/esm/test_option2.js +0 -46
  120. package/test/esm/test_playwright_ghost.js +0 -30
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,
@@ -24,26 +24,29 @@ export async function pageController({ browser, page, proxy, turnstile }) {
24
24
 
25
25
  // === POPUP AD BLOCKING ===
26
26
  const context = page.context();
27
- context.on('page', async (newPage) => {
28
- try {
29
- const opener = await newPage.opener();
30
- if (opener) {
31
- const url = newPage.url();
32
- const isAdPopup = url === 'about:blank' ||
33
- url.includes('ad') ||
34
- url.includes('pop') ||
35
- url.includes('click') ||
36
- url.includes('redirect') ||
37
- url.includes('track');
38
- if (isAdPopup) {
39
- await newPage.close().catch(() => { });
40
- console.error('[popup-blocker] Blocked popup ad:', url.substring(0, 50));
27
+ if (!context._popupBlockerApplied) {
28
+ context._popupBlockerApplied = true;
29
+ context.on('page', async (newPage) => {
30
+ try {
31
+ const opener = await newPage.opener();
32
+ if (opener) {
33
+ const url = newPage.url();
34
+ const isAdPopup = url === 'about:blank' ||
35
+ url.includes('ad') ||
36
+ url.includes('pop') ||
37
+ url.includes('click') ||
38
+ url.includes('redirect') ||
39
+ url.includes('track');
40
+ if (isAdPopup) {
41
+ await newPage.close().catch(() => { });
42
+ console.error('[popup-blocker] Blocked popup ad:', url.substring(0, 50));
43
+ }
41
44
  }
45
+ } catch (e) {
46
+ // Ignore errors
42
47
  }
43
- } catch (e) {
44
- // Ignore errors
45
- }
46
- });
48
+ });
49
+ }
47
50
 
48
51
  // NOTE: JS stealth overrides are commented out because Patchright natively handles automation hiding.
49
52
  // Manual JS overrides trigger Pixelscan fingerprint masking detectors.
@@ -2,6 +2,13 @@ export const checkTurnstile = async ({ page }) => {
2
2
  try {
3
3
  const elements = await page.locator('[name="cf-turnstile-response"]').all();
4
4
  if (elements.length <= 0) {
5
+ const isChallenge = await page.evaluate(() => {
6
+ return document.title.includes('Just a moment') ||
7
+ document.querySelector('#challenge-stage') !== null ||
8
+ document.querySelector('.cf-turnstile') !== null;
9
+ });
10
+ if (!isChallenge) return false;
11
+
5
12
  const coordinates = await page.evaluate(() => {
6
13
  let coordinates = [];
7
14
  document.querySelectorAll('div').forEach(item => {
package/package.json CHANGED
@@ -1,35 +1,41 @@
1
1
  {
2
2
  "name": "real-browser-mcp-server",
3
- "version": "1.2.0",
3
+ "version": "1.2.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
- "main": "lib/cjs/index.js",
5
+ "main": "dist/lib/cjs/index.js",
6
6
  "module": "lib/esm/index.mjs",
7
7
  "type": "commonjs",
8
8
  "exports": {
9
9
  ".": {
10
10
  "types": "./typings.d.ts",
11
11
  "import": "./lib/esm/index.mjs",
12
- "require": "./lib/cjs/index.js"
12
+ "require": "./dist/lib/cjs/index.js"
13
13
  }
14
14
  },
15
+ "files": [
16
+ "dist",
17
+ "lib/esm",
18
+ "typings.d.ts"
19
+ ],
15
20
  "typings": "typings.d.ts",
16
21
  "engines": {
17
22
  "node": ">=18.0.0"
18
23
  },
19
24
  "bin": {
20
- "real-browser-mcp": "./src/index.js"
25
+ "real-browser-mcp": "./dist/src/index.js"
21
26
  },
22
27
  "scripts": {
23
- "start": "node src/index.js",
24
- "dev": "node src/index.js",
25
- "mcp": "node src/index.js mcp",
26
- "mcp:verbose": "node src/index.js mcp --verbose",
27
- "list": "node src/index.js --list",
28
+ "prepublishOnly": "npm run build",
29
+ "build": "tsc",
30
+ "start": "node dist/src/index.js",
31
+ "dev": "npm run build && node dist/src/index.js",
32
+ "mcp": "node dist/src/index.js mcp",
33
+ "mcp:verbose": "node dist/src/index.js mcp --verbose",
34
+ "list": "node dist/src/index.js --list",
28
35
  "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.'",
32
- "postinstall": "npx patchright install chromium"
36
+ "esm_test": "node ./test/esm/test.mjs",
37
+ "cjs_test": "node ./dist/test/cjs/test.js",
38
+ "mcp_test": "node ./dist/test/mcp/smoke-test.js"
33
39
  },
34
40
  "keywords": [
35
41
  "mcp-server",
@@ -45,7 +51,11 @@
45
51
  "dependencies": {
46
52
  "@ghostery/adblocker-playwright": "latest",
47
53
  "@modelcontextprotocol/sdk": "latest",
48
- "patchright": "latest",
49
- "playwright-ghost": "latest"
54
+ "ghost-cursor-patchright": "^1.0.2",
55
+ "patchright": "latest"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^25.9.2",
59
+ "typescript": "^6.0.3"
50
60
  }
51
61
  }
package/typings.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- declare module "brave-real-browser-mcp-server" {
2
- import type { Browser, Page } from "brave-real-puppeteer-core";
3
- import type { GhostCursor } from "ghost-cursor";
1
+ declare module "real-browser-mcp-server" {
2
+ import type { Browser, Page } from "patchright";
3
+ import type { GhostCursor } from "ghost-cursor-patchright";
4
4
 
5
5
  export function connect(options?: Options): Promise<ConnectResult>;
6
6
 
@@ -19,13 +19,14 @@ declare module "brave-real-browser-mcp-server" {
19
19
  interface Options {
20
20
  args?: string[];
21
21
  headless?: boolean;
22
- customConfig?: import("brave-real-launcher").Options;
22
+ customConfig?: any;
23
23
  proxy?: ProxyOptions;
24
24
  turnstile?: boolean;
25
- connectOption?: import("brave-real-puppeteer-core").ConnectOptions;
25
+ connectOption?: any;
26
26
  disableXvfb?: boolean;
27
- plugins?: import("puppeteer-extra").PuppeteerExtraPlugin[];
28
27
  ignoreAllFlags?: boolean;
28
+ /** Path to the browser executable (defaults to auto-detected Brave browser or falls back to Chromium) */
29
+ executablePath?: string;
29
30
  /** Enable blocker on all pages (default: true) */
30
31
  enableBlocker?: boolean;
31
32
  /** Blocker configuration options */
@@ -77,3 +78,8 @@ declare module "brave-real-browser-mcp-server" {
77
78
  shouldBlock(url: string): boolean;
78
79
  }
79
80
  }
81
+
82
+ declare module "real-browser-mcp-server" {
83
+ export * from "real-browser-mcp-server";
84
+ }
85
+
@@ -1,58 +0,0 @@
1
- name: Report Issue
2
- description: Please use this to report any issue
3
- labels: [triage]
4
- assignees:
5
- - zfcsoftware
6
- body:
7
- - type: markdown
8
- attributes:
9
- value: |
10
- Please take care to fill in all fields. Recreating the issue will speed up its resolution. Thank you for contributing to the betterment of the library by reporting issues.
11
- - type: textarea
12
- id: issue-detail
13
- attributes:
14
- label: Description
15
- description: Please describe the problem you are experiencing. You only need to provide information about the problem in this field.
16
- validations:
17
- required: true
18
- - type: textarea
19
- id: issue-recreate
20
- attributes:
21
- label: Full steps to reproduce the issue
22
- description: Please provide a full working code to reproduce the issue. Make sure that the code you provide is directly executable. This step is very important to resolve the issue.
23
- validations:
24
- required: true
25
- - type: dropdown
26
- id: issue-type
27
- attributes:
28
- label: Issue Type
29
- description: What type of issue would you like to report?
30
- multiple: true
31
- options:
32
- - Bug
33
- - Build/Install
34
- - Performance
35
- - Support
36
- - Feature Request
37
- - Documentation Request
38
- - Others
39
- - type: dropdown
40
- id: Operating-System
41
- attributes:
42
- label: Operating System
43
- description: What OS are you seeing the issue in? If you don't see your OS listed, please provide more details in the "Description" section above.
44
- multiple: true
45
- options:
46
- - Windows 10
47
- - Linux
48
- - Mac OS
49
- - Other
50
- - type: dropdown
51
- id: use-type
52
- attributes:
53
- label: Do you use Docker?
54
- description: Are you running it with Docker or on your local computer?
55
- multiple: false
56
- options:
57
- - Docker
58
- - I don't use Docker