brave-real-browser-mcp-server 2.9.5 โ 2.9.6
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 +115 -43
- package/dist/browser-manager.js +47 -141
- package/dist/browser-manager.test.js +8 -7
- package/dist/handlers/api-integration-handlers.js +1 -1
- package/dist/handlers/captcha-handlers.js +1 -1
- package/dist/handlers/pagination-handlers.js +25 -10
- package/dist/handlers/visual-tools-handlers.js +1 -1
- package/dist/index.js +2 -2
- package/dist/tool-definitions.js +4 -4
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -61,6 +61,20 @@ If you're just using this MCP server (not developing it), you don't need to run
|
|
|
61
61
|
}
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"brave-real-browser": {
|
|
68
|
+
"command": "npx",
|
|
69
|
+
"args": ["brave-real-browser-mcp-server@latest"],
|
|
70
|
+
"env": {
|
|
71
|
+
"CHROME_PATH": "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
64
78
|
**For Mac:**
|
|
65
79
|
1. Open Finder and press `Cmd+Shift+G`
|
|
66
80
|
2. Go to: `~/Library/Application Support/Claude/`
|
|
@@ -122,7 +136,7 @@ assistants to control a real browser, extract content, and more.
|
|
|
122
136
|
- **Advanced configuration**: Full support for all brave-real-browser options
|
|
123
137
|
- **Dynamic selector discovery**: Intelligent element finding without hardcoded selectors
|
|
124
138
|
- **Random scrolling**: Tools for natural scrolling to avoid detection
|
|
125
|
-
- **Comprehensive toolset**:
|
|
139
|
+
- **Comprehensive toolset**: 62+ professional tools covering all browser automation needs
|
|
126
140
|
- **Proxy support**: Built-in proxy configuration for enhanced privacy
|
|
127
141
|
- **Captcha handling**: Support for solving reCAPTCHA, hCaptcha, and Turnstile
|
|
128
142
|
- **Robust error handling**: Advanced error recovery with circuit breaker pattern
|
|
@@ -528,48 +542,106 @@ AI: I'll set up the browser with your proxy configuration.
|
|
|
528
542
|
|
|
529
543
|
## Available Tools
|
|
530
544
|
|
|
531
|
-
###
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
545
|
+
### ๐ฏ **Complete Professional Toolset - 62 Tools**
|
|
546
|
+
|
|
547
|
+
This MCP server provides **62 professional-grade tools** organized into 11 major categories:
|
|
548
|
+
|
|
549
|
+
### ๐ฅ **Core Browser Management (11 Tools)**
|
|
550
|
+
1. `browser_init` - Initialize stealth browser with advanced options
|
|
551
|
+
2. `navigate` - Navigate to a URL
|
|
552
|
+
3. `get_content` - Get page content (HTML or text)
|
|
553
|
+
4. `click` - Click on elements
|
|
554
|
+
5. `type` - Type text into input fields
|
|
555
|
+
6. `wait` - Wait for various conditions
|
|
556
|
+
7. `browser_close` - Close browser instance
|
|
557
|
+
8. `solve_captcha` - Solve captchas (reCAPTCHA, hCaptcha, Turnstile)
|
|
558
|
+
9. `random_scroll` - Natural scrolling behavior
|
|
559
|
+
10. `find_selector` - Find CSS selectors by text content
|
|
560
|
+
11. `save_content_as_markdown` - Save page content as markdown
|
|
561
|
+
|
|
562
|
+
### ๐ **Smart Data Extractors (5 Tools)**
|
|
563
|
+
12. `scrape_table` - Extract structured data from HTML tables
|
|
564
|
+
13. `extract_list` - Extract data from bullet and numbered lists
|
|
565
|
+
14. `extract_json` - Extract embedded JSON/API data
|
|
566
|
+
15. `scrape_meta_tags` - Extract SEO meta tags and Open Graph data
|
|
567
|
+
16. `extract_schema` - Extract Schema.org structured data
|
|
568
|
+
|
|
569
|
+
### ๐ **Multi-Element Extractors (7 Tools)**
|
|
570
|
+
17. `batch_element_scraper` - Scrape multiple similar elements in batch
|
|
571
|
+
18. `nested_data_extraction` - Extract data maintaining parent-child relationships
|
|
572
|
+
19. `attribute_harvester` - Collect element attributes (href, src, data-*)
|
|
573
|
+
20. `image_scraper` - Extract image URLs, alt text, dimensions
|
|
574
|
+
21. `link_harvester` - Collect internal/external links with classification
|
|
575
|
+
22. `media_extractor` - Extract video, audio, iframe URLs and metadata
|
|
576
|
+
23. `pdf_link_finder` - Find downloadable files (PDF, DOC, etc.)
|
|
577
|
+
|
|
578
|
+
### ๐ **Pagination Tools (5 Tools)**
|
|
579
|
+
24. `auto_pagination` - Automatically detect and navigate through pages
|
|
580
|
+
25. `infinite_scroll` - Handle lazy-loading pages with auto-scroll
|
|
581
|
+
26. `multi_page_scraper` - Collect data from multiple pages
|
|
582
|
+
27. `sitemap_parser` - Extract URLs from sitemap.xml
|
|
583
|
+
28. `breadcrumb_navigator` - Navigate site structure using breadcrumbs
|
|
584
|
+
|
|
585
|
+
### ๐งน **Data Processing Tools (8 Tools)**
|
|
586
|
+
29. `smart_text_cleaner` - Clean and normalize text data
|
|
587
|
+
30. `html_to_text` - Convert HTML to clean text
|
|
588
|
+
31. `price_parser` - Extract numbers from currency strings
|
|
589
|
+
32. `date_normalizer` - Convert various date formats to standard format
|
|
590
|
+
33. `contact_extractor` - Detect phone numbers and email addresses
|
|
591
|
+
34. `schema_validator` - Validate data against JSON schema
|
|
592
|
+
35. `required_fields_checker` - Check for missing required fields
|
|
593
|
+
36. `duplicate_remover` - Remove duplicate items from arrays
|
|
594
|
+
|
|
595
|
+
### ๐ค **AI-Powered Features (5 Tools)**
|
|
596
|
+
37. `smart_selector_generator` - AI-based CSS selector generation
|
|
597
|
+
38. `content_classification` - Classify webpage content into categories
|
|
598
|
+
39. `sentiment_analysis` - Analyze sentiment of page content
|
|
599
|
+
40. `summary_generator` - Generate page content summaries using TF-IDF
|
|
600
|
+
41. `translation_support` - Detect language and provide translation info
|
|
601
|
+
|
|
602
|
+
### ๐ **Search & Filter Tools (5 Tools)**
|
|
603
|
+
42. `keyword_search` - Advanced keyword search with context
|
|
604
|
+
43. `regex_pattern_matcher` - Search using regular expressions
|
|
605
|
+
44. `xpath_support` - Query elements using XPath expressions
|
|
606
|
+
45. `advanced_css_selectors` - Support for complex CSS selectors
|
|
607
|
+
46. `visual_element_finder` - Find elements by visual properties
|
|
608
|
+
|
|
609
|
+
### ๐ง **Data Quality & Validation (5 Tools)**
|
|
610
|
+
47. `data_deduplication` - Advanced duplicate removal with fuzzy matching
|
|
611
|
+
48. `missing_data_handler` - Detect and handle missing data
|
|
612
|
+
49. `data_type_validator` - Validate data types against JSON schema
|
|
613
|
+
50. `outlier_detection` - Detect outliers in numerical data
|
|
614
|
+
51. `consistency_checker` - Check data consistency across fields
|
|
615
|
+
|
|
616
|
+
### ๐ก๏ธ **Advanced Captcha Handling (3 Tools)**
|
|
617
|
+
52. `ocr_engine` - Extract text from images using OCR
|
|
618
|
+
53. `audio_captcha_solver` - Handle audio captchas
|
|
619
|
+
54. `puzzle_captcha_handler` - Handle slider and puzzle captchas
|
|
620
|
+
|
|
621
|
+
### ๐ธ **Screenshot & Visual Tools (5 Tools)**
|
|
622
|
+
55. `full_page_screenshot` - Capture complete page screenshots
|
|
623
|
+
56. `element_screenshot` - Capture screenshots of specific elements
|
|
624
|
+
57. `pdf_generation` - Convert pages to PDF
|
|
625
|
+
58. `video_recording` - Record browser sessions
|
|
626
|
+
59. `visual_comparison` - Compare two screenshots for differences
|
|
627
|
+
|
|
628
|
+
### ๐ **Website API Integration (3 Tools)**
|
|
629
|
+
60. `rest_api_endpoint_finder` - Discover REST API endpoints
|
|
630
|
+
61. `webhook_support` - Set up and test webhooks
|
|
631
|
+
62. `all_website_api_finder` - Comprehensive API discovery
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
### ๐ **Usage Examples**
|
|
636
|
+
|
|
637
|
+
| Category | Example Usage |
|
|
638
|
+
|----------|---------------|
|
|
639
|
+
| **Basic Browsing** | `browser_init` โ `navigate` โ `get_content` |
|
|
640
|
+
| **Data Extraction** | `scrape_table` โ `smart_text_cleaner` โ `duplicate_remover` |
|
|
641
|
+
| **Form Automation** | `find_selector` โ `type` โ `click` โ `wait` |
|
|
642
|
+
| **Content Analysis** | `get_content` โ `sentiment_analysis` โ `summary_generator` |
|
|
643
|
+
| **Visual Capture** | `full_page_screenshot` โ `element_screenshot` |
|
|
644
|
+
| **Quality Control** | `data_type_validator` โ `consistency_checker` |
|
|
573
645
|
|
|
574
646
|
## Advanced Features
|
|
575
647
|
|
package/dist/browser-manager.js
CHANGED
|
@@ -109,33 +109,48 @@ export function categorizeError(error) {
|
|
|
109
109
|
// Timeout wrapper for operations that may hang
|
|
110
110
|
export async function withTimeout(operation, timeoutMs, context = 'unknown') {
|
|
111
111
|
return new Promise((resolve, reject) => {
|
|
112
|
+
let isResolved = false;
|
|
112
113
|
const timer = setTimeout(() => {
|
|
113
|
-
|
|
114
|
+
if (!isResolved) {
|
|
115
|
+
isResolved = true;
|
|
116
|
+
reject(new Error(`Operation timed out after ${timeoutMs}ms in context: ${context}`));
|
|
117
|
+
}
|
|
114
118
|
}, timeoutMs);
|
|
115
119
|
operation()
|
|
116
120
|
.then((result) => {
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
if (!isResolved) {
|
|
122
|
+
isResolved = true;
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
resolve(result);
|
|
125
|
+
}
|
|
119
126
|
})
|
|
120
127
|
.catch((error) => {
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
if (!isResolved) {
|
|
129
|
+
isResolved = true;
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
reject(error);
|
|
132
|
+
}
|
|
123
133
|
});
|
|
124
134
|
});
|
|
125
135
|
}
|
|
126
136
|
// Port availability and connection utilities for enhanced resilience
|
|
127
137
|
export async function isPortAvailable(port, host = '127.0.0.1') {
|
|
128
138
|
return new Promise((resolve) => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
server.
|
|
132
|
-
|
|
139
|
+
try {
|
|
140
|
+
const server = net.createServer();
|
|
141
|
+
server.listen(port, host, () => {
|
|
142
|
+
server.once('close', () => {
|
|
143
|
+
resolve(true);
|
|
144
|
+
});
|
|
145
|
+
server.close();
|
|
133
146
|
});
|
|
134
|
-
server.
|
|
135
|
-
|
|
136
|
-
|
|
147
|
+
server.on('error', () => {
|
|
148
|
+
resolve(false);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
137
152
|
resolve(false);
|
|
138
|
-
}
|
|
153
|
+
}
|
|
139
154
|
});
|
|
140
155
|
}
|
|
141
156
|
// Test localhost resolution and connectivity
|
|
@@ -247,57 +262,8 @@ function getWindowsBraveFromRegistry() {
|
|
|
247
262
|
}
|
|
248
263
|
return null;
|
|
249
264
|
}
|
|
250
|
-
// Windows Registry Chrome detection
|
|
251
|
-
function getWindowsChromeFromRegistry() {
|
|
252
|
-
if (process.platform !== 'win32')
|
|
253
|
-
return null;
|
|
254
|
-
try {
|
|
255
|
-
const { execSync } = require('child_process');
|
|
256
|
-
const registryQueries = [
|
|
257
|
-
'reg query "HKEY_CURRENT_USER\\Software\\Google\\Chrome\\BLBeacon" /v version 2>nul',
|
|
258
|
-
'reg query "HKEY_LOCAL_MACHINE\\Software\\Google\\Chrome\\BLBeacon" /v version 2>nul',
|
|
259
|
-
'reg query "HKEY_LOCAL_MACHINE\\Software\\WOW6432Node\\Google\\Chrome\\BLBeacon" /v version 2>nul',
|
|
260
|
-
];
|
|
261
|
-
for (const query of registryQueries) {
|
|
262
|
-
try {
|
|
263
|
-
const result = execSync(query, { encoding: 'utf8', timeout: 5000 });
|
|
264
|
-
if (result) {
|
|
265
|
-
const standardPaths = [
|
|
266
|
-
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
267
|
-
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
|
|
268
|
-
];
|
|
269
|
-
for (const standardPath of standardPaths) {
|
|
270
|
-
if (fs.existsSync(standardPath)) {
|
|
271
|
-
console.error(`โ Found Chrome via Registry detection: ${standardPath}`);
|
|
272
|
-
return standardPath;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
catch (error) {
|
|
278
|
-
// Continue to next registry query
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
try {
|
|
282
|
-
const installDirQuery = 'reg query "HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe" /ve 2>nul';
|
|
283
|
-
const result = execSync(installDirQuery, { encoding: 'utf8', timeout: 5000 });
|
|
284
|
-
const match = result.match(/REG_SZ\s+(.+\.exe)/);
|
|
285
|
-
if (match && match[1] && fs.existsSync(match[1])) {
|
|
286
|
-
console.error(`โ Found Chrome via App Paths registry: ${match[1]}`);
|
|
287
|
-
return match[1];
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
catch (error) {
|
|
291
|
-
// Registry detection failed, will fall back to file system search
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
catch (error) {
|
|
295
|
-
console.error('Windows Registry Chrome detection failed:', error instanceof Error ? error.message : String(error));
|
|
296
|
-
}
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
265
|
// Brave Browser path detection (cross-platform support)
|
|
300
|
-
//
|
|
266
|
+
// Pure Brave-only detection - This project is designed specifically for Brave Browser
|
|
301
267
|
export function detectBravePath() {
|
|
302
268
|
const platform = process.platform;
|
|
303
269
|
// PRIORITY -1: Use brave-real-launcher's professional detection (BEST METHOD)
|
|
@@ -327,20 +293,14 @@ export function detectBravePath() {
|
|
|
327
293
|
catch (error) {
|
|
328
294
|
// Config file not found or invalid, continue with other methods
|
|
329
295
|
}
|
|
330
|
-
// PRIORITY 1: Check environment variables first (BRAVE_PATH
|
|
296
|
+
// PRIORITY 1: Check environment variables first (BRAVE_PATH only)
|
|
331
297
|
const envBravePath = process.env.BRAVE_PATH;
|
|
332
298
|
if (envBravePath && fs.existsSync(envBravePath)) {
|
|
333
299
|
console.error(`โ Found Brave via BRAVE_PATH environment variable: ${envBravePath}`);
|
|
334
300
|
return envBravePath;
|
|
335
301
|
}
|
|
336
|
-
|
|
337
|
-
if (envChromePath && fs.existsSync(envChromePath)) {
|
|
338
|
-
console.error(`โ Found Chrome via environment variable: ${envChromePath}`);
|
|
339
|
-
return envChromePath;
|
|
340
|
-
}
|
|
341
|
-
// PRIORITY 2: Try Brave paths FIRST (this is Brave-Real-Browser project!)
|
|
302
|
+
// PRIORITY 2: Brave paths detection (Brave-only project!)
|
|
342
303
|
let bravePaths = [];
|
|
343
|
-
let chromePaths = [];
|
|
344
304
|
switch (platform) {
|
|
345
305
|
case 'win32':
|
|
346
306
|
// BRAVE PATHS (PRIORITY - Try these first!)
|
|
@@ -352,23 +312,7 @@ export function detectBravePath() {
|
|
|
352
312
|
path.join(process.env.PROGRAMFILES || '', 'BraveSoftware\\Brave-Browser\\Application\\brave.exe'),
|
|
353
313
|
path.join(process.env['PROGRAMFILES(X86)'] || '', 'BraveSoftware\\Brave-Browser\\Application\\brave.exe'),
|
|
354
314
|
];
|
|
355
|
-
//
|
|
356
|
-
chromePaths = [
|
|
357
|
-
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
358
|
-
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
359
|
-
path.join(process.env.LOCALAPPDATA || '', 'Google\\Chrome\\Application\\chrome.exe'),
|
|
360
|
-
path.join(process.env.USERPROFILE || '', 'AppData\\Local\\Google\\Chrome\\Application\\chrome.exe'),
|
|
361
|
-
path.join(process.env.PROGRAMFILES || '', 'Google\\Chrome\\Application\\chrome.exe'),
|
|
362
|
-
path.join(process.env['PROGRAMFILES(X86)'] || '', 'Google\\Chrome\\Application\\chrome.exe'),
|
|
363
|
-
path.join(process.env.LOCALAPPDATA || '', 'Google\\Chrome SxS\\Application\\chrome.exe'),
|
|
364
|
-
'C:\\Program Files\\Google\\Chrome SxS\\Application\\chrome.exe',
|
|
365
|
-
'C:\\Users\\Public\\Desktop\\Google Chrome.exe',
|
|
366
|
-
path.join(process.env.APPDATA || '', 'Google\\Chrome\\Application\\chrome.exe'),
|
|
367
|
-
'C:\\Chrome\\chrome.exe',
|
|
368
|
-
'C:\\google\\chrome\\chrome.exe',
|
|
369
|
-
'C:\\PortableApps\\GoogleChromePortable\\App\\Chrome-bin\\chrome.exe',
|
|
370
|
-
];
|
|
371
|
-
// Try Brave registry first
|
|
315
|
+
// Try Brave registry detection
|
|
372
316
|
try {
|
|
373
317
|
const braveRegistryPath = getWindowsBraveFromRegistry();
|
|
374
318
|
if (braveRegistryPath) {
|
|
@@ -378,16 +322,6 @@ export function detectBravePath() {
|
|
|
378
322
|
catch (error) {
|
|
379
323
|
console.error('Brave registry detection failed, continuing with file system search...');
|
|
380
324
|
}
|
|
381
|
-
// Try Chrome registry as fallback
|
|
382
|
-
try {
|
|
383
|
-
const chromeRegistryPath = getWindowsChromeFromRegistry();
|
|
384
|
-
if (chromeRegistryPath) {
|
|
385
|
-
chromePaths.unshift(chromeRegistryPath);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
catch (error) {
|
|
389
|
-
console.error('Chrome registry detection failed, continuing with file system search...');
|
|
390
|
-
}
|
|
391
325
|
break;
|
|
392
326
|
case 'darwin':
|
|
393
327
|
// BRAVE PATHS (PRIORITY)
|
|
@@ -397,12 +331,6 @@ export function detectBravePath() {
|
|
|
397
331
|
'/Applications/Brave Browser Beta.app/Contents/MacOS/Brave Browser Beta',
|
|
398
332
|
'/Applications/Brave Browser Dev.app/Contents/MacOS/Brave Browser Dev',
|
|
399
333
|
];
|
|
400
|
-
// Chrome paths (fallback)
|
|
401
|
-
chromePaths = [
|
|
402
|
-
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
403
|
-
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
404
|
-
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary'
|
|
405
|
-
];
|
|
406
334
|
break;
|
|
407
335
|
case 'linux':
|
|
408
336
|
// BRAVE PATHS (PRIORITY)
|
|
@@ -415,16 +343,6 @@ export function detectBravePath() {
|
|
|
415
343
|
'/opt/brave/brave-browser',
|
|
416
344
|
'/usr/local/bin/brave-browser',
|
|
417
345
|
];
|
|
418
|
-
// Chrome paths (fallback)
|
|
419
|
-
chromePaths = [
|
|
420
|
-
'/usr/bin/google-chrome',
|
|
421
|
-
'/usr/bin/google-chrome-stable',
|
|
422
|
-
'/usr/bin/chromium-browser',
|
|
423
|
-
'/usr/bin/chromium',
|
|
424
|
-
'/snap/bin/chromium',
|
|
425
|
-
'/usr/bin/chrome',
|
|
426
|
-
'/opt/google/chrome/chrome'
|
|
427
|
-
];
|
|
428
346
|
break;
|
|
429
347
|
default:
|
|
430
348
|
console.error(`Platform ${platform} not explicitly supported for browser path detection`);
|
|
@@ -476,26 +394,20 @@ export function detectBravePath() {
|
|
|
476
394
|
console.error(` - Set BRAVE_PATH environment variable if needed`);
|
|
477
395
|
console.error(`\n 2. Environment Variables:`);
|
|
478
396
|
console.error(` - Set BRAVE_PATH for Brave: set BRAVE_PATH="C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"`);
|
|
479
|
-
console.error(` -
|
|
480
|
-
console.error(
|
|
481
|
-
console.error(`\n 3. Alternative: Install Chrome:`);
|
|
482
|
-
console.error(` - Download Chrome: https://www.google.com/chrome/`);
|
|
483
|
-
console.error(` - Chrome works as fallback when Brave is not available`);
|
|
484
|
-
console.error(`\n 4. Permissions & Security:`);
|
|
397
|
+
console.error(` - For Cursor IDE: Add BRAVE_PATH env var to MCP configuration`);
|
|
398
|
+
console.error(`\n 3. Permissions & Security:`);
|
|
485
399
|
console.error(` - Run IDE/terminal as Administrator`);
|
|
486
|
-
console.error(` - Add browser to Windows Defender exclusions`);
|
|
487
|
-
console.error(`\n
|
|
488
|
-
console.error(` - Use customConfig.chromePath parameter in browser_init`);
|
|
489
|
-
console.error(` -
|
|
400
|
+
console.error(` - Add Brave browser to Windows Defender exclusions`);
|
|
401
|
+
console.error(`\n 4. Custom Configuration:`);
|
|
402
|
+
console.error(` - Use customConfig.chromePath parameter in browser_init (with Brave path)`);
|
|
403
|
+
console.error(` - This project is optimized specifically for Brave Browser`);
|
|
490
404
|
}
|
|
491
405
|
else {
|
|
492
|
-
console.error(`โ
|
|
406
|
+
console.error(`โ Brave Browser not found at any expected paths for platform: ${platform}`);
|
|
493
407
|
console.error(`\n ๐ฆ Brave paths checked:`);
|
|
494
408
|
bravePaths.forEach(p => console.error(` - ${p}`));
|
|
495
|
-
console.error(`\n
|
|
496
|
-
|
|
497
|
-
console.error(`\n ๐ก Install Brave Browser (recommended): https://brave.com/download/`);
|
|
498
|
-
console.error(` ๐ก Or install Chrome as fallback: https://www.google.com/chrome/`);
|
|
409
|
+
console.error(`\n ๐ก Install Brave Browser: https://brave.com/download/`);
|
|
410
|
+
console.error(` ๐ก This project is specifically designed for Brave Browser`);
|
|
499
411
|
}
|
|
500
412
|
return null;
|
|
501
413
|
}
|
|
@@ -813,7 +725,7 @@ export async function initializeBrowser(options) {
|
|
|
813
725
|
}
|
|
814
726
|
throw connectionError;
|
|
815
727
|
}
|
|
816
|
-
}, platform === 'win32' ?
|
|
728
|
+
}, platform === 'win32' ? 30000 : 25000, `browser-connection-${strategyName.toLowerCase().replace(/\s+/g, '-')}`);
|
|
817
729
|
const { browser, page } = result;
|
|
818
730
|
browserInstance = browser;
|
|
819
731
|
pageInstance = page;
|
|
@@ -954,31 +866,25 @@ export async function closeBrowser() {
|
|
|
954
866
|
}
|
|
955
867
|
}
|
|
956
868
|
}
|
|
957
|
-
// Force kill all Brave
|
|
869
|
+
// Force kill all Brave browser processes system-wide
|
|
958
870
|
export async function forceKillBraveProcesses() {
|
|
959
871
|
try {
|
|
960
872
|
const { spawn } = await import('child_process');
|
|
961
873
|
if (process.platform !== 'win32') {
|
|
962
|
-
// Kill Brave processes
|
|
874
|
+
// Kill Brave processes on Unix-like systems
|
|
963
875
|
spawn('pkill', ['-f', 'Brave Browser'], { stdio: 'ignore' });
|
|
964
876
|
spawn('pkill', ['-f', 'brave'], { stdio: 'ignore' });
|
|
965
|
-
// Kill Chrome processes (fallback)
|
|
966
|
-
spawn('pkill', ['-f', 'Google Chrome'], { stdio: 'ignore' });
|
|
967
|
-
spawn('pkill', ['-f', 'chrome'], { stdio: 'ignore' });
|
|
968
877
|
}
|
|
969
878
|
else {
|
|
970
|
-
// Windows: Kill Brave processes
|
|
879
|
+
// Windows: Kill Brave processes
|
|
971
880
|
spawn('taskkill', ['/F', '/IM', 'brave.exe'], { stdio: 'ignore' });
|
|
972
|
-
// Windows: Kill Chrome processes (fallback)
|
|
973
|
-
spawn('taskkill', ['/F', '/IM', 'chrome.exe'], { stdio: 'ignore' });
|
|
974
|
-
spawn('taskkill', ['/F', '/IM', 'GoogleChrome.exe'], { stdio: 'ignore' });
|
|
975
881
|
}
|
|
976
882
|
}
|
|
977
883
|
catch (error) {
|
|
978
|
-
console.error('Error force-killing browser processes:', error);
|
|
884
|
+
console.error('Error force-killing Brave browser processes:', error);
|
|
979
885
|
}
|
|
980
886
|
}
|
|
981
|
-
//
|
|
887
|
+
// Aliases for backward compatibility (now all point to Brave-only function)
|
|
982
888
|
export const forceKillChromeProcesses = forceKillBraveProcesses;
|
|
983
889
|
export const forceKillAllChromeProcesses = forceKillBraveProcesses;
|
|
984
890
|
// Getters for browser instances
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - AAA Pattern (Arrange-Act-Assert)
|
|
6
6
|
* - Behavior-focused testing with proper mocking
|
|
7
7
|
* - Error categorization and circuit breaker testing
|
|
8
|
-
* -
|
|
8
|
+
* - Brave detection and network utilities testing
|
|
9
9
|
*/
|
|
10
10
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
11
11
|
import { categorizeError, BrowserErrorType, withTimeout, isPortAvailable, testHostConnectivity, findAvailablePort, updateCircuitBreakerOnFailure, updateCircuitBreakerOnSuccess, isCircuitBreakerOpen, detectBravePath, validateSession, findAuthElements, getContentPriorityConfig, updateContentPriorityConfig, getBrowserInstance, getPageInstance, forceKillBraveProcesses } from './browser-manager.js';
|
|
@@ -110,8 +110,8 @@ describe('Browser Manager', () => {
|
|
|
110
110
|
});
|
|
111
111
|
describe('Timeout Wrapper', () => {
|
|
112
112
|
it('should resolve when operation completes within timeout', async () => {
|
|
113
|
-
// Arrange: Create operation that resolves
|
|
114
|
-
const operation = vi.fn().
|
|
113
|
+
// Arrange: Create operation that resolves immediately
|
|
114
|
+
const operation = vi.fn().mockImplementation(() => Promise.resolve('success'));
|
|
115
115
|
// Act: Execute with timeout
|
|
116
116
|
const result = await withTimeout(operation, 1000, 'test-context');
|
|
117
117
|
// Assert: Should return operation result
|
|
@@ -127,21 +127,22 @@ describe('Browser Manager', () => {
|
|
|
127
127
|
expect(operation).toHaveBeenCalledOnce();
|
|
128
128
|
});
|
|
129
129
|
it('should reject when operation throws error', async () => {
|
|
130
|
-
// Arrange: Create operation that throws
|
|
131
|
-
const operation = vi.fn().
|
|
130
|
+
// Arrange: Create operation that throws immediately
|
|
131
|
+
const operation = vi.fn().mockImplementation(() => Promise.reject(new Error('operation failed')));
|
|
132
132
|
// Act & Assert: Should propagate operation error
|
|
133
133
|
await expect(withTimeout(operation, 1000, 'test-context'))
|
|
134
134
|
.rejects.toThrow('operation failed');
|
|
135
135
|
expect(operation).toHaveBeenCalledOnce();
|
|
136
136
|
});
|
|
137
137
|
it('should clear timeout when operation completes', async () => {
|
|
138
|
-
// Arrange: Create operation that resolves
|
|
139
|
-
const operation = vi.fn().
|
|
138
|
+
// Arrange: Create operation that resolves immediately
|
|
139
|
+
const operation = vi.fn().mockImplementation(() => Promise.resolve('success'));
|
|
140
140
|
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
|
141
141
|
// Act: Execute with timeout
|
|
142
142
|
await withTimeout(operation, 1000, 'test-context');
|
|
143
143
|
// Assert: Should clear timeout
|
|
144
144
|
expect(clearTimeoutSpy).toHaveBeenCalled();
|
|
145
|
+
clearTimeoutSpy.mockRestore();
|
|
145
146
|
});
|
|
146
147
|
});
|
|
147
148
|
describe('Port Availability', () => {
|
|
@@ -46,7 +46,7 @@ export async function handleRESTAPIEndpointFinder(args) {
|
|
|
46
46
|
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
|
|
47
47
|
}
|
|
48
48
|
// Wait for additional requests
|
|
49
|
-
await
|
|
49
|
+
await new Promise(resolve => setTimeout(resolve, scanDuration));
|
|
50
50
|
page.off('request', requestHandler);
|
|
51
51
|
}
|
|
52
52
|
// Also scan page content for API endpoints
|
|
@@ -207,7 +207,7 @@ export async function handlePuzzleCaptchaHandler(args) {
|
|
|
207
207
|
const stepSize = targetDistance / steps;
|
|
208
208
|
for (let i = 0; i < steps; i++) {
|
|
209
209
|
await page.mouse.move(box.x + box.width / 2 + (stepSize * i), box.y + box.height / 2, { steps: 5 });
|
|
210
|
-
await
|
|
210
|
+
await new Promise(resolve => setTimeout(resolve, 50 + Math.random() * 50)); // Random delay for human-like behavior
|
|
211
211
|
}
|
|
212
212
|
await page.mouse.up();
|
|
213
213
|
result.attemptedSolve = true;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Pagination & Navigation Tools
|
|
2
2
|
// Auto pagination, infinite scroll, multi-page scraping, sitemap parser
|
|
3
3
|
// @ts-nocheck
|
|
4
|
-
import {
|
|
4
|
+
import { getPageInstance } from '../browser-manager.js';
|
|
5
5
|
import { validateWorkflow } from '../workflow-validation.js';
|
|
6
6
|
import { withErrorHandling } from '../system-utils.js';
|
|
7
7
|
import * as xml2js from 'xml2js';
|
|
@@ -14,7 +14,10 @@ export async function handleAutoPagination(args) {
|
|
|
14
14
|
requireBrowser: true,
|
|
15
15
|
requirePage: true,
|
|
16
16
|
});
|
|
17
|
-
const page =
|
|
17
|
+
const page = getPageInstance();
|
|
18
|
+
if (!page) {
|
|
19
|
+
throw new Error('Browser not initialized. Call browser_init first.');
|
|
20
|
+
}
|
|
18
21
|
const nextButtonSelector = args.nextButtonSelector || 'a[rel="next"], button:contains("Next"), .next, .pagination-next';
|
|
19
22
|
const maxPages = args.maxPages || 10;
|
|
20
23
|
const dataSelector = args.dataSelector;
|
|
@@ -51,14 +54,14 @@ export async function handleAutoPagination(args) {
|
|
|
51
54
|
}
|
|
52
55
|
// Click next button
|
|
53
56
|
await nextButton.click();
|
|
54
|
-
await
|
|
57
|
+
await new Promise(resolve => setTimeout(resolve, waitBetweenPages));
|
|
55
58
|
// Wait for navigation or content load
|
|
56
59
|
try {
|
|
57
60
|
await page.waitForNavigation({ timeout: 5000, waitUntil: 'domcontentloaded' });
|
|
58
61
|
}
|
|
59
62
|
catch (e) {
|
|
60
63
|
// No navigation occurred, content loaded dynamically
|
|
61
|
-
await
|
|
64
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
62
65
|
}
|
|
63
66
|
currentPage++;
|
|
64
67
|
}
|
|
@@ -81,7 +84,10 @@ export async function handleInfiniteScroll(args) {
|
|
|
81
84
|
requireBrowser: true,
|
|
82
85
|
requirePage: true,
|
|
83
86
|
});
|
|
84
|
-
const page =
|
|
87
|
+
const page = getPageInstance();
|
|
88
|
+
if (!page) {
|
|
89
|
+
throw new Error('Browser not initialized. Call browser_init first.');
|
|
90
|
+
}
|
|
85
91
|
const maxScrolls = args.maxScrolls || 10;
|
|
86
92
|
const scrollDelay = args.scrollDelay || 1000;
|
|
87
93
|
const dataSelector = args.dataSelector;
|
|
@@ -114,7 +120,7 @@ export async function handleInfiniteScroll(args) {
|
|
|
114
120
|
window.scrollTo(0, document.body.scrollHeight);
|
|
115
121
|
});
|
|
116
122
|
// Wait for new content to load
|
|
117
|
-
await
|
|
123
|
+
await new Promise(resolve => setTimeout(resolve, scrollDelay));
|
|
118
124
|
scrollCount++;
|
|
119
125
|
}
|
|
120
126
|
return {
|
|
@@ -136,7 +142,10 @@ export async function handleMultiPageScraper(args) {
|
|
|
136
142
|
requireBrowser: true,
|
|
137
143
|
requirePage: true,
|
|
138
144
|
});
|
|
139
|
-
const page =
|
|
145
|
+
const page = getPageInstance();
|
|
146
|
+
if (!page) {
|
|
147
|
+
throw new Error('Browser not initialized. Call browser_init first.');
|
|
148
|
+
}
|
|
140
149
|
const urls = args.urls;
|
|
141
150
|
const dataSelector = args.dataSelector;
|
|
142
151
|
const waitBetweenPages = args.waitBetweenPages || 1000;
|
|
@@ -145,7 +154,7 @@ export async function handleMultiPageScraper(args) {
|
|
|
145
154
|
const url = urls[i];
|
|
146
155
|
try {
|
|
147
156
|
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
148
|
-
await
|
|
157
|
+
await new Promise(resolve => setTimeout(resolve, waitBetweenPages));
|
|
149
158
|
const pageData = await page.evaluate((selector) => {
|
|
150
159
|
const elements = document.querySelectorAll(selector);
|
|
151
160
|
return Array.from(elements).map((el) => ({
|
|
@@ -187,7 +196,10 @@ export async function handleSitemapParser(args) {
|
|
|
187
196
|
requireBrowser: true,
|
|
188
197
|
requirePage: true,
|
|
189
198
|
});
|
|
190
|
-
const page =
|
|
199
|
+
const page = getPageInstance();
|
|
200
|
+
if (!page) {
|
|
201
|
+
throw new Error('Browser not initialized. Call browser_init first.');
|
|
202
|
+
}
|
|
191
203
|
const currentUrl = page.url();
|
|
192
204
|
const baseUrl = new URL(currentUrl).origin;
|
|
193
205
|
const sitemapUrl = args.sitemapUrl || `${baseUrl}/sitemap.xml`;
|
|
@@ -255,7 +267,10 @@ export async function handleBreadcrumbNavigator(args) {
|
|
|
255
267
|
requireBrowser: true,
|
|
256
268
|
requirePage: true,
|
|
257
269
|
});
|
|
258
|
-
const page =
|
|
270
|
+
const page = getPageInstance();
|
|
271
|
+
if (!page) {
|
|
272
|
+
throw new Error('Browser not initialized. Call browser_init first.');
|
|
273
|
+
}
|
|
259
274
|
const breadcrumbSelector = args.breadcrumbSelector || '.breadcrumb, nav[aria-label="breadcrumb"], .breadcrumbs';
|
|
260
275
|
const followLinks = args.followLinks || false;
|
|
261
276
|
const breadcrumbData = await page.evaluate((selector) => {
|
|
@@ -236,7 +236,7 @@ export async function handleVideoRecording(args) {
|
|
|
236
236
|
const framePath = path.join(framesDir, `frame_${i.toString().padStart(4, '0')}.png`);
|
|
237
237
|
await page.screenshot({ path: framePath });
|
|
238
238
|
frames.push(framePath);
|
|
239
|
-
await
|
|
239
|
+
await new Promise(resolve => setTimeout(resolve, frameDelay));
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
return {
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { TOOLS, SERVER_INFO, CAPABILITIES, TOOL_NAMES } from './tool-definitions
|
|
|
13
13
|
console.error('๐ [DEBUG] Loading system utils...');
|
|
14
14
|
import { withErrorHandling } from './system-utils.js';
|
|
15
15
|
console.error('๐ [DEBUG] Loading browser manager...');
|
|
16
|
-
import { closeBrowser,
|
|
16
|
+
import { closeBrowser, forceKillBraveProcesses } from './browser-manager.js';
|
|
17
17
|
console.error('๐ [DEBUG] Loading core infrastructure...');
|
|
18
18
|
import { setupProcessCleanup } from './core-infrastructure.js';
|
|
19
19
|
// Import handlers
|
|
@@ -266,7 +266,7 @@ async function main() {
|
|
|
266
266
|
setupProcessCleanup(async () => {
|
|
267
267
|
console.error('๐ [DEBUG] Process cleanup triggered');
|
|
268
268
|
await closeBrowser();
|
|
269
|
-
await
|
|
269
|
+
await forceKillBraveProcesses();
|
|
270
270
|
});
|
|
271
271
|
// Create and start the server transport
|
|
272
272
|
console.error('๐ [DEBUG] Creating StdioServerTransport...');
|
package/dist/tool-definitions.js
CHANGED
|
@@ -25,7 +25,7 @@ export const DEFAULT_CONTENT_PRIORITY_CONFIG = {
|
|
|
25
25
|
export const TOOLS = [
|
|
26
26
|
{
|
|
27
27
|
name: 'browser_init',
|
|
28
|
-
description: 'Initialize a new browser instance with anti-detection features and automatic
|
|
28
|
+
description: 'Initialize a new browser instance with anti-detection features and automatic Brave Browser path detection',
|
|
29
29
|
inputSchema: {
|
|
30
30
|
type: 'object',
|
|
31
31
|
properties: {
|
|
@@ -41,7 +41,7 @@ export const TOOLS = [
|
|
|
41
41
|
},
|
|
42
42
|
ignoreAllFlags: {
|
|
43
43
|
type: 'boolean',
|
|
44
|
-
description: 'Ignore all
|
|
44
|
+
description: 'Ignore all browser flags (recommended: true for clean startup without --no-sandbox)',
|
|
45
45
|
default: true,
|
|
46
46
|
},
|
|
47
47
|
proxy: {
|
|
@@ -62,11 +62,11 @@ export const TOOLS = [
|
|
|
62
62
|
},
|
|
63
63
|
customConfig: {
|
|
64
64
|
type: 'object',
|
|
65
|
-
description: 'Custom configuration for
|
|
65
|
+
description: 'Custom configuration for Brave launcher. Use chromePath to specify custom Brave executable path',
|
|
66
66
|
properties: {
|
|
67
67
|
chromePath: {
|
|
68
68
|
type: 'string',
|
|
69
|
-
description: 'Custom path to
|
|
69
|
+
description: 'Custom path to Brave executable (auto-detected if not specified)',
|
|
70
70
|
},
|
|
71
71
|
},
|
|
72
72
|
additionalProperties: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.6",
|
|
4
4
|
"description": "MCP server for brave-real-browser",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,13 +34,14 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.20.0",
|
|
37
|
+
"@types/pngjs": "^6.0.5",
|
|
37
38
|
"@types/turndown": "^5.0.5",
|
|
38
39
|
"ajv": "^8.12.0",
|
|
39
|
-
"axios": "^1.
|
|
40
|
+
"axios": "^1.12.2",
|
|
40
41
|
"brave-real-browser": "^1.5.102",
|
|
41
42
|
"brave-real-launcher": "^1.2.16",
|
|
42
|
-
"brave-real-playwright-core": "^1.
|
|
43
|
-
"brave-real-puppeteer-core": "^24.
|
|
43
|
+
"brave-real-playwright-core": "^1.56.0",
|
|
44
|
+
"brave-real-puppeteer-core": "^24.24.0",
|
|
44
45
|
"cheerio": "^1.0.0-rc.12",
|
|
45
46
|
"chrono-node": "^2.7.0",
|
|
46
47
|
"compromise": "^14.13.0",
|