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,409 @@
1
+ "use strict";
2
+ // @ts-nocheck
3
+ /**
4
+ * AI Element Finder - Smart element finding with multiple strategies
5
+ *
6
+ * Strategies:
7
+ * - text: Find by visible text content
8
+ * - aria: Find by accessibility attributes (aria-label, role, etc.)
9
+ * - visual: Find by position, size, color context
10
+ * - semantic: Find by HTML structure and semantic meaning
11
+ * - auto: Try all strategies and return best matches
12
+ */
13
+ class ElementFinder {
14
+ constructor() {
15
+ // Common element patterns for different types
16
+ this.patterns = {
17
+ button: {
18
+ tags: ['button', 'input[type="button"]', 'input[type="submit"]', 'a.btn', '[role="button"]'],
19
+ keywords: ['click', 'submit', 'button', 'btn', 'press']
20
+ },
21
+ input: {
22
+ tags: ['input', 'textarea', '[contenteditable="true"]'],
23
+ keywords: ['input', 'field', 'textbox', 'enter', 'type', 'write']
24
+ },
25
+ link: {
26
+ tags: ['a[href]', '[role="link"]'],
27
+ keywords: ['link', 'go to', 'navigate', 'open']
28
+ },
29
+ search: {
30
+ tags: ['input[type="search"]', 'input[name*="search"]', 'input[placeholder*="search"]', '[role="searchbox"]'],
31
+ keywords: ['search', 'find', 'lookup', 'query']
32
+ },
33
+ login: {
34
+ tags: ['input[type="password"]', 'input[name*="password"]', 'input[name*="login"]', 'input[name*="user"]'],
35
+ keywords: ['login', 'password', 'username', 'email', 'signin', 'sign in']
36
+ },
37
+ form: {
38
+ tags: ['form', '[role="form"]'],
39
+ keywords: ['form', 'submit']
40
+ },
41
+ menu: {
42
+ tags: ['nav', '[role="navigation"]', '[role="menu"]', 'ul.menu', '.nav'],
43
+ keywords: ['menu', 'navigation', 'nav']
44
+ },
45
+ image: {
46
+ tags: ['img', 'picture', '[role="img"]', 'svg'],
47
+ keywords: ['image', 'picture', 'photo', 'icon', 'logo']
48
+ },
49
+ video: {
50
+ tags: ['video', 'iframe[src*="youtube"]', 'iframe[src*="vimeo"]', '[role="video"]'],
51
+ keywords: ['video', 'player', 'watch']
52
+ }
53
+ };
54
+ // Common action keywords
55
+ this.actionKeywords = {
56
+ click: ['click', 'press', 'tap', 'hit', 'select', 'choose'],
57
+ type: ['type', 'enter', 'write', 'input', 'fill'],
58
+ scroll: ['scroll', 'move', 'go down', 'go up'],
59
+ hover: ['hover', 'mouse over', 'point'],
60
+ wait: ['wait', 'pause', 'delay']
61
+ };
62
+ }
63
+ /**
64
+ * Find elements matching the query
65
+ */
66
+ async find(page, query, options = {}) {
67
+ const { strategy = 'auto', context = null, confidence = 0.7, returnMultiple = false } = options;
68
+ let results = [];
69
+ // Parse query to understand what user is looking for
70
+ const parsedQuery = this.parseQuery(query);
71
+ // Apply different strategies
72
+ if (strategy === 'auto' || strategy === 'text') {
73
+ const textResults = await this.findByText(page, parsedQuery, context);
74
+ results = [...results, ...textResults];
75
+ }
76
+ if (strategy === 'auto' || strategy === 'aria') {
77
+ const ariaResults = await this.findByAria(page, parsedQuery, context);
78
+ results = [...results, ...ariaResults];
79
+ }
80
+ if (strategy === 'auto' || strategy === 'semantic') {
81
+ const semanticResults = await this.findBySemantic(page, parsedQuery, context);
82
+ results = [...results, ...semanticResults];
83
+ }
84
+ if (strategy === 'auto' || strategy === 'visual') {
85
+ const visualResults = await this.findByVisual(page, parsedQuery, context);
86
+ results = [...results, ...visualResults];
87
+ }
88
+ // Deduplicate and sort by confidence
89
+ results = this.deduplicateResults(results);
90
+ results.sort((a, b) => b.confidence - a.confidence);
91
+ // Filter by confidence threshold
92
+ results = results.filter(r => r.confidence >= confidence);
93
+ return returnMultiple ? results : results.slice(0, 1);
94
+ }
95
+ /**
96
+ * Parse natural language query
97
+ */
98
+ parseQuery(query) {
99
+ const lowerQuery = query.toLowerCase();
100
+ // Detect element type
101
+ let elementType = 'any';
102
+ for (const [type, pattern] of Object.entries(this.patterns)) {
103
+ if (pattern.keywords.some(kw => lowerQuery.includes(kw))) {
104
+ elementType = type;
105
+ break;
106
+ }
107
+ }
108
+ // Extract key terms
109
+ const terms = query.split(/\s+/).filter(t => t.length > 2);
110
+ // Detect if looking for specific text
111
+ const textMatch = query.match(/["']([^"']+)["']|containing\s+(.+)|with text\s+(.+)|labeled\s+(.+)/i);
112
+ const targetText = textMatch ? (textMatch[1] || textMatch[2] || textMatch[3] || textMatch[4]) : null;
113
+ // Detect color mentions
114
+ const colorMatch = query.match(/\b(red|blue|green|yellow|orange|purple|black|white|gray|grey)\b/i);
115
+ const color = colorMatch ? colorMatch[1].toLowerCase() : null;
116
+ // Detect position mentions
117
+ const positionMatch = query.match(/\b(top|bottom|left|right|center|first|last|header|footer)\b/i);
118
+ const position = positionMatch ? positionMatch[1].toLowerCase() : null;
119
+ return {
120
+ original: query,
121
+ elementType,
122
+ terms,
123
+ targetText,
124
+ color,
125
+ position
126
+ };
127
+ }
128
+ /**
129
+ * Find by text content
130
+ */
131
+ async findByText(page, parsedQuery, context) {
132
+ const contextSelector = context || 'body';
133
+ return await page.evaluate(({ parsedQuery, contextSelector }) => {
134
+ const results = [];
135
+ const container = document.querySelector(contextSelector) || document.body;
136
+ // Get all interactive and text-containing elements
137
+ const elements = container.querySelectorAll('button, a, input, label, span, div, p, h1, h2, h3, h4, h5, h6, [role]');
138
+ for (const el of elements) {
139
+ const text = el.textContent?.trim().toLowerCase() || '';
140
+ const placeholder = el.placeholder?.toLowerCase() || '';
141
+ const value = el.value?.toLowerCase() || '';
142
+ const ariaLabel = el.getAttribute('aria-label')?.toLowerCase() || '';
143
+ let confidence = 0;
144
+ const matchedTerms = [];
145
+ // Check each search term
146
+ for (const term of parsedQuery.terms) {
147
+ const lowerTerm = term.toLowerCase();
148
+ if (text.includes(lowerTerm)) {
149
+ confidence += 0.3;
150
+ matchedTerms.push({ term, in: 'text' });
151
+ }
152
+ if (placeholder.includes(lowerTerm)) {
153
+ confidence += 0.25;
154
+ matchedTerms.push({ term, in: 'placeholder' });
155
+ }
156
+ if (ariaLabel.includes(lowerTerm)) {
157
+ confidence += 0.25;
158
+ matchedTerms.push({ term, in: 'aria-label' });
159
+ }
160
+ }
161
+ // Exact text match bonus
162
+ if (parsedQuery.targetText && text.includes(parsedQuery.targetText.toLowerCase())) {
163
+ confidence += 0.4;
164
+ }
165
+ // Visibility bonus
166
+ const rect = el.getBoundingClientRect();
167
+ if (rect.width > 0 && rect.height > 0) {
168
+ confidence += 0.1;
169
+ }
170
+ if (confidence > 0) {
171
+ // Generate selector
172
+ let selector = '';
173
+ if (el.id) {
174
+ selector = `#${el.id}`;
175
+ }
176
+ else if (el.className && typeof el.className === 'string') {
177
+ const classes = el.className.split(' ').filter(c => c).slice(0, 2).join('.');
178
+ selector = `${el.tagName.toLowerCase()}.${classes}`;
179
+ }
180
+ else {
181
+ selector = el.tagName.toLowerCase();
182
+ if (el.getAttribute('type')) {
183
+ selector += `[type="${el.getAttribute('type')}"]`;
184
+ }
185
+ }
186
+ results.push({
187
+ selector,
188
+ confidence: Math.min(confidence, 1),
189
+ text: text.substring(0, 100),
190
+ strategy: 'text',
191
+ matchedTerms,
192
+ tag: el.tagName.toLowerCase(),
193
+ visible: rect.width > 0 && rect.height > 0
194
+ });
195
+ }
196
+ }
197
+ return results;
198
+ }, { parsedQuery, contextSelector });
199
+ }
200
+ /**
201
+ * Find by ARIA attributes
202
+ */
203
+ async findByAria(page, parsedQuery, context) {
204
+ const contextSelector = context || 'body';
205
+ return await page.evaluate(({ parsedQuery, contextSelector }) => {
206
+ const results = [];
207
+ const container = document.querySelector(contextSelector) || document.body;
208
+ // Find elements with ARIA attributes
209
+ const ariaElements = container.querySelectorAll('[aria-label], [aria-describedby], [role], [aria-placeholder]');
210
+ for (const el of ariaElements) {
211
+ const ariaLabel = el.getAttribute('aria-label')?.toLowerCase() || '';
212
+ const role = el.getAttribute('role')?.toLowerCase() || '';
213
+ const ariaPlaceholder = el.getAttribute('aria-placeholder')?.toLowerCase() || '';
214
+ let confidence = 0;
215
+ for (const term of parsedQuery.terms) {
216
+ const lowerTerm = term.toLowerCase();
217
+ if (ariaLabel.includes(lowerTerm)) {
218
+ confidence += 0.35;
219
+ }
220
+ if (role.includes(lowerTerm)) {
221
+ confidence += 0.2;
222
+ }
223
+ if (ariaPlaceholder.includes(lowerTerm)) {
224
+ confidence += 0.25;
225
+ }
226
+ }
227
+ // Role matching for element type
228
+ if (parsedQuery.elementType !== 'any') {
229
+ if (role === parsedQuery.elementType ||
230
+ role === 'button' && parsedQuery.elementType === 'button') {
231
+ confidence += 0.2;
232
+ }
233
+ }
234
+ if (confidence > 0) {
235
+ let selector = '';
236
+ if (el.getAttribute('aria-label')) {
237
+ selector = `[aria-label="${el.getAttribute('aria-label')}"]`;
238
+ }
239
+ else if (el.id) {
240
+ selector = `#${el.id}`;
241
+ }
242
+ else {
243
+ selector = `[role="${role}"]`;
244
+ }
245
+ const rect = el.getBoundingClientRect();
246
+ results.push({
247
+ selector,
248
+ confidence: Math.min(confidence, 1),
249
+ ariaLabel,
250
+ role,
251
+ strategy: 'aria',
252
+ tag: el.tagName.toLowerCase(),
253
+ visible: rect.width > 0 && rect.height > 0
254
+ });
255
+ }
256
+ }
257
+ return results;
258
+ }, { parsedQuery, contextSelector });
259
+ }
260
+ /**
261
+ * Find by semantic HTML structure
262
+ */
263
+ async findBySemantic(page, parsedQuery, context) {
264
+ const contextSelector = context || 'body';
265
+ const patterns = this.patterns;
266
+ return await page.evaluate(({ parsedQuery, contextSelector, patterns }) => {
267
+ const results = [];
268
+ const container = document.querySelector(contextSelector) || document.body;
269
+ // Get pattern for element type
270
+ const pattern = patterns[parsedQuery.elementType];
271
+ if (pattern) {
272
+ for (const tagSelector of pattern.tags) {
273
+ const elements = container.querySelectorAll(tagSelector);
274
+ for (const el of elements) {
275
+ const rect = el.getBoundingClientRect();
276
+ if (rect.width === 0 || rect.height === 0)
277
+ continue;
278
+ let confidence = 0.5; // Base confidence for matching pattern
279
+ // Check text content matches
280
+ const text = el.textContent?.toLowerCase() || '';
281
+ for (const term of parsedQuery.terms) {
282
+ if (text.includes(term.toLowerCase())) {
283
+ confidence += 0.2;
284
+ }
285
+ }
286
+ // Generate unique selector
287
+ let selector = '';
288
+ if (el.id) {
289
+ selector = `#${el.id}`;
290
+ confidence += 0.1;
291
+ }
292
+ else if (el.name) {
293
+ selector = `[name="${el.name}"]`;
294
+ confidence += 0.1;
295
+ }
296
+ else {
297
+ selector = tagSelector;
298
+ }
299
+ results.push({
300
+ selector,
301
+ confidence: Math.min(confidence, 1),
302
+ text: text.substring(0, 50),
303
+ strategy: 'semantic',
304
+ elementType: parsedQuery.elementType,
305
+ tag: el.tagName.toLowerCase(),
306
+ visible: true
307
+ });
308
+ }
309
+ }
310
+ }
311
+ return results;
312
+ }, { parsedQuery, contextSelector, patterns });
313
+ }
314
+ /**
315
+ * Find by visual properties (position, approximate area)
316
+ */
317
+ async findByVisual(page, parsedQuery, context) {
318
+ const contextSelector = context || 'body';
319
+ return await page.evaluate(({ parsedQuery, contextSelector }) => {
320
+ const results = [];
321
+ const container = document.querySelector(contextSelector) || document.body;
322
+ // Get viewport dimensions
323
+ const viewportHeight = window.innerHeight;
324
+ const viewportWidth = window.innerWidth;
325
+ const elements = container.querySelectorAll('button, a, input, [role="button"], [role="link"]');
326
+ for (const el of elements) {
327
+ const rect = el.getBoundingClientRect();
328
+ if (rect.width === 0 || rect.height === 0)
329
+ continue;
330
+ let confidence = 0.3; // Base confidence
331
+ // Position matching
332
+ if (parsedQuery.position) {
333
+ const pos = parsedQuery.position;
334
+ if (pos === 'top' && rect.top < viewportHeight * 0.3) {
335
+ confidence += 0.3;
336
+ }
337
+ else if (pos === 'bottom' && rect.bottom > viewportHeight * 0.7) {
338
+ confidence += 0.3;
339
+ }
340
+ else if (pos === 'left' && rect.left < viewportWidth * 0.3) {
341
+ confidence += 0.3;
342
+ }
343
+ else if (pos === 'right' && rect.right > viewportWidth * 0.7) {
344
+ confidence += 0.3;
345
+ }
346
+ else if (pos === 'center' &&
347
+ rect.left > viewportWidth * 0.3 &&
348
+ rect.right < viewportWidth * 0.7) {
349
+ confidence += 0.3;
350
+ }
351
+ else if (pos === 'header' && rect.top < 100) {
352
+ confidence += 0.3;
353
+ }
354
+ else if (pos === 'footer' && rect.bottom > viewportHeight - 100) {
355
+ confidence += 0.3;
356
+ }
357
+ }
358
+ // Check text content
359
+ const text = el.textContent?.toLowerCase() || '';
360
+ for (const term of parsedQuery.terms) {
361
+ if (text.includes(term.toLowerCase())) {
362
+ confidence += 0.2;
363
+ }
364
+ }
365
+ if (confidence > 0.3) {
366
+ let selector = '';
367
+ if (el.id) {
368
+ selector = `#${el.id}`;
369
+ }
370
+ else {
371
+ selector = el.tagName.toLowerCase();
372
+ if (el.className && typeof el.className === 'string') {
373
+ selector += '.' + el.className.split(' ')[0];
374
+ }
375
+ }
376
+ results.push({
377
+ selector,
378
+ confidence: Math.min(confidence, 1),
379
+ text: text.substring(0, 50),
380
+ strategy: 'visual',
381
+ position: {
382
+ top: Math.round(rect.top),
383
+ left: Math.round(rect.left),
384
+ width: Math.round(rect.width),
385
+ height: Math.round(rect.height)
386
+ },
387
+ tag: el.tagName.toLowerCase(),
388
+ visible: true
389
+ });
390
+ }
391
+ }
392
+ return results;
393
+ }, { parsedQuery, contextSelector });
394
+ }
395
+ /**
396
+ * Deduplicate results based on selector
397
+ */
398
+ deduplicateResults(results) {
399
+ const seen = new Map();
400
+ for (const result of results) {
401
+ const existing = seen.get(result.selector);
402
+ if (!existing || existing.confidence < result.confidence) {
403
+ seen.set(result.selector, result);
404
+ }
405
+ }
406
+ return Array.from(seen.values());
407
+ }
408
+ }
409
+ module.exports = ElementFinder;
@@ -1,82 +1,67 @@
1
+ "use strict";
2
+ // @ts-nocheck
1
3
  /**
2
4
  * AI Module - Main Entry Point
3
- *
5
+ *
4
6
  * This module provides AI-powered features that automatically enhance
5
7
  * ALL tools in the browser automation project.
6
- *
8
+ *
7
9
  * Features:
8
10
  * 1. Smart Element Finding - Find elements using natural language
9
11
  * 2. Selector Auto-Healing - Automatically fix broken selectors
10
12
  * 3. Page Understanding - Analyze and understand page structure
11
13
  * 4. Natural Language Commands - Execute actions from text commands
12
- *
14
+ *
13
15
  * Usage:
14
16
  * ```javascript
15
17
  * const { aiCore, smartFind, smartClick, executeCommand } = require('./ai');
16
- *
18
+ *
17
19
  * // Smart find
18
20
  * const elements = await smartFind(page, 'login button');
19
- *
21
+ *
20
22
  * // Smart click with auto-healing
21
23
  * await smartClick(page, '#old-selector');
22
- *
24
+ *
23
25
  * // Execute natural language command
24
26
  * await executeCommand(page, 'click the submit button');
25
27
  * ```
26
- *
28
+ *
27
29
  * Integration with existing tools:
28
30
  * All existing handlers can be wrapped with AI capabilities using:
29
31
  * ```javascript
30
32
  * const enhancedHandler = wrapHandler(originalHandler, 'handlerName');
31
33
  * ```
32
34
  */
