real-browser-mcp-server 1.3.0 → 1.3.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/README.md +1 -1
- package/lib/cjs/index.js +33 -18
- package/lib/esm/index.mjs +33 -13
- package/package.json +3 -3
- package/src/mcp/handlers.js +1 -1
- package/src/mcp/server.js +1 -1
- package/typings.d.ts +6 -6
- package/test/esm/test_option2.js +0 -46
- package/test/esm/test_playwright_ghost.js +0 -30
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ This server is **100% compatible with all major AI IDEs** (Cursor, VS Code, Clin
|
|
|
15
15
|
|
|
16
16
|
* **Undetected Browser Engine**: Powered by **Patchright Chromium**, bypassing modern fingerprinting checks (does not expose automation indicators or Webdriver/BiDi flags).
|
|
17
17
|
* **Integrated Ad & Tracker Blocker**: Utilizes `@ghostery/adblocker-playwright` with asynchronous pre-compiled filter caching to `adblocker.bin`, blocking ads and speed-bumps completely offline.
|
|
18
|
-
* **Human-like Interactions**: Integrates **
|
|
18
|
+
* **Human-like Interactions**: Integrates **ghost-cursor-patchright** (Bézier curves) to transparently simulate human mouse movements, velocity, and natural hover-before-click behaviors.
|
|
19
19
|
* **Turnstile Auto-Solver**: Seamlessly detects and bypasses Cloudflare Turnstile widgets.
|
|
20
20
|
* **Anti-Race Condition Guards**: Robust state-guards ensure popup blockers, shims, and adblockers attach exactly once per page, preventing context destruction.
|
|
21
21
|
|
package/lib/cjs/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
const { PlaywrightBlocker } = require("@ghostery/adblocker-playwright");
|
|
2
2
|
const { pageController } = require("./module/pageController.js");
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const recommendedPromise = import("playwright-ghost/plugins/recommended");
|
|
4
|
+
const { chromium } = require("patchright");
|
|
5
|
+
const { createCursor } = require("ghost-cursor-patchright");
|
|
7
6
|
const fs = require('fs');
|
|
8
7
|
const path = require('path');
|
|
9
8
|
|
|
@@ -72,7 +71,7 @@ function getDefaultHeadless() {
|
|
|
72
71
|
return envHeadless === 'true';
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
function
|
|
74
|
+
function applyBrowserShims(browser, page) {
|
|
76
75
|
if (page._shimsApplied) return page;
|
|
77
76
|
page._shimsApplied = true;
|
|
78
77
|
|
|
@@ -87,7 +86,7 @@ function applyPuppeteerShims(browser, page) {
|
|
|
87
86
|
});
|
|
88
87
|
}
|
|
89
88
|
|
|
90
|
-
// Cookie shims (
|
|
89
|
+
// Cookie shims (Compatibility adapter for Playwright context)
|
|
91
90
|
if (!page.cookies) {
|
|
92
91
|
page.cookies = async (urls) => {
|
|
93
92
|
return await page.context().cookies(urls ? (Array.isArray(urls) ? urls : [urls]) : []);
|
|
@@ -133,21 +132,43 @@ function applyPuppeteerShims(browser, page) {
|
|
|
133
132
|
};
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
// Ghost Cursor integration via
|
|
135
|
+
// Ghost Cursor integration via ghost-cursor-patchright
|
|
136
|
+
const cursorPromise = createCursor(page).catch(err => {
|
|
137
|
+
console.error("Failed to create cursor:", err);
|
|
138
|
+
return null;
|
|
139
|
+
});
|
|
140
|
+
|
|
137
141
|
page.realCursor = {
|
|
138
142
|
move: async (selector, options = {}) => {
|
|
139
143
|
try {
|
|
140
|
-
await
|
|
144
|
+
const cursor = await cursorPromise;
|
|
145
|
+
if (cursor) {
|
|
146
|
+
await cursor.move(selector, options);
|
|
147
|
+
} else {
|
|
148
|
+
await page.hover(selector, options);
|
|
149
|
+
}
|
|
141
150
|
} catch (e) {
|
|
142
151
|
// Silently fallback if element is not found
|
|
152
|
+
try {
|
|
153
|
+
await page.hover(selector, options);
|
|
154
|
+
} catch (err) {}
|
|
143
155
|
}
|
|
144
156
|
}
|
|
145
157
|
};
|
|
146
158
|
page.realClick = async (selector, options = {}) => {
|
|
147
|
-
|
|
159
|
+
try {
|
|
160
|
+
const cursor = await cursorPromise;
|
|
161
|
+
if (cursor) {
|
|
162
|
+
await cursor.click(selector, options);
|
|
163
|
+
} else {
|
|
164
|
+
await page.click(selector, options);
|
|
165
|
+
}
|
|
166
|
+
} catch (e) {
|
|
167
|
+
await page.click(selector, options);
|
|
168
|
+
}
|
|
148
169
|
};
|
|
149
170
|
|
|
150
|
-
// Mouse wheel shim (
|
|
171
|
+
// Mouse wheel shim (Adapter for Playwright positional args)
|
|
151
172
|
if (page.mouse && page.mouse.wheel) {
|
|
152
173
|
const originalWheel = page.mouse.wheel.bind(page.mouse);
|
|
153
174
|
page.mouse.wheel = async (x, y) => {
|
|
@@ -215,16 +236,10 @@ async function connect({
|
|
|
215
236
|
...args
|
|
216
237
|
];
|
|
217
238
|
|
|
218
|
-
// Load the dynamic ES Module imports
|
|
219
|
-
const { default: playwright } = await playwrightGhostPromise;
|
|
220
|
-
const { default: recommended } = await recommendedPromise;
|
|
221
|
-
const { chromium } = playwright;
|
|
222
|
-
|
|
223
239
|
const browser = await chromium.launch({
|
|
224
240
|
headless,
|
|
225
241
|
args: chromiumArgs,
|
|
226
|
-
proxy: playwrightProxy
|
|
227
|
-
plugins: [recommended()]
|
|
242
|
+
proxy: playwrightProxy
|
|
228
243
|
});
|
|
229
244
|
|
|
230
245
|
if (xvfbInstance) {
|
|
@@ -255,7 +270,7 @@ async function connect({
|
|
|
255
270
|
|
|
256
271
|
let page = await context.newPage();
|
|
257
272
|
|
|
258
|
-
|
|
273
|
+
applyBrowserShims(browser, page);
|
|
259
274
|
|
|
260
275
|
page = await pageController({
|
|
261
276
|
browser,
|
|
@@ -265,7 +280,7 @@ async function connect({
|
|
|
265
280
|
});
|
|
266
281
|
|
|
267
282
|
context.on('page', async (newPage) => {
|
|
268
|
-
|
|
283
|
+
applyBrowserShims(browser, newPage);
|
|
269
284
|
await pageController({
|
|
270
285
|
browser,
|
|
271
286
|
page: newPage,
|
package/lib/esm/index.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
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';
|
|
@@ -76,7 +75,7 @@ function getDefaultHeadless() {
|
|
|
76
75
|
return envHeadless === 'true';
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
function
|
|
78
|
+
function applyBrowserShims(browser, page) {
|
|
80
79
|
if (page._shimsApplied) return page;
|
|
81
80
|
page._shimsApplied = true;
|
|
82
81
|
|
|
@@ -91,7 +90,7 @@ function applyPuppeteerShims(browser, page) {
|
|
|
91
90
|
});
|
|
92
91
|
}
|
|
93
92
|
|
|
94
|
-
// Cookie shims (
|
|
93
|
+
// Cookie shims (Compatibility adapter for Playwright context)
|
|
95
94
|
if (!page.cookies) {
|
|
96
95
|
page.cookies = async (urls) => {
|
|
97
96
|
return await page.context().cookies(urls ? (Array.isArray(urls) ? urls : [urls]) : []);
|
|
@@ -137,21 +136,43 @@ function applyPuppeteerShims(browser, page) {
|
|
|
137
136
|
};
|
|
138
137
|
}
|
|
139
138
|
|
|
140
|
-
// Ghost Cursor integration via
|
|
139
|
+
// Ghost Cursor integration via ghost-cursor-patchright
|
|
140
|
+
const cursorPromise = createCursor(page).catch(err => {
|
|
141
|
+
console.error("Failed to create cursor:", err);
|
|
142
|
+
return null;
|
|
143
|
+
});
|
|
144
|
+
|
|
141
145
|
page.realCursor = {
|
|
142
146
|
move: async (selector, options = {}) => {
|
|
143
147
|
try {
|
|
144
|
-
await
|
|
148
|
+
const cursor = await cursorPromise;
|
|
149
|
+
if (cursor) {
|
|
150
|
+
await cursor.move(selector, options);
|
|
151
|
+
} else {
|
|
152
|
+
await page.hover(selector, options);
|
|
153
|
+
}
|
|
145
154
|
} catch (e) {
|
|
146
155
|
// Silently fallback if element is not found
|
|
156
|
+
try {
|
|
157
|
+
await page.hover(selector, options);
|
|
158
|
+
} catch (err) {}
|
|
147
159
|
}
|
|
148
160
|
}
|
|
149
161
|
};
|
|
150
162
|
page.realClick = async (selector, options = {}) => {
|
|
151
|
-
|
|
163
|
+
try {
|
|
164
|
+
const cursor = await cursorPromise;
|
|
165
|
+
if (cursor) {
|
|
166
|
+
await cursor.click(selector, options);
|
|
167
|
+
} else {
|
|
168
|
+
await page.click(selector, options);
|
|
169
|
+
}
|
|
170
|
+
} catch (e) {
|
|
171
|
+
await page.click(selector, options);
|
|
172
|
+
}
|
|
152
173
|
};
|
|
153
174
|
|
|
154
|
-
// Mouse wheel shim (
|
|
175
|
+
// Mouse wheel shim (Adapter for Playwright positional args)
|
|
155
176
|
if (page.mouse && page.mouse.wheel) {
|
|
156
177
|
const originalWheel = page.mouse.wheel.bind(page.mouse);
|
|
157
178
|
page.mouse.wheel = async (x, y) => {
|
|
@@ -221,8 +242,7 @@ export async function connect({
|
|
|
221
242
|
const browser = await chromium.launch({
|
|
222
243
|
headless,
|
|
223
244
|
args: chromiumArgs,
|
|
224
|
-
proxy: playwrightProxy
|
|
225
|
-
plugins: [recommended()]
|
|
245
|
+
proxy: playwrightProxy
|
|
226
246
|
});
|
|
227
247
|
|
|
228
248
|
if (xvfbInstance) {
|
|
@@ -253,7 +273,7 @@ export async function connect({
|
|
|
253
273
|
|
|
254
274
|
let page = await context.newPage();
|
|
255
275
|
|
|
256
|
-
|
|
276
|
+
applyBrowserShims(browser, page);
|
|
257
277
|
|
|
258
278
|
page = await pageController({
|
|
259
279
|
browser,
|
|
@@ -263,7 +283,7 @@ export async function connect({
|
|
|
263
283
|
});
|
|
264
284
|
|
|
265
285
|
context.on('page', async (newPage) => {
|
|
266
|
-
|
|
286
|
+
applyBrowserShims(browser, newPage);
|
|
267
287
|
await pageController({
|
|
268
288
|
browser,
|
|
269
289
|
page: newPage,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "real-browser-mcp-server",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.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",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@ghostery/adblocker-playwright": "latest",
|
|
47
47
|
"@modelcontextprotocol/sdk": "latest",
|
|
48
48
|
"patchright": "latest",
|
|
49
|
-
"
|
|
50
|
-
"xvfb": "
|
|
49
|
+
"ghost-cursor-patchright": "file:../ghost-cursor",
|
|
50
|
+
"xvfb": "latest"
|
|
51
51
|
}
|
|
52
52
|
}
|
package/src/mcp/handlers.js
CHANGED
|
@@ -595,7 +595,7 @@ const handlers = {
|
|
|
595
595
|
// ═══════════════════════════════════════════════════════════════
|
|
596
596
|
// INJECTED SCRIPT - Silent Handling of Popups
|
|
597
597
|
// Override window.confirm/alert to handle them inside the page context
|
|
598
|
-
// Note: Using evaluateOnNewDocument
|
|
598
|
+
// Note: Using evaluateOnNewDocument shim instead of raw addInitScript
|
|
599
599
|
// ═══════════════════════════════════════════════════════════════
|
|
600
600
|
await pageInstance.evaluateOnNewDocument(() => {
|
|
601
601
|
window.originalConfirm = window.confirm;
|
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 (
|
|
10
|
+
// Any console.log from this code or ANY dependency (browser, 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) {
|
package/typings.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
declare module "
|
|
2
|
-
import type { Browser, Page } from "
|
|
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,12 +19,12 @@ declare module "brave-real-browser-mcp-server" {
|
|
|
19
19
|
interface Options {
|
|
20
20
|
args?: string[];
|
|
21
21
|
headless?: boolean;
|
|
22
|
-
customConfig?:
|
|
22
|
+
customConfig?: any;
|
|
23
23
|
proxy?: ProxyOptions;
|
|
24
24
|
turnstile?: boolean;
|
|
25
|
-
connectOption?:
|
|
25
|
+
connectOption?: any;
|
|
26
26
|
disableXvfb?: boolean;
|
|
27
|
-
plugins?:
|
|
27
|
+
plugins?: any[];
|
|
28
28
|
ignoreAllFlags?: boolean;
|
|
29
29
|
/** Enable blocker on all pages (default: true) */
|
|
30
30
|
enableBlocker?: boolean;
|
package/test/esm/test_option2.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import playwright from "playwright-ghost/patchright";
|
|
2
|
-
import recommended from "playwright-ghost/plugins/recommended";
|
|
3
|
-
|
|
4
|
-
console.log("🚀 Starting Option 2 Demonstration...");
|
|
5
|
-
|
|
6
|
-
try {
|
|
7
|
-
// Launch browser with playwright-ghost overlay & recommended stealth/humanize plugins
|
|
8
|
-
console.log("Launching browser with Option 2 (playwright-ghost overlay & recommended plugins)...");
|
|
9
|
-
const browser = await playwright.chromium.launch({
|
|
10
|
-
headless: false, // Set to false so you can see it if run locally, but works in headless too
|
|
11
|
-
plugins: [recommended()]
|
|
12
|
-
});
|
|
13
|
-
console.log("✅ Browser launched successfully!");
|
|
14
|
-
|
|
15
|
-
const context = await browser.newContext();
|
|
16
|
-
const page = await context.newPage();
|
|
17
|
-
console.log("✅ Page created!");
|
|
18
|
-
|
|
19
|
-
// Go to DrissionPage Detector
|
|
20
|
-
console.log("Navigating to DrissionPage Detector page...");
|
|
21
|
-
await page.goto("https://web.archive.org/web/20240913054632/https://drissionpage.pages.dev/", { timeout: 60000 });
|
|
22
|
-
console.log("✅ Page loaded successfully!");
|
|
23
|
-
|
|
24
|
-
console.log("Triggering normal, standard page.click('#detector')!");
|
|
25
|
-
console.log("👉 Option 2 will intercept this click, calculate a Bézier curve path, scroll it into view, move the cursor humanly, and then click!");
|
|
26
|
-
|
|
27
|
-
const startTime = Date.now();
|
|
28
|
-
await page.click("#detector");
|
|
29
|
-
console.log(`✅ Standard click intercepted and completed in ${(Date.now() - startTime) / 1000}s!`);
|
|
30
|
-
|
|
31
|
-
// Verify the detector says "not a bot"
|
|
32
|
-
const isNotBot = await page.evaluate(() => {
|
|
33
|
-
return document.querySelector('#isBot span').textContent.includes("not");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
if (isNotBot) {
|
|
37
|
-
console.log("🎉 SUCCESS! The detector passed: 'not a bot'.");
|
|
38
|
-
} else {
|
|
39
|
-
console.log("❌ FAILED! Detected as bot.");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
await browser.close();
|
|
43
|
-
console.log("🏁 Browser closed. Demonstration completed successfully.");
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error("❌ An error occurred during the demonstration:", error);
|
|
46
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import playwright from "playwright-ghost/patchright";
|
|
2
|
-
import recommended from "playwright-ghost/plugins/recommended";
|
|
3
|
-
|
|
4
|
-
console.log("🚀 Testing playwright-ghost integration...");
|
|
5
|
-
|
|
6
|
-
try {
|
|
7
|
-
const browser = await playwright.chromium.launch({
|
|
8
|
-
headless: true,
|
|
9
|
-
plugins: [recommended()]
|
|
10
|
-
});
|
|
11
|
-
console.log("✅ Browser launched successfully with playwright-ghost!");
|
|
12
|
-
|
|
13
|
-
const context = await browser.newContext();
|
|
14
|
-
const page = await context.newPage();
|
|
15
|
-
console.log("✅ Context and page created!");
|
|
16
|
-
|
|
17
|
-
console.log("Navigating to simple website...");
|
|
18
|
-
await page.goto("https://example.com");
|
|
19
|
-
console.log("✅ Navigated successfully!");
|
|
20
|
-
|
|
21
|
-
console.log("Attempting humanized click on link...");
|
|
22
|
-
const link = page.locator("a");
|
|
23
|
-
await link.click();
|
|
24
|
-
console.log("✅ Clicked successfully!");
|
|
25
|
-
|
|
26
|
-
await browser.close();
|
|
27
|
-
console.log("🎉 Test completed successfully!");
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error("❌ Test failed:", error);
|
|
30
|
-
}
|