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.
Files changed (46) hide show
  1. package/dist/lib/cjs/index.js +384 -0
  2. package/{lib → dist/lib}/cjs/module/pageController.js +27 -29
  3. package/{lib → dist/lib}/cjs/module/turnstile.js +23 -12
  4. package/dist/src/ai/action-parser.js +229 -0
  5. package/dist/src/ai/core.js +367 -0
  6. package/dist/src/ai/element-finder.js +409 -0
  7. package/{src → dist/src}/ai/index.js +35 -50
  8. package/dist/src/ai/page-analyzer.js +264 -0
  9. package/dist/src/ai/selector-healer.js +215 -0
  10. package/dist/src/index.js +116 -0
  11. package/dist/src/mcp/handlers/browser.js +230 -0
  12. package/dist/src/mcp/handlers/dom.js +550 -0
  13. package/dist/src/mcp/handlers/extract.js +451 -0
  14. package/dist/src/mcp/handlers/helpers.js +514 -0
  15. package/dist/src/mcp/handlers/index.js +63 -0
  16. package/dist/src/mcp/handlers/misc.js +1224 -0
  17. package/dist/src/mcp/handlers/network.js +1134 -0
  18. package/dist/src/mcp/handlers/state.js +215 -0
  19. package/dist/src/mcp/handlers/vision.js +475 -0
  20. package/dist/src/mcp/index.js +166 -0
  21. package/dist/src/mcp/server.js +117 -0
  22. package/{src → dist/src}/mcp/tools.js +12 -11
  23. package/dist/src/shared/tools.js +598 -0
  24. package/{test → dist/test}/cjs/test.js +119 -169
  25. package/dist/test/mcp/smoke-test.js +131 -0
  26. package/lib/esm/module/pageController.mjs +21 -18
  27. package/lib/esm/module/turnstile.mjs +7 -0
  28. package/package.json +22 -11
  29. package/.github/ISSUE_TEMPLATE/general_issue.yaml +0 -58
  30. package/.github/SETUP.md +0 -111
  31. package/.github/workflows/publish.yml +0 -162
  32. package/Dockerfile +0 -78
  33. package/lib/cjs/adblocker.bin +0 -0
  34. package/lib/cjs/index.js +0 -396
  35. package/src/ai/action-parser.js +0 -269
  36. package/src/ai/core.js +0 -379
  37. package/src/ai/element-finder.js +0 -466
  38. package/src/ai/page-analyzer.js +0 -295
  39. package/src/ai/selector-healer.js +0 -236
  40. package/src/index.js +0 -128
  41. package/src/mcp/handlers.js +0 -5306
  42. package/src/mcp/index.js +0 -190
  43. package/src/mcp/server.js +0 -141
  44. package/src/shared/tools.js +0 -625
  45. package/test/esm/test.mjs +0 -299
  46. 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
+ };