33
-
34
- const {
35
- AICore,
36
- aiCore,
37
- smartFind,
38
- smartClick,
39
- smartType,
40
- understandPage,
41
- executeCommand,
42
- wrapHandler,
43
- configure
44
- } = require('./core');
45
-
35
+ const { AICore, aiCore, smartFind, smartClick, smartType, understandPage, executeCommand, wrapHandler, configure } = require('./core');
46
36
  const ElementFinder = require('./element-finder');
47
37
  const SelectorHealer = require('./selector-healer');
48
38
  const PageAnalyzer = require('./page-analyzer');
49
39
  const ActionParser = require('./action-parser');
50
-
51
40
  // Export everything
52
41
  module.exports = {
53
- // Main AI Core
54
- AICore,
55
- aiCore,
56
-
57
- // Quick access functions
58
- smartFind,
59
- smartClick,
60
- smartType,
61
- understandPage,
62
- executeCommand,
63
- wrapHandler,
64
- configure,
65
-
66
- // Individual modules (for advanced usage)
67
- ElementFinder,
68
- SelectorHealer,
69
- PageAnalyzer,
70
- ActionParser,
71
-
72
- // Version info
73
- version: '1.0.0',
74
-
75
- // Feature flags
76
- features: {
77
- smartFind: true,
78
- autoHealing: true,
79
- pageAnalysis: true,
80
- naturalLanguage: true
81
- }
42
+ // Main AI Core
43
+ AICore,
44
+ aiCore,
45
+ // Quick access functions
46
+ smartFind,
47
+ smartClick,
48
+ smartType,
49
+ understandPage,
50
+ executeCommand,
51
+ wrapHandler,
52
+ configure,
53
+ // Individual modules (for advanced usage)
54
+ ElementFinder,
55
+ SelectorHealer,
56
+ PageAnalyzer,
57
+ ActionParser,
58
+ // Version info
59
+ version: '1.0.0',
60
+ // Feature flags
61
+ features: {
62
+ smartFind: true,
63
+ autoHealing: true,
64
+ pageAnalysis: true,
65
+ naturalLanguage: true
66
+ }
82
67
  };