real-browser-mcp-server 1.1.7 → 1.1.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/dist/lib/cjs/index.js +384 -0
- package/{lib → dist/lib}/cjs/module/pageController.js +27 -29
- package/{lib → dist/lib}/cjs/module/turnstile.js +23 -12
- package/dist/src/ai/action-parser.js +229 -0
- package/dist/src/ai/core.js +367 -0
- package/dist/src/ai/element-finder.js +409 -0
- package/{src → dist/src}/ai/index.js +35 -50
- package/dist/src/ai/page-analyzer.js +264 -0
- package/dist/src/ai/selector-healer.js +215 -0
- package/dist/src/index.js +116 -0
- package/dist/src/mcp/handlers/browser.js +230 -0
- package/dist/src/mcp/handlers/dom.js +550 -0
- package/dist/src/mcp/handlers/extract.js +451 -0
- package/dist/src/mcp/handlers/helpers.js +514 -0
- package/dist/src/mcp/handlers/index.js +63 -0
- package/dist/src/mcp/handlers/misc.js +1224 -0
- package/dist/src/mcp/handlers/network.js +1134 -0
- package/dist/src/mcp/handlers/state.js +215 -0
- package/dist/src/mcp/handlers/vision.js +475 -0
- package/dist/src/mcp/index.js +166 -0
- package/dist/src/mcp/server.js +117 -0
- package/{src → dist/src}/mcp/tools.js +12 -11
- package/dist/src/shared/tools.js +598 -0
- package/{test → dist/test}/cjs/test.js +119 -169
- package/dist/test/mcp/smoke-test.js +131 -0
- package/lib/esm/module/pageController.mjs +21 -18
- package/lib/esm/module/turnstile.mjs +7 -0
- package/package.json +22 -11
- package/.github/ISSUE_TEMPLATE/general_issue.yaml +0 -58
- package/.github/SETUP.md +0 -111
- package/.github/workflows/publish.yml +0 -162
- package/Dockerfile +0 -78
- package/lib/cjs/adblocker.bin +0 -0
- package/lib/cjs/index.js +0 -396
- package/src/ai/action-parser.js +0 -269
- package/src/ai/core.js +0 -379
- package/src/ai/element-finder.js +0 -466
- package/src/ai/page-analyzer.js +0 -295
- package/src/ai/selector-healer.js +0 -236
- package/src/index.js +0 -128
- package/src/mcp/handlers.js +0 -5306
- package/src/mcp/index.js +0 -190
- package/src/mcp/server.js +0 -141
- package/src/shared/tools.js +0 -625
- package/test/esm/test.mjs +0 -299
- package/test/mcp/smoke-test.js +0 -141
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.browserHandlers = void 0;
|
|
4
|
+
const state_1 = require("./state");
|
|
5
|
+
// Auto-generated browser handlers
|
|
6
|
+
exports.browserHandlers = {
|
|
7
|
+
async browser_init(params = {}) {
|
|
8
|
+
(0, state_1.notifyProgress)('browser_init', 'started', 'Initializing browser...');
|
|
9
|
+
const { connect } = require('../../../lib/cjs/index.js');
|
|
10
|
+
// Get headless from params OR environment variable
|
|
11
|
+
const envHeadless = (0, state_1.getHeadlessFromEnv)();
|
|
12
|
+
const headless = params.headless !== undefined ? params.headless : envHeadless;
|
|
13
|
+
const { proxy = {}, turnstile = false, enableBlocker = true } = params;
|
|
14
|
+
(0, state_1.notifyProgress)('browser_init', 'progress', `Mode: ${headless ? 'Headless' : 'GUI (Visible)'}`, { headless });
|
|
15
|
+
const result = await connect({
|
|
16
|
+
headless,
|
|
17
|
+
proxy,
|
|
18
|
+
turnstile,
|
|
19
|
+
enableBlocker,
|
|
20
|
+
});
|
|
21
|
+
state_1.state.browserInstance = result.browser;
|
|
22
|
+
state_1.state.pageInstance = result.page;
|
|
23
|
+
state_1.state.blockerInstance = result.blocker;
|
|
24
|
+
state_1.state.setupPageFn = result.setupPage; // Store CDP early injection function
|
|
25
|
+
// ═══════════════════════════════════════════════════════════════
|
|
26
|
+
// GLOBAL DIALOG HANDLER - Auto-handle dialogs
|
|
27
|
+
// Logic: BLOCK redirects to external sites, ACCEPT everything else
|
|
28
|
+
// ═══════════════════════════════════════════════════════════════
|
|
29
|
+
state_1.state.pageInstance.on('dialog', async (dialog) => {
|
|
30
|
+
const dialogType = dialog.type();
|
|
31
|
+
const msg = dialog.message().toLowerCase();
|
|
32
|
+
(0, state_1.notifyProgress)('browser_init', 'progress', `🔔 Handling dialog: ${dialogType} - ${dialog.message().substring(0, 100)}...`);
|
|
33
|
+
try {
|
|
34
|
+
// Critical Fix: BLOCK redirects to external sites (e.g., eCommittee)
|
|
35
|
+
// These redirects take the user away from the search page
|
|
36
|
+
if (msg.includes('redirect') || msg.includes('external') || msg.includes('leaving')) {
|
|
37
|
+
console.error('🚫 Blocking redirect dialog (Dismiss)');
|
|
38
|
+
await dialog.dismiss(); // Simulate clicking 'Cancel'
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Auto-accept other dialogs (like alerts or simple confirmations)
|
|
42
|
+
await dialog.accept(); // Simulate clicking 'OK'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
// Ignore errors (dialog might be closed by injected script)
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
// ═══════════════════════════════════════════════════════════════
|
|
50
|
+
// INJECTED SCRIPT - Silent Handling of Popups
|
|
51
|
+
// Override window.confirm/alert to handle them inside the page context
|
|
52
|
+
// Note: Using addInitScript (Playwright) to intercept popups early
|
|
53
|
+
// ═══════════════════════════════════════════════════════════════
|
|
54
|
+
await state_1.state.pageInstance.addInitScript(() => {
|
|
55
|
+
window.originalConfirm = window.confirm;
|
|
56
|
+
window.originalAlert = window.alert;
|
|
57
|
+
// Smart Confirm Handler
|
|
58
|
+
window.confirm = (msg) => {
|
|
59
|
+
console.log('Intercepted Confirm Dialog:', msg);
|
|
60
|
+
if (msg && (msg.toLowerCase().includes('redirect') || msg.toLowerCase().includes('external'))) {
|
|
61
|
+
console.log('🚫 Blocking redirect confirmation inside page');
|
|
62
|
+
return false; // Return FALSE = Click Cancel
|
|
63
|
+
}
|
|
64
|
+
return true; // Return TRUE = Click OK
|
|
65
|
+
};
|
|
66
|
+
// Silently ignore alerts (always OK)
|
|
67
|
+
window.alert = (msg) => {
|
|
68
|
+
console.log('Blocked Alert Dialog:', msg);
|
|
69
|
+
return true;
|
|
70
|
+
};
|
|
71
|
+
// Silently return null for prompts
|
|
72
|
+
window.prompt = (msg) => {
|
|
73
|
+
console.log('Blocked Prompt Dialog:', msg);
|
|
74
|
+
return null;
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
const pid = (typeof state_1.state.browserInstance.process === 'function') ? state_1.state.browserInstance.process()?.pid : null;
|
|
78
|
+
(0, state_1.notifyProgress)('browser_init', 'completed', `Browser started (PID: ${pid})`, {
|
|
79
|
+
headless,
|
|
80
|
+
pid,
|
|
81
|
+
blockerEnabled: enableBlocker
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
success: true,
|
|
85
|
+
message: `Browser initialized in ${headless ? 'headless' : 'GUI'} mode`,
|
|
86
|
+
pid,
|
|
87
|
+
headless,
|
|
88
|
+
blockerEnabled: enableBlocker
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
async navigate(params) {
|
|
92
|
+
const { page } = (0, state_1.requireBrowser)();
|
|
93
|
+
let { url, waitUntil = 'networkidle', timeout = 30000, retries = 2 } = params;
|
|
94
|
+
// Playwright/Patchright wait states: load | domcontentloaded | networkidle | commit
|
|
95
|
+
waitUntil = (0, state_1.resolveWaitUntil)(waitUntil);
|
|
96
|
+
(0, state_1.notifyProgress)('navigate', 'started', `Navigating to: ${url}`);
|
|
97
|
+
// ═══════════════════════════════════════════════════════════════
|
|
98
|
+
// CDP EARLY INJECTION - Setup BEFORE navigation for better ad blocking
|
|
99
|
+
// This ensures CSS and scripts are injected before page scripts run
|
|
100
|
+
// ═══════════════════════════════════════════════════════════════
|
|
101
|
+
console.error('[Navigate] state.setupPageFn available:', !!state_1.state.setupPageFn);
|
|
102
|
+
if (state_1.state.setupPageFn) {
|
|
103
|
+
try {
|
|
104
|
+
await state_1.state.setupPageFn(page);
|
|
105
|
+
(0, state_1.notifyProgress)('navigate', 'progress', 'CDP early injection setup complete');
|
|
106
|
+
console.error('[Navigate] CDP early injection SUCCESS');
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
// Non-critical error, continue navigation
|
|
110
|
+
console.error('[Navigate] CDP early injection failed:', e.message);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
console.error('[Navigate] No state.setupPageFn available - CDP early injection skipped');
|
|
115
|
+
}
|
|
116
|
+
let lastError = null;
|
|
117
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
118
|
+
try {
|
|
119
|
+
// Wait a bit if this is a retry
|
|
120
|
+
if (attempt > 0) {
|
|
121
|
+
(0, state_1.notifyProgress)('navigate', 'progress', `Retry attempt ${attempt}...`);
|
|
122
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
123
|
+
}
|
|
124
|
+
await page.goto(url, { waitUntil, timeout });
|
|
125
|
+
// Wait for page to stabilize after navigation
|
|
126
|
+
await new Promise(r => setTimeout(r, 500));
|
|
127
|
+
// Try to get title with error handling
|
|
128
|
+
let title = '';
|
|
129
|
+
try {
|
|
130
|
+
title = await page.title();
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
// Title might fail if page is still loading
|
|
134
|
+
title = 'Loading...';
|
|
135
|
+
}
|
|
136
|
+
(0, state_1.notifyProgress)('navigate', 'completed', `Loaded: ${title}`, { url: page.url(), title });
|
|
137
|
+
return {
|
|
138
|
+
success: true,
|
|
139
|
+
url: page.url(),
|
|
140
|
+
title
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
lastError = error;
|
|
145
|
+
// Handle specific errors that might be recoverable
|
|
146
|
+
if (error.message?.includes('Execution context was destroyed') ||
|
|
147
|
+
error.message?.includes('context') ||
|
|
148
|
+
error.message?.includes('Target closed')) {
|
|
149
|
+
(0, state_1.notifyProgress)('navigate', 'progress', `Navigation interrupted (${error.message.substring(0, 50)}...), waiting for page...`);
|
|
150
|
+
// Wait for any ongoing navigation to complete
|
|
151
|
+
try {
|
|
152
|
+
await page.waitForNavigation({ timeout: 5000, waitUntil: 'domcontentloaded' }).catch(() => { });
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
// Ignore timeout
|
|
156
|
+
}
|
|
157
|
+
// Check if we actually landed on the page
|
|
158
|
+
try {
|
|
159
|
+
const currentUrl = page.url();
|
|
160
|
+
if (currentUrl && currentUrl !== 'about:blank') {
|
|
161
|
+
const title = await page.title().catch(() => 'Unknown');
|
|
162
|
+
(0, state_1.notifyProgress)('navigate', 'completed', `Loaded after recovery: ${title}`, { url: currentUrl, title });
|
|
163
|
+
return {
|
|
164
|
+
success: true,
|
|
165
|
+
url: currentUrl,
|
|
166
|
+
title,
|
|
167
|
+
recovered: true
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
// Continue to retry
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
// Non-recoverable error, throw immediately
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// All retries failed
|
|
182
|
+
(0, state_1.notifyProgress)('navigate', 'error', `Navigation failed after ${retries + 1} attempts: ${lastError?.message}`);
|
|
183
|
+
throw lastError || new Error('Navigation failed');
|
|
184
|
+
},
|
|
185
|
+
async wait(params) {
|
|
186
|
+
const { page } = (0, state_1.requireBrowser)();
|
|
187
|
+
const { type = 'timeout', value, timeout = 30000 } = params;
|
|
188
|
+
(0, state_1.notifyProgress)('wait', 'started', `Waiting for ${type}: ${value}`);
|
|
189
|
+
switch (type) {
|
|
190
|
+
case 'selector':
|
|
191
|
+
await page.waitForSelector(value, { timeout });
|
|
192
|
+
break;
|
|
193
|
+
case 'navigation':
|
|
194
|
+
await page.waitForNavigation({ timeout });
|
|
195
|
+
break;
|
|
196
|
+
case 'networkidle':
|
|
197
|
+
await page.waitForLoadState('networkidle', { timeout });
|
|
198
|
+
break;
|
|
199
|
+
case 'timeout':
|
|
200
|
+
default:
|
|
201
|
+
await new Promise(r => setTimeout(r, parseInt(value) || 1000));
|
|
202
|
+
}
|
|
203
|
+
(0, state_1.notifyProgress)('wait', 'completed', `Wait completed: ${type}`, { type, value });
|
|
204
|
+
return { success: true, type, value };
|
|
205
|
+
},
|
|
206
|
+
async browser_close(params = {}) {
|
|
207
|
+
const { force = false } = params;
|
|
208
|
+
(0, state_1.notifyProgress)('browser_close', 'started', 'Closing browser...');
|
|
209
|
+
if (state_1.state.browserInstance) {
|
|
210
|
+
try {
|
|
211
|
+
await state_1.state.browserInstance.close();
|
|
212
|
+
(0, state_1.notifyProgress)('browser_close', 'progress', 'Browser closed gracefully');
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
if (force) {
|
|
216
|
+
if (typeof state_1.state.browserInstance.process === 'function') {
|
|
217
|
+
state_1.state.browserInstance.process()?.kill('SIGKILL');
|
|
218
|
+
}
|
|
219
|
+
(0, state_1.notifyProgress)('browser_close', 'progress', 'Browser force killed');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
state_1.state.browserInstance = null;
|
|
223
|
+
state_1.state.pageInstance = null;
|
|
224
|
+
state_1.state.blockerInstance = null;
|
|
225
|
+
state_1.state.setupPageFn = null;
|
|
226
|
+
}
|
|
227
|
+
(0, state_1.notifyProgress)('browser_close', 'completed', 'Browser closed');
|
|
228
|
+
return { success: true, message: 'Browser closed' };
|
|
229
|
+
}
|
|
230
|
+
};
|