brave-real-browser-mcp-server 2.21.2 → 2.21.4

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.
@@ -57,106 +57,286 @@ export async function handleFindSelector(args) {
57
57
  if (!pageInstance) {
58
58
  throw new Error('Browser not initialized. Call browser_init first.');
59
59
  }
60
- const { text, elementType = '*', exact = false } = args;
61
- // Enhanced semantic element type mappings
62
- const semanticMappings = {
63
- 'button': ['button', '[role="button"]', 'input[type="button"]', 'input[type="submit"]'],
64
- 'link': ['a', '[role="link"]'],
65
- 'input': ['input', 'textarea', '[role="textbox"]', '[contenteditable="true"]'],
66
- 'navigation': ['nav', '[role="navigation"]', '.nav', '.navbar', '.menu'],
67
- 'heading': ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', '[role="heading"]'],
68
- 'list': ['ul', 'ol', '[role="list"]', '.list'],
69
- 'article': ['article', '[role="article"]', '.article', '.post'],
70
- 'form': ['form', '[role="form"]'],
71
- 'dialog': ['dialog', '[role="dialog"]', '.modal', '.popup'],
72
- 'tab': ['[role="tab"]', '.tab'],
73
- 'menu': ['[role="menu"]', '.menu', '.dropdown'],
74
- 'checkbox': ['input[type="checkbox"]', '[role="checkbox"]'],
75
- 'radio': ['input[type="radio"]', '[role="radio"]']
76
- };
77
- // Convert semantic element type to actual selectors
78
- let searchSelectors;
79
- if (semanticMappings[elementType.toLowerCase()]) {
80
- searchSelectors = semanticMappings[elementType.toLowerCase()];
60
+ const { text, selector, xpath, attributes, description, exact = false, context } = args || {};
61
+ // Ensure elementType has a fallback value
62
+ const elementType = args?.elementType || '*';
63
+ // Priority 1: Direct CSS selector search
64
+ if (selector) {
65
+ const elements = await pageInstance.$$(selector);
66
+ if (elements.length > 0) {
67
+ const elementInfo = await pageInstance.evaluate((sel) => {
68
+ const el = document.querySelector(sel);
69
+ return el ? {
70
+ selector: sel,
71
+ text: el.textContent?.trim().substring(0, 100) || '',
72
+ tagName: el.tagName.toLowerCase()
73
+ } : null;
74
+ }, selector);
75
+ if (elementInfo) {
76
+ return {
77
+ content: [{
78
+ type: 'text',
79
+ text: `Found element: ${elementInfo.selector}\nText: "${elementInfo.text}"\nConfidence: 100\n\n` +
80
+ 'šŸ”„ Workflow Status: Element located\n' +
81
+ ' • Next step: Use interaction tools (click, type) with this selector\n' +
82
+ ' • Selector is validated and ready for automation\n\n' +
83
+ 'āœ… Element discovery complete - ready for interactions'
84
+ }]
85
+ };
86
+ }
87
+ }
88
+ throw new Error(`Element not found with selector: ${selector}`);
81
89
  }
82
- else {
83
- searchSelectors = [elementType];
90
+ // Priority 2: XPath expression search
91
+ if (xpath) {
92
+ const elements = await pageInstance.$x(xpath);
93
+ if (elements.length > 0) {
94
+ const elementInfo = await pageInstance.evaluate((xp) => {
95
+ const result = document.evaluate(xp, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
96
+ const el = result.singleNodeValue;
97
+ return el ? {
98
+ selector: xp,
99
+ text: el.textContent?.trim().substring(0, 100) || '',
100
+ tagName: el.tagName?.toLowerCase() || 'unknown'
101
+ } : null;
102
+ }, xpath);
103
+ if (elementInfo) {
104
+ return {
105
+ content: [{
106
+ type: 'text',
107
+ text: `Found element via XPath: ${xpath}\nText: "${elementInfo.text}"\nTag: ${elementInfo.tagName}\nConfidence: 95\n\n` +
108
+ 'šŸ”„ Workflow Status: Element located\n' +
109
+ ' • Next step: Use interaction tools with XPath or convert to CSS selector\n\n' +
110
+ 'āœ… XPath element discovery complete'
111
+ }]
112
+ };
113
+ }
114
+ }
115
+ throw new Error(`Element not found with XPath: ${xpath}`);
84
116
  }
85
- // Enhanced selector finding with authentication detection
86
- const results = await pageInstance.evaluate((searchText, selectors, isExact) => {
87
- const elements = [];
88
- const lowerSearchText = searchText.toLowerCase();
89
- // Search through specified selectors
90
- for (const baseSelector of selectors) {
91
- const candidates = document.querySelectorAll(baseSelector);
92
- candidates.forEach(element => {
93
- const elementText = element.textContent?.trim() || '';
94
- const lowerElementText = elementText.toLowerCase();
95
- let matches = false;
96
- if (isExact) {
97
- matches = lowerElementText === lowerSearchText;
98
- }
99
- else {
100
- matches = lowerElementText.includes(lowerSearchText);
117
+ // Priority 3: Attributes JSON matching
118
+ if (attributes) {
119
+ let attrObj;
120
+ try {
121
+ attrObj = JSON.parse(attributes);
122
+ }
123
+ catch {
124
+ throw new Error(`Invalid attributes JSON: ${attributes}. Please provide valid JSON.`);
125
+ }
126
+ const results = await pageInstance.evaluate((attrs, elemType) => {
127
+ const elements = document.querySelectorAll(elemType);
128
+ const matches = [];
129
+ elements.forEach(el => {
130
+ let allMatch = true;
131
+ for (const [key, value] of Object.entries(attrs)) {
132
+ const attrValue = el.getAttribute(key);
133
+ if (attrValue !== value && !attrValue?.includes(value)) {
134
+ allMatch = false;
135
+ break;
136
+ }
101
137
  }
102
- if (matches) {
103
- // Generate simple selector
104
- let selector = element.tagName.toLowerCase();
105
- if (element.id) {
106
- selector += '#' + element.id;
138
+ if (allMatch) {
139
+ let sel = el.tagName.toLowerCase();
140
+ if (el.id)
141
+ sel += '#' + el.id;
142
+ else if (el.className && typeof el.className === 'string') {
143
+ const cls = el.className.trim().split(/\s+/)[0];
144
+ if (cls)
145
+ sel += '.' + cls;
107
146
  }
108
- else if (element.className && typeof element.className === 'string') {
109
- const cls = element.className.trim().split(/\s+/)[0];
147
+ matches.push({
148
+ selector: sel,
149
+ text: el.textContent?.trim().substring(0, 100) || '',
150
+ tagName: el.tagName.toLowerCase()
151
+ });
152
+ }
153
+ });
154
+ return matches.slice(0, 5);
155
+ }, attrObj, elementType);
156
+ if (results.length > 0) {
157
+ const best = results[0];
158
+ return {
159
+ content: [{
160
+ type: 'text',
161
+ text: `Found element by attributes: ${best.selector}\nText: "${best.text}"\nConfidence: 90\n${results.length > 1 ? `\nAdditional matches: ${results.length - 1}` : ''}\n\n` +
162
+ 'šŸ”„ Workflow Status: Element located\n' +
163
+ ' • Next step: Use interaction tools (click, type) with this selector\n\n' +
164
+ 'āœ… Attribute-based element discovery complete'
165
+ }]
166
+ };
167
+ }
168
+ throw new Error(`No elements found matching attributes: ${attributes}`);
169
+ }
170
+ // Priority 4: Natural language description search (AI-powered fuzzy matching)
171
+ if (description) {
172
+ // Extract keywords from description for fuzzy matching
173
+ const keywords = description.toLowerCase()
174
+ .replace(/[^a-z0-9\s]/g, ' ')
175
+ .split(/\s+/)
176
+ .filter(w => w.length > 2 && !['the', 'and', 'for', 'with', 'that', 'this', 'from', 'button', 'link', 'element'].includes(w));
177
+ if (keywords.length === 0) {
178
+ throw new Error(`Could not extract meaningful keywords from description: "${description}". Please provide more specific terms.`);
179
+ }
180
+ const results = await pageInstance.evaluate((kws, elemType, ctx) => {
181
+ const searchArea = ctx ? document.querySelector(ctx) || document.body : document.body;
182
+ const elements = searchArea.querySelectorAll(elemType);
183
+ const matches = [];
184
+ elements.forEach(el => {
185
+ const elText = (el.textContent || '').toLowerCase();
186
+ const elAttrs = Array.from(el.attributes).map(a => a.value.toLowerCase()).join(' ');
187
+ const combined = elText + ' ' + elAttrs;
188
+ let score = 0;
189
+ for (const kw of kws) {
190
+ if (combined.includes(kw))
191
+ score++;
192
+ }
193
+ if (score > 0) {
194
+ let sel = el.tagName.toLowerCase();
195
+ if (el.id)
196
+ sel += '#' + el.id;
197
+ else if (el.className && typeof el.className === 'string') {
198
+ const cls = el.className.trim().split(/\s+/)[0];
110
199
  if (cls)
111
- selector += '.' + cls;
200
+ sel += '.' + cls;
112
201
  }
113
- // Calculate dummy confidence / score based on match
114
- let confidence = 100;
115
- if (lowerElementText === lowerSearchText)
116
- confidence = 100;
117
- else
118
- confidence = 80;
119
- elements.push({
120
- selector,
121
- text: elementText.substring(0, 100),
122
- tagName: element.tagName.toLowerCase(),
123
- confidence
202
+ matches.push({
203
+ selector: sel,
204
+ text: el.textContent?.trim().substring(0, 100) || '',
205
+ tagName: el.tagName.toLowerCase(),
206
+ score: (score / kws.length) * 100
124
207
  });
125
208
  }
126
209
  });
210
+ // Sort by score descending
211
+ matches.sort((a, b) => b.score - a.score);
212
+ return matches.slice(0, 5);
213
+ }, keywords, elementType, context);
214
+ if (results.length > 0) {
215
+ const best = results[0];
216
+ const additionalMatches = results.slice(1, 3).map((r) => ` • ${r.selector} (confidence: ${Math.round(r.score)})`).join('\n');
217
+ return {
218
+ content: [{
219
+ type: 'text',
220
+ text: `Found element: ${best.selector}\nText: "${best.text}"\nConfidence: ${Math.round(best.score)}\n` +
221
+ (additionalMatches ? `\nAlternative matches:\n${additionalMatches}` : '') +
222
+ '\n\nšŸ”„ Workflow Status: Element located\n' +
223
+ ' • Next step: Use interaction tools (click, type) with this selector\n' +
224
+ ' • Selector is validated and ready for automation\n\n' +
225
+ 'āœ… Element discovery complete - ready for interactions'
226
+ }]
227
+ };
127
228
  }
128
- // Return top 10 unique
129
- return elements.slice(0, 10);
130
- }, text, // Use the text argument from args
131
- searchSelectors, exact);
132
- if (results.length === 0) {
133
- throw new Error(`No elements found containing text: "${text}"\n\n` +
134
- 'šŸ’” Troubleshooting suggestions:\n' +
135
- ' • Check if the text appears exactly as shown on the page\n' +
136
- ' • Try partial text search with exact=false\n' +
137
- ' • Use get_content to see all available text first\n' +
138
- ' • Verify the page has fully loaded\n' +
139
- ' • Check if the element is hidden or in a different frame');
229
+ throw new Error(`No elements found matching description: "${description}". Keywords searched: ${keywords.join(', ')}`);
140
230
  }
141
- // Return the best match with additional options
142
- const bestMatch = results[0];
143
- const additionalMatches = results.slice(1, 3).map((r) => ` • ${r.selector} (confidence: ${r.confidence})`).join('\n');
144
- const workflowMessage = '\n\nšŸ”„ Workflow Status: Element located\n' +
145
- ' • Next step: Use interaction tools (click, type) with this selector\n' +
146
- ' • Selector is validated and ready for automation\n\n' +
147
- 'āœ… Element discovery complete - ready for interactions';
148
- return {
149
- content: [
150
- {
151
- type: 'text',
152
- text: `Found element: ${bestMatch.selector}\n` +
153
- `Text: "${bestMatch.text}"\n` +
154
- `Confidence: ${bestMatch.confidence}\n` +
155
- (additionalMatches ? `\nAlternative matches:\n${additionalMatches}` : '') +
156
- workflowMessage,
157
- },
158
- ],
159
- };
231
+ // Priority 5: Text content search (original functionality)
232
+ if (text) {
233
+ // Enhanced semantic element type mappings
234
+ const semanticMappings = {
235
+ 'button': ['button', '[role="button"]', 'input[type="button"]', 'input[type="submit"]'],
236
+ 'link': ['a', '[role="link"]'],
237
+ 'input': ['input', 'textarea', '[role="textbox"]', '[contenteditable="true"]'],
238
+ 'navigation': ['nav', '[role="navigation"]', '.nav', '.navbar', '.menu'],
239
+ 'heading': ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', '[role="heading"]'],
240
+ 'list': ['ul', 'ol', '[role="list"]', '.list'],
241
+ 'article': ['article', '[role="article"]', '.article', '.post'],
242
+ 'form': ['form', '[role="form"]'],
243
+ 'dialog': ['dialog', '[role="dialog"]', '.modal', '.popup'],
244
+ 'tab': ['[role="tab"]', '.tab'],
245
+ 'menu': ['[role="menu"]', '.menu', '.dropdown'],
246
+ 'checkbox': ['input[type="checkbox"]', '[role="checkbox"]'],
247
+ 'radio': ['input[type="radio"]', '[role="radio"]']
248
+ };
249
+ // Convert semantic element type to actual selectors
250
+ let searchSelectors;
251
+ if (semanticMappings[elementType.toLowerCase()]) {
252
+ searchSelectors = semanticMappings[elementType.toLowerCase()];
253
+ }
254
+ else {
255
+ searchSelectors = [elementType];
256
+ }
257
+ // Enhanced selector finding with context support
258
+ const results = await pageInstance.evaluate((searchText, selectors, isExact, ctx) => {
259
+ const searchArea = ctx ? document.querySelector(ctx) || document.body : document.body;
260
+ const elements = [];
261
+ const lowerSearchText = searchText.toLowerCase();
262
+ // Search through specified selectors
263
+ for (const baseSelector of selectors) {
264
+ const candidates = searchArea.querySelectorAll(baseSelector);
265
+ candidates.forEach(element => {
266
+ const elementText = element.textContent?.trim() || '';
267
+ const lowerElementText = elementText.toLowerCase();
268
+ let matches = false;
269
+ if (isExact) {
270
+ matches = lowerElementText === lowerSearchText;
271
+ }
272
+ else {
273
+ matches = lowerElementText.includes(lowerSearchText);
274
+ }
275
+ if (matches) {
276
+ // Generate simple selector
277
+ let selector = element.tagName.toLowerCase();
278
+ if (element.id) {
279
+ selector += '#' + element.id;
280
+ }
281
+ else if (element.className && typeof element.className === 'string') {
282
+ const cls = element.className.trim().split(/\s+/)[0];
283
+ if (cls)
284
+ selector += '.' + cls;
285
+ }
286
+ // Calculate confidence based on match quality
287
+ let confidence = 100;
288
+ if (lowerElementText === lowerSearchText)
289
+ confidence = 100;
290
+ else
291
+ confidence = 80;
292
+ elements.push({
293
+ selector,
294
+ text: elementText.substring(0, 100),
295
+ tagName: element.tagName.toLowerCase(),
296
+ confidence
297
+ });
298
+ }
299
+ });
300
+ }
301
+ // Return top 10 unique
302
+ return elements.slice(0, 10);
303
+ }, text, searchSelectors, exact, context);
304
+ if (results.length === 0) {
305
+ throw new Error(`No elements found containing text: "${text}"\n\n` +
306
+ 'šŸ’” Troubleshooting suggestions:\n' +
307
+ ' • Check if the text appears exactly as shown on the page\n' +
308
+ ' • Try partial text search with exact=false\n' +
309
+ ' • Use get_content to see all available text first\n' +
310
+ ' • Verify the page has fully loaded\n' +
311
+ ' • Check if the element is hidden or in a different frame');
312
+ }
313
+ // Return the best match with additional options
314
+ const bestMatch = results[0];
315
+ const additionalMatches = results.slice(1, 3).map((r) => ` • ${r.selector} (confidence: ${r.confidence})`).join('\n');
316
+ const workflowMessage = '\n\nšŸ”„ Workflow Status: Element located\n' +
317
+ ' • Next step: Use interaction tools (click, type) with this selector\n' +
318
+ ' • Selector is validated and ready for automation\n\n' +
319
+ 'āœ… Element discovery complete - ready for interactions';
320
+ return {
321
+ content: [
322
+ {
323
+ type: 'text',
324
+ text: `Found element: ${bestMatch.selector}\n` +
325
+ `Text: "${bestMatch.text}"\n` +
326
+ `Confidence: ${bestMatch.confidence}\n` +
327
+ (additionalMatches ? `\nAlternative matches:\n${additionalMatches}` : '') +
328
+ workflowMessage,
329
+ },
330
+ ],
331
+ };
332
+ }
333
+ // No search criteria provided
334
+ throw new Error('No search criteria provided. Please specify at least one of:\n' +
335
+ ' • text - Text content to search for\n' +
336
+ ' • selector - CSS selector\n' +
337
+ ' • xpath - XPath expression\n' +
338
+ ' • attributes - JSON string of attributes\n' +
339
+ ' • description - Natural language description');
160
340
  }, 'Failed to find selector');
161
341
  });
162
342
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-browser-mcp-server",
3
- "version": "2.21.2",
3
+ "version": "2.21.4",
4
4
  "description": "🦁 MCP server for Brave Real Browser - NPM Workspaces Monorepo with anti-detection features",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -41,7 +41,7 @@
41
41
  "dependencies": {
42
42
  "@modelcontextprotocol/sdk": "latest",
43
43
  "@types/turndown": "latest",
44
- "brave-real-browser": "^2.3.2",
44
+ "brave-real-browser": "^2.3.4",
45
45
  "turndown": "latest"
46
46
  },
47
47
  "peerDependencies": {