real-browser-mcp-server 1.0.6 → 1.0.8
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 +20 -0
- package/lib/cjs/index.js +15 -6
- package/lib/esm/index.mjs +15 -6
- package/package.json +1 -1
- package/src/ai/core.js +7 -3
- package/src/mcp/handlers.js +37 -17
- package/test/cjs/test.js +3 -1
- package/test/esm/test.mjs +3 -1
- package/typings.d.ts +2 -0
package/README.md
CHANGED
|
@@ -11,6 +11,26 @@ This server is **100% compatible with all major AI IDEs** (Cursor, VS Code, Clin
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## ⚙️ Installation & Setup
|
|
15
|
+
|
|
16
|
+
To install and run the server locally, clone the repository, install NPM dependencies, and configure the undetected browser binary using **Patchright**:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 1. Clone the repository
|
|
20
|
+
git clone https://github.com/codeiva4u/Real-Browser-Mcp-Server.git
|
|
21
|
+
|
|
22
|
+
# 2. Navigate to the project directory
|
|
23
|
+
cd Real-Browser-Mcp-Server
|
|
24
|
+
|
|
25
|
+
# 3. Install dependencies
|
|
26
|
+
npm install
|
|
27
|
+
|
|
28
|
+
# 4. Install Chromium-Driver for Patchright (Undetected Browser binary)
|
|
29
|
+
npx patchright install chromium
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
14
34
|
## 🚀 Key Evasion & Stealth Features
|
|
15
35
|
|
|
16
36
|
* **Undetected Browser Engine**: Powered by **Patchright Chromium**, bypassing modern fingerprinting checks (does not expose automation indicators or Webdriver/BiDi flags).
|
package/lib/cjs/index.js
CHANGED
|
@@ -70,10 +70,13 @@ function getDefaultHeadless() {
|
|
|
70
70
|
return envHeadless === 'true';
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function setupRealPage(browser, page) {
|
|
73
|
+
function setupRealPage(browser, page, options = {}) {
|
|
74
74
|
if (page._setupApplied) return page;
|
|
75
75
|
page._setupApplied = true;
|
|
76
76
|
|
|
77
|
+
const headless = options.headless !== undefined ? options.headless : getDefaultHeadless();
|
|
78
|
+
const showCursor = options.showCursor !== undefined ? options.showCursor : false;
|
|
79
|
+
|
|
77
80
|
// Enable ad blocker
|
|
78
81
|
if (adBlockerInstance) {
|
|
79
82
|
adBlockerInstance.enableBlockingInPage(page).catch(() => {});
|
|
@@ -87,11 +90,13 @@ function setupRealPage(browser, page) {
|
|
|
87
90
|
|
|
88
91
|
// Ghost Cursor integration - Bézier curve human-like mouse movement
|
|
89
92
|
try {
|
|
90
|
-
|
|
93
|
+
// 5th parameter controls visual cursor (installMouseHelper under the hood)
|
|
94
|
+
const cursorPromise = createCursor(page, undefined, false, {}, showCursor);
|
|
91
95
|
page.realCursor = {
|
|
92
96
|
move: async (selector, options = {}) => {
|
|
93
97
|
try {
|
|
94
|
-
|
|
98
|
+
const cursor = await cursorPromise;
|
|
99
|
+
await cursor.move(selector, options);
|
|
95
100
|
} catch (e) {
|
|
96
101
|
// Fallback to native hover if ghost-cursor fails
|
|
97
102
|
try { await page.hover(selector); } catch (_) {}
|
|
@@ -100,7 +105,8 @@ function setupRealPage(browser, page) {
|
|
|
100
105
|
};
|
|
101
106
|
page.realClick = async (selector, options = {}) => {
|
|
102
107
|
try {
|
|
103
|
-
|
|
108
|
+
const cursor = await cursorPromise;
|
|
109
|
+
await cursor.click(selector, options);
|
|
104
110
|
} catch (e) {
|
|
105
111
|
// Fallback to native click if ghost-cursor fails
|
|
106
112
|
await page.click(selector, options);
|
|
@@ -228,6 +234,7 @@ async function connect({
|
|
|
228
234
|
proxy = {},
|
|
229
235
|
turnstile = false,
|
|
230
236
|
executablePath = undefined,
|
|
237
|
+
visualCursor = undefined,
|
|
231
238
|
} = {}) {
|
|
232
239
|
let playwrightProxy = undefined;
|
|
233
240
|
if (proxy && proxy.host && proxy.port) {
|
|
@@ -328,7 +335,9 @@ async function connect({
|
|
|
328
335
|
|
|
329
336
|
await applyUserAgentOverride(page, modifiedUa, userAgentMetadata);
|
|
330
337
|
|
|
331
|
-
|
|
338
|
+
const showCursor = visualCursor !== undefined ? visualCursor : false;
|
|
339
|
+
|
|
340
|
+
setupRealPage(browser, page, { headless, showCursor });
|
|
332
341
|
|
|
333
342
|
page = await pageController({
|
|
334
343
|
browser,
|
|
@@ -339,7 +348,7 @@ async function connect({
|
|
|
339
348
|
|
|
340
349
|
context.on('page', async (newPage) => {
|
|
341
350
|
await applyUserAgentOverride(newPage, modifiedUa, userAgentMetadata);
|
|
342
|
-
setupRealPage(browser, newPage);
|
|
351
|
+
setupRealPage(browser, newPage, { headless, showCursor });
|
|
343
352
|
await pageController({
|
|
344
353
|
browser,
|
|
345
354
|
page: newPage,
|
package/lib/esm/index.mjs
CHANGED
|
@@ -75,10 +75,13 @@ function getDefaultHeadless() {
|
|
|
75
75
|
return envHeadless === 'true';
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
function setupRealPage(browser, page) {
|
|
78
|
+
function setupRealPage(browser, page, options = {}) {
|
|
79
79
|
if (page._setupApplied) return page;
|
|
80
80
|
page._setupApplied = true;
|
|
81
81
|
|
|
82
|
+
const headless = options.headless !== undefined ? options.headless : getDefaultHeadless();
|
|
83
|
+
const showCursor = options.showCursor !== undefined ? options.showCursor : false;
|
|
84
|
+
|
|
82
85
|
// Enable ad blocker
|
|
83
86
|
if (adBlockerInstance) {
|
|
84
87
|
adBlockerInstance.enableBlockingInPage(page).catch(() => {});
|
|
@@ -92,11 +95,13 @@ function setupRealPage(browser, page) {
|
|
|
92
95
|
|
|
93
96
|
// Ghost Cursor integration - Bézier curve human-like mouse movement
|
|
94
97
|
try {
|
|
95
|
-
|
|
98
|
+
// 5th parameter controls visual cursor (installMouseHelper under the hood)
|
|
99
|
+
const cursorPromise = createCursor(page, undefined, false, {}, showCursor);
|
|
96
100
|
page.realCursor = {
|
|
97
101
|
move: async (selector, options = {}) => {
|
|
98
102
|
try {
|
|
99
|
-
|
|
103
|
+
const cursor = await cursorPromise;
|
|
104
|
+
await cursor.move(selector, options);
|
|
100
105
|
} catch (e) {
|
|
101
106
|
// Fallback to native hover if ghost-cursor fails
|
|
102
107
|
try { await page.hover(selector); } catch (_) {}
|
|
@@ -105,7 +110,8 @@ function setupRealPage(browser, page) {
|
|
|
105
110
|
};
|
|
106
111
|
page.realClick = async (selector, options = {}) => {
|
|
107
112
|
try {
|
|
108
|
-
|
|
113
|
+
const cursor = await cursorPromise;
|
|
114
|
+
await cursor.click(selector, options);
|
|
109
115
|
} catch (e) {
|
|
110
116
|
// Fallback to native click if ghost-cursor fails
|
|
111
117
|
await page.click(selector, options);
|
|
@@ -232,6 +238,7 @@ export async function connect({
|
|
|
232
238
|
proxy = {},
|
|
233
239
|
turnstile = false,
|
|
234
240
|
executablePath = undefined,
|
|
241
|
+
visualCursor = undefined,
|
|
235
242
|
} = {}) {
|
|
236
243
|
let playwrightProxy = undefined;
|
|
237
244
|
if (proxy && proxy.host && proxy.port) {
|
|
@@ -332,7 +339,9 @@ export async function connect({
|
|
|
332
339
|
|
|
333
340
|
await applyUserAgentOverride(page, modifiedUa, userAgentMetadata);
|
|
334
341
|
|
|
335
|
-
|
|
342
|
+
const showCursor = visualCursor !== undefined ? visualCursor : false;
|
|
343
|
+
|
|
344
|
+
setupRealPage(browser, page, { headless, showCursor });
|
|
336
345
|
|
|
337
346
|
page = await pageController({
|
|
338
347
|
browser,
|
|
@@ -343,7 +352,7 @@ export async function connect({
|
|
|
343
352
|
|
|
344
353
|
context.on('page', async (newPage) => {
|
|
345
354
|
await applyUserAgentOverride(newPage, modifiedUa, userAgentMetadata);
|
|
346
|
-
setupRealPage(browser, newPage);
|
|
355
|
+
setupRealPage(browser, newPage, { headless, showCursor });
|
|
347
356
|
await pageController({
|
|
348
357
|
browser,
|
|
349
358
|
page: newPage,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "real-browser-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
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",
|
package/src/ai/core.js
CHANGED
|
@@ -90,9 +90,13 @@ class AICore {
|
|
|
90
90
|
if (element) {
|
|
91
91
|
if (humanLike) {
|
|
92
92
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
if (page.realClick) {
|
|
94
|
+
await page.realClick(selector);
|
|
95
|
+
} else {
|
|
96
|
+
const { createCursor } = require('ghost-cursor-patchright');
|
|
97
|
+
const cursor = await createCursor(page);
|
|
98
|
+
await cursor.click(selector);
|
|
99
|
+
}
|
|
96
100
|
} catch {
|
|
97
101
|
await element.click();
|
|
98
102
|
}
|
package/src/mcp/handlers.js
CHANGED
|
@@ -1239,20 +1239,23 @@ const handlers = {
|
|
|
1239
1239
|
notifyProgress('click', 'progress', 'Used force click (JS)');
|
|
1240
1240
|
} else if (humanLike) {
|
|
1241
1241
|
try {
|
|
1242
|
-
const { createCursor } = require('ghost-cursor-patchright');
|
|
1243
|
-
const cursor = createCursor(page);
|
|
1244
|
-
|
|
1245
1242
|
if (context !== page) {
|
|
1246
1243
|
const element = await context.$(selector);
|
|
1247
1244
|
if (element) {
|
|
1248
1245
|
const box = await element.boundingBox();
|
|
1249
1246
|
if (box) {
|
|
1250
|
-
|
|
1247
|
+
if (page.realCursor) {
|
|
1248
|
+
await page.realCursor.move({ x: box.x + box.width / 2, y: box.y + box.height / 2 });
|
|
1249
|
+
}
|
|
1251
1250
|
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2, { clickCount, delay });
|
|
1252
1251
|
}
|
|
1253
1252
|
}
|
|
1254
1253
|
} else {
|
|
1255
|
-
|
|
1254
|
+
if (page.realClick) {
|
|
1255
|
+
await page.realClick(selector);
|
|
1256
|
+
} else {
|
|
1257
|
+
await context.click(selector, { clickCount, delay });
|
|
1258
|
+
}
|
|
1256
1259
|
}
|
|
1257
1260
|
notifyProgress('click', 'progress', 'Used human-like cursor movement');
|
|
1258
1261
|
} catch (e) {
|
|
@@ -2002,9 +2005,13 @@ const handlers = {
|
|
|
2002
2005
|
|
|
2003
2006
|
// Click submit button with human-like behavior
|
|
2004
2007
|
try {
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
+
if (page.realClick) {
|
|
2009
|
+
await page.realClick(submitSelector);
|
|
2010
|
+
} else {
|
|
2011
|
+
const { createCursor } = require('ghost-cursor-patchright');
|
|
2012
|
+
const cursor = await createCursor(page);
|
|
2013
|
+
await cursor.click(submitSelector);
|
|
2014
|
+
}
|
|
2008
2015
|
} catch (e) {
|
|
2009
2016
|
await page.click(submitSelector);
|
|
2010
2017
|
}
|
|
@@ -4766,13 +4773,18 @@ const handlers = {
|
|
|
4766
4773
|
}
|
|
4767
4774
|
}
|
|
4768
4775
|
}, identity, String(value));
|
|
4769
|
-
} else {
|
|
4770
4776
|
// Smart Type
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4777
|
+
if (page.realClick) {
|
|
4778
|
+
await page.realClick(identity);
|
|
4779
|
+
} else {
|
|
4780
|
+
try {
|
|
4781
|
+
const { createCursor } = require('ghost-cursor-patchright');
|
|
4782
|
+
const cursor = await createCursor(page);
|
|
4783
|
+
await cursor.click(identity);
|
|
4784
|
+
} catch (e) {
|
|
4785
|
+
await page.click(identity);
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4776
4788
|
|
|
4777
4789
|
// Clear existing
|
|
4778
4790
|
await page.evaluate(s => document.querySelector(s).value = '', identity);
|
|
@@ -4835,9 +4847,17 @@ const handlers = {
|
|
|
4835
4847
|
});
|
|
4836
4848
|
|
|
4837
4849
|
if (submitSelector) {
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4850
|
+
if (page.realClick) {
|
|
4851
|
+
await page.realClick(submitSelector);
|
|
4852
|
+
} else {
|
|
4853
|
+
try {
|
|
4854
|
+
const { createCursor } = require('ghost-cursor-patchright');
|
|
4855
|
+
const cursor = await createCursor(page);
|
|
4856
|
+
await cursor.click(submitSelector);
|
|
4857
|
+
} catch (e) {
|
|
4858
|
+
await page.click(submitSelector);
|
|
4859
|
+
}
|
|
4860
|
+
}
|
|
4841
4861
|
|
|
4842
4862
|
try {
|
|
4843
4863
|
await page.waitForNavigation({ timeout: 5000, waitUntil: 'domcontentloaded' });
|
package/test/cjs/test.js
CHANGED
|
@@ -4,7 +4,7 @@ const { connect } = require('../../lib/cjs/index.js');
|
|
|
4
4
|
|
|
5
5
|
const realBrowserOption = {
|
|
6
6
|
turnstile: true,
|
|
7
|
-
headless:
|
|
7
|
+
headless: false,
|
|
8
8
|
customConfig: {}
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -32,7 +32,9 @@ test.after(async () => {
|
|
|
32
32
|
|
|
33
33
|
test('DrissionPage Detector', async () => {
|
|
34
34
|
await page.goto("https://web.archive.org/web/20240913054632/https://drissionpage.pages.dev/", { timeout: 60000 });
|
|
35
|
+
await page.waitForSelector("#detector", { timeout: 20000 });
|
|
35
36
|
await page.realClick("#detector")
|
|
37
|
+
await page.waitForSelector("#isBot span", { timeout: 20000 });
|
|
36
38
|
let result = await page.evaluate(() => { return document.querySelector('#isBot span').textContent.includes("not") ? true : false })
|
|
37
39
|
assert.strictEqual(result, true, "DrissionPage Detector test failed!")
|
|
38
40
|
})
|
package/test/esm/test.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { connect } from '../../lib/esm/index.mjs';
|
|
|
4
4
|
|
|
5
5
|
const realBrowserOption = {
|
|
6
6
|
turnstile: true,
|
|
7
|
-
headless:
|
|
7
|
+
headless: false,
|
|
8
8
|
customConfig: {}
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -32,7 +32,9 @@ test.after(async () => {
|
|
|
32
32
|
|
|
33
33
|
test('DrissionPage Detector', async () => {
|
|
34
34
|
await page.goto("https://web.archive.org/web/20240913054632/https://drissionpage.pages.dev/", { timeout: 70000 });
|
|
35
|
+
await page.waitForSelector("#detector", { timeout: 20000 });
|
|
35
36
|
await page.realClick("#detector")
|
|
37
|
+
await page.waitForSelector("#isBot span", { timeout: 20000 });
|
|
36
38
|
let result = await page.evaluate(() => { return document.querySelector('#isBot span').textContent.includes("not") ? true : false })
|
|
37
39
|
assert.strictEqual(result, true, "DrissionPage Detector test failed!")
|
|
38
40
|
})
|
package/typings.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ declare module "real-browser-mcp-server" {
|
|
|
31
31
|
enableBlocker?: boolean;
|
|
32
32
|
/** Blocker configuration options */
|
|
33
33
|
blockerOptions?: BlockerOptions;
|
|
34
|
+
/** Enable visual cursor arrow overlay (defaults to true in headed/visible mode, false in headless mode) */
|
|
35
|
+
visualCursor?: boolean;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
interface ProxyOptions {
|