real-browser-mcp-server 1.3.3 → 1.4.0
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 +96 -9
- package/dist/lib/cjs/index.d.ts +2 -0
- package/dist/lib/cjs/index.d.ts.map +1 -0
- package/dist/lib/cjs/index.js +386 -0
- package/dist/lib/cjs/index.js.map +1 -0
- package/dist/lib/cjs/module/pageController.d.ts +2 -0
- package/dist/lib/cjs/module/pageController.d.ts.map +1 -0
- package/{lib → dist/lib}/cjs/module/pageController.js +28 -29
- package/dist/lib/cjs/module/pageController.js.map +1 -0
- package/dist/lib/cjs/module/turnstile.d.ts +2 -0
- package/dist/lib/cjs/module/turnstile.d.ts.map +1 -0
- package/{lib → dist/lib}/cjs/module/turnstile.js +24 -12
- package/dist/lib/cjs/module/turnstile.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +118 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp/handlers/browser.d.ts +30 -0
- package/dist/src/mcp/handlers/browser.d.ts.map +1 -0
- package/dist/src/mcp/handlers/browser.js +232 -0
- package/dist/src/mcp/handlers/browser.js.map +1 -0
- package/dist/src/mcp/handlers/dom.d.ts +149 -0
- package/dist/src/mcp/handlers/dom.d.ts.map +1 -0
- package/dist/src/mcp/handlers/dom.js +577 -0
- package/dist/src/mcp/handlers/dom.js.map +1 -0
- package/dist/src/mcp/handlers/extract.d.ts +59 -0
- package/dist/src/mcp/handlers/extract.d.ts.map +1 -0
- package/dist/src/mcp/handlers/extract.js +455 -0
- package/dist/src/mcp/handlers/extract.js.map +1 -0
- package/dist/src/mcp/handlers/form-handlers.d.ts +9 -0
- package/dist/src/mcp/handlers/form-handlers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/form-handlers.js +56 -0
- package/dist/src/mcp/handlers/form-handlers.js.map +1 -0
- package/dist/src/mcp/handlers/helpers.d.ts +47 -0
- package/dist/src/mcp/handlers/helpers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/helpers.js +515 -0
- package/dist/src/mcp/handlers/helpers.js.map +1 -0
- package/dist/src/mcp/handlers/index.d.ts +6 -0
- package/dist/src/mcp/handlers/index.d.ts.map +1 -0
- package/dist/src/mcp/handlers/index.js +61 -0
- package/dist/src/mcp/handlers/index.js.map +1 -0
- package/dist/src/mcp/handlers/media-handlers.d.ts +10 -0
- package/dist/src/mcp/handlers/media-handlers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/media-handlers.js +535 -0
- package/dist/src/mcp/handlers/media-handlers.js.map +1 -0
- package/dist/src/mcp/handlers/network.d.ts +147 -0
- package/dist/src/mcp/handlers/network.d.ts.map +1 -0
- package/dist/src/mcp/handlers/network.js +1135 -0
- package/dist/src/mcp/handlers/network.js.map +1 -0
- package/dist/src/mcp/handlers/state.d.ts +34 -0
- package/dist/src/mcp/handlers/state.d.ts.map +1 -0
- package/dist/src/mcp/handlers/state.js +226 -0
- package/dist/src/mcp/handlers/state.js.map +1 -0
- package/dist/src/mcp/handlers/utility-handlers.d.ts +167 -0
- package/dist/src/mcp/handlers/utility-handlers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/utility-handlers.js +280 -0
- package/dist/src/mcp/handlers/utility-handlers.js.map +1 -0
- package/dist/src/mcp/handlers/vision.d.ts +127 -0
- package/dist/src/mcp/handlers/vision.d.ts.map +1 -0
- package/dist/src/mcp/handlers/vision.js +549 -0
- package/dist/src/mcp/handlers/vision.js.map +1 -0
- package/dist/src/mcp/index.d.ts +3 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +166 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/mcp/server.d.ts +2 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +117 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools.d.ts +8 -0
- package/dist/src/mcp/tools.d.ts.map +1 -0
- package/{src → dist/src}/mcp/tools.js +12 -11
- package/dist/src/mcp/tools.js.map +1 -0
- package/dist/src/shared/cache-manager.d.ts +80 -0
- package/dist/src/shared/cache-manager.d.ts.map +1 -0
- package/dist/src/shared/cache-manager.js +221 -0
- package/dist/src/shared/cache-manager.js.map +1 -0
- package/dist/src/shared/tools.d.ts +2 -0
- package/dist/src/shared/tools.d.ts.map +1 -0
- package/dist/src/shared/tools.js +606 -0
- package/dist/src/shared/tools.js.map +1 -0
- package/dist/src/types.d.ts +376 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +9 -0
- package/dist/src/types.js.map +1 -0
- package/dist/test/cjs/test.d.ts +11 -0
- package/dist/test/cjs/test.d.ts.map +1 -0
- package/dist/test/cjs/test.js +289 -0
- package/dist/test/cjs/test.js.map +1 -0
- package/dist/test/mcp/smoke-test.d.ts +29 -0
- package/dist/test/mcp/smoke-test.d.ts.map +1 -0
- package/dist/test/mcp/smoke-test.js +132 -0
- package/dist/test/mcp/smoke-test.js.map +1 -0
- package/lib/esm/index.mjs +230 -154
- package/lib/esm/module/pageController.mjs +21 -18
- package/lib/esm/module/turnstile.mjs +7 -0
- package/package.json +25 -16
- package/typings.d.ts +7 -1
- package/.github/ISSUE_TEMPLATE/general_issue.yaml +0 -58
- package/.github/SETUP.md +0 -111
- package/.github/workflows/publish.yml +0 -135
- package/Dockerfile +0 -79
- package/lib/cjs/adblocker.bin +0 -0
- package/lib/cjs/index.js +0 -321
- package/src/ai/action-parser.js +0 -274
- package/src/ai/core.js +0 -378
- package/src/ai/element-finder.js +0 -466
- package/src/ai/index.js +0 -82
- package/src/ai/page-analyzer.js +0 -304
- package/src/ai/selector-healer.js +0 -236
- package/src/index.js +0 -121
- package/src/mcp/handlers.js +0 -5071
- package/src/mcp/index.js +0 -190
- package/src/mcp/server.js +0 -144
- package/src/shared/tools.js +0 -618
- package/test/cjs/test.js +0 -259
- package/test/esm/package.json +0 -13
- package/test/esm/test.js +0 -226
package/lib/esm/index.mjs
CHANGED
|
@@ -3,9 +3,9 @@ import { createCursor } from "ghost-cursor-patchright";
|
|
|
3
3
|
import { PlaywrightBlocker } from "@ghostery/adblocker-playwright";
|
|
4
4
|
import { pageController } from "./module/pageController.mjs";
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import Xvfb from "xvfb";
|
|
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 =
|
|
75
|
-
|
|
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
|
|
79
|
-
if (page.
|
|
80
|
-
page.
|
|
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,115 +105,164 @@ function applyBrowserShims(browser, page) {
|
|
|
90
105
|
});
|
|
91
106
|
}
|
|
92
107
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
146
|
+
await cursor.actions.click({ target: selector, ...options });
|
|
127
147
|
} catch (e) {
|
|
128
|
-
//
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
+
];
|
|
144
186
|
|
|
145
|
-
|
|
146
|
-
move: async (selector, options = {}) => {
|
|
187
|
+
for (const cmd of regQueries) {
|
|
147
188
|
try {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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;
|
|
153
200
|
}
|
|
154
|
-
} catch (e) {
|
|
155
|
-
// Silently fallback if element is not found
|
|
156
|
-
try {
|
|
157
|
-
await page.hover(selector, options);
|
|
158
|
-
} catch (err) {}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
page.realClick = async (selector, options = {}) => {
|
|
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);
|
|
201
|
+
} catch (e) {}
|
|
172
202
|
}
|
|
173
|
-
};
|
|
174
203
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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;
|
|
183
214
|
}
|
|
184
|
-
|
|
185
|
-
|
|
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) {}
|
|
186
221
|
}
|
|
187
222
|
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
+
];
|
|
193
245
|
}
|
|
194
246
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return
|
|
198
|
-
}
|
|
247
|
+
for (const p of paths) {
|
|
248
|
+
if (p && fs.existsSync(p)) {
|
|
249
|
+
return p;
|
|
250
|
+
}
|
|
199
251
|
}
|
|
200
252
|
|
|
201
|
-
return
|
|
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
|
+
}
|
|
202
266
|
}
|
|
203
267
|
|
|
204
268
|
export async function connect({
|
|
@@ -206,21 +270,8 @@ export async function connect({
|
|
|
206
270
|
headless = getDefaultHeadless(),
|
|
207
271
|
proxy = {},
|
|
208
272
|
turnstile = false,
|
|
209
|
-
|
|
273
|
+
executablePath = undefined,
|
|
210
274
|
} = {}) {
|
|
211
|
-
let xvfbInstance = null;
|
|
212
|
-
if (process.platform === 'linux' && !process.env.DISPLAY && !disableXvfb) {
|
|
213
|
-
try {
|
|
214
|
-
xvfbInstance = new Xvfb({
|
|
215
|
-
silent: true,
|
|
216
|
-
xvfb_args: ['-screen', '0', '1280x1024x24', '-ac']
|
|
217
|
-
});
|
|
218
|
-
xvfbInstance.startSync();
|
|
219
|
-
} catch (err) {
|
|
220
|
-
console.error('[xvfb] Failed to start Xvfb server automatically:', err.message);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
275
|
let playwrightProxy = undefined;
|
|
225
276
|
if (proxy && proxy.host && proxy.port) {
|
|
226
277
|
playwrightProxy = {
|
|
@@ -232,60 +283,82 @@ export async function connect({
|
|
|
232
283
|
}
|
|
233
284
|
}
|
|
234
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
|
+
|
|
235
338
|
const chromiumArgs = [
|
|
339
|
+
`--user-agent=${modifiedUa}`,
|
|
236
340
|
'--disable-blink-features=AutomationControlled',
|
|
237
341
|
'--no-sandbox',
|
|
238
342
|
'--disable-setuid-sandbox',
|
|
239
|
-
'--window-size=1920,1080',
|
|
240
343
|
...args
|
|
241
344
|
];
|
|
242
345
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
let browser;
|
|
252
|
-
try {
|
|
253
|
-
// Try launching using the system's real installed Google Chrome first for maximum stealth/fingerprint integrity
|
|
254
|
-
browser = await chromium.launch({
|
|
255
|
-
headless,
|
|
256
|
-
args: chromiumArgs,
|
|
257
|
-
proxy: playwrightProxy,
|
|
258
|
-
channel: 'chrome'
|
|
259
|
-
});
|
|
260
|
-
console.error('[Launch] Successfully launched using system Google Chrome channel');
|
|
261
|
-
} catch (e) {
|
|
262
|
-
// Fallback to pre-packaged Chromium if Chrome is not installed
|
|
263
|
-
browser = await chromium.launch({
|
|
264
|
-
headless,
|
|
265
|
-
args: chromiumArgs,
|
|
266
|
-
proxy: playwrightProxy
|
|
267
|
-
});
|
|
268
|
-
console.error('[Launch] Google Chrome channel not available, falling back to pre-packaged Chromium');
|
|
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
|
+
}
|
|
269
354
|
}
|
|
270
355
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
const originalClose = browser.close.bind(browser);
|
|
280
|
-
browser.close = async () => {
|
|
281
|
-
await originalClose();
|
|
282
|
-
try {
|
|
283
|
-
xvfbInstance.stopSync();
|
|
284
|
-
} catch (e) {
|
|
285
|
-
console.error('[xvfb] Failed to stop Xvfb server during close:', e.message);
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
}
|
|
356
|
+
const browser = await chromium.launch({
|
|
357
|
+
headless: launchHeadless,
|
|
358
|
+
args: chromiumArgs,
|
|
359
|
+
proxy: playwrightProxy,
|
|
360
|
+
...(executablePath ? { executablePath } : {}),
|
|
361
|
+
});
|
|
289
362
|
|
|
290
363
|
// Ensure ad blocker is ready
|
|
291
364
|
await getAdBlocker();
|
|
@@ -296,7 +369,9 @@ export async function connect({
|
|
|
296
369
|
|
|
297
370
|
let page = await context.newPage();
|
|
298
371
|
|
|
299
|
-
|
|
372
|
+
await applyUserAgentOverride(page, modifiedUa, userAgentMetadata);
|
|
373
|
+
|
|
374
|
+
setupRealPage(browser, page);
|
|
300
375
|
|
|
301
376
|
page = await pageController({
|
|
302
377
|
browser,
|
|
@@ -306,7 +381,8 @@ export async function connect({
|
|
|
306
381
|
});
|
|
307
382
|
|
|
308
383
|
context.on('page', async (newPage) => {
|
|
309
|
-
|
|
384
|
+
await applyUserAgentOverride(newPage, modifiedUa, userAgentMetadata);
|
|
385
|
+
setupRealPage(browser, newPage);
|
|
310
386
|
await pageController({
|
|
311
387
|
browser,
|
|
312
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.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
url.
|
|
34
|
-
url
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
}
|
|
44
|
-
|
|
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.
|
|
3
|
+
"version": "1.4.0",
|
|
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
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
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.
|
|
30
|
-
"cjs_test": "node ./test/cjs/test.js",
|
|
31
|
-
"
|
|
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,8 +51,11 @@
|
|
|
45
51
|
"dependencies": {
|
|
46
52
|
"@ghostery/adblocker-playwright": "latest",
|
|
47
53
|
"@modelcontextprotocol/sdk": "latest",
|
|
48
|
-
"patchright": "
|
|
49
|
-
"
|
|
50
|
-
|
|
54
|
+
"ghost-cursor-patchright": "^1.0.2",
|
|
55
|
+
"patchright": "latest"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^25.9.2",
|
|
59
|
+
"typescript": "^6.0.3"
|
|
51
60
|
}
|
|
52
61
|
}
|
package/typings.d.ts
CHANGED
|
@@ -24,8 +24,9 @@ declare module "real-browser-mcp-server" {
|
|
|
24
24
|
turnstile?: boolean;
|
|
25
25
|
connectOption?: any;
|
|
26
26
|
disableXvfb?: boolean;
|
|
27
|
-
plugins?: any[];
|
|
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 "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
|
+
|