brave-real-browser-mcp-server 2.9.4 โ 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 +71 -153
- 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 +10 -5
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
|
@@ -4,20 +4,30 @@ import * as path from 'path';
|
|
|
4
4
|
import * as net from 'net';
|
|
5
5
|
// Import Brave launcher for professional Brave detection
|
|
6
6
|
let braveLauncher = null;
|
|
7
|
-
try {
|
|
8
|
-
braveLauncher = require('brave-real-launcher');
|
|
9
|
-
}
|
|
10
|
-
catch (error) {
|
|
11
|
-
console.error('โ ๏ธ brave-real-launcher not available, using fallback detection');
|
|
12
|
-
}
|
|
13
7
|
// Import brave-real-puppeteer-core for enhanced stealth features
|
|
14
8
|
let braveRealPuppeteerCore = null;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
// Async function to load brave packages
|
|
10
|
+
async function loadBravePackages() {
|
|
11
|
+
// Load brave-real-launcher (CommonJS module)
|
|
12
|
+
try {
|
|
13
|
+
const { createRequire } = await import('module');
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
braveLauncher = require('brave-real-launcher');
|
|
16
|
+
console.log('โ
brave-real-launcher loaded - professional Brave detection enabled');
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
console.warn('โ ๏ธ brave-real-launcher not available, using fallback detection:', error.message);
|
|
20
|
+
}
|
|
21
|
+
// Load brave-real-puppeteer-core (CommonJS module)
|
|
22
|
+
try {
|
|
23
|
+
const { createRequire } = await import('module');
|
|
24
|
+
const require = createRequire(import.meta.url);
|
|
25
|
+
braveRealPuppeteerCore = require('brave-real-puppeteer-core');
|
|
26
|
+
console.log('โ
brave-real-puppeteer-core loaded - enhanced stealth features available');
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.warn('โ ๏ธ brave-real-puppeteer-core not available, using standard puppeteer:', error.message);
|
|
30
|
+
}
|
|
21
31
|
}
|
|
22
32
|
// Browser error categorization
|
|
23
33
|
export var BrowserErrorType;
|
|
@@ -99,33 +109,48 @@ export function categorizeError(error) {
|
|
|
99
109
|
// Timeout wrapper for operations that may hang
|
|
100
110
|
export async function withTimeout(operation, timeoutMs, context = 'unknown') {
|
|
101
111
|
return new Promise((resolve, reject) => {
|
|
112
|
+
let isResolved = false;
|
|
102
113
|
const timer = setTimeout(() => {
|
|
103
|
-
|
|
114
|
+
if (!isResolved) {
|
|
115
|
+
isResolved = true;
|
|
116
|
+
reject(new Error(`Operation timed out after ${timeoutMs}ms in context: ${context}`));
|
|
117
|
+
}
|
|
104
118
|
}, timeoutMs);
|
|
105
119
|
operation()
|
|
106
120
|
.then((result) => {
|
|
107
|
-
|
|
108
|
-
|
|
121
|
+
if (!isResolved) {
|
|
122
|
+
isResolved = true;
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
resolve(result);
|
|
125
|
+
}
|
|
109
126
|
})
|
|
110
127
|
.catch((error) => {
|
|
111
|
-
|
|
112
|
-
|
|
128
|
+
if (!isResolved) {
|
|
129
|
+
isResolved = true;
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
reject(error);
|
|
132
|
+
}
|
|
113
133
|
});
|
|
114
134
|
});
|
|
115
135
|
}
|
|
116
136
|
// Port availability and connection utilities for enhanced resilience
|
|
117
137
|
export async function isPortAvailable(port, host = '127.0.0.1') {
|
|
118
138
|
return new Promise((resolve) => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
server.
|
|
122
|
-
|
|
139
|
+
try {
|
|
140
|
+
const server = net.createServer();
|
|
141
|
+
server.listen(port, host, () => {
|
|
142
|
+
server.once('close', () => {
|
|
143
|
+
resolve(true);
|
|
144
|
+
});
|
|
145
|
+
server.close();
|
|
123
146
|
});
|
|
124
|
-
server.
|
|
125
|
-
|
|
126
|
-
|
|
147
|
+
server.on('error', () => {
|
|
148
|
+
resolve(false);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
127
152
|
resolve(false);
|
|
128
|
-
}
|
|
153
|
+
}
|
|
129
154
|
});
|
|
130
155
|
}
|
|
131
156
|
// Test localhost resolution and connectivity
|
|
@@ -237,57 +262,8 @@ function getWindowsBraveFromRegistry() {
|
|
|
237
262
|
}
|
|
238
263
|
return null;
|
|
239
264
|
}
|
|
240
|
-
// Windows Registry Chrome detection
|
|
241
|
-
function getWindowsChromeFromRegistry() {
|
|
242
|
-
if (process.platform !== 'win32')
|
|
243
|
-
return null;
|
|
244
|
-
try {
|
|
245
|
-
const { execSync } = require('child_process');
|
|
246
|
-
const registryQueries = [
|
|
247
|
-
'reg query "HKEY_CURRENT_USER\\Software\\Google\\Chrome\\BLBeacon" /v version 2>nul',
|
|
248
|
-
'reg query "HKEY_LOCAL_MACHINE\\Software\\Google\\Chrome\\BLBeacon" /v version 2>nul',
|
|
249
|
-
'reg query "HKEY_LOCAL_MACHINE\\Software\\WOW6432Node\\Google\\Chrome\\BLBeacon" /v version 2>nul',
|
|
250
|
-
];
|
|
251
|
-
for (const query of registryQueries) {
|
|
252
|
-
try {
|
|
253
|
-
const result = execSync(query, { encoding: 'utf8', timeout: 5000 });
|
|
254
|
-
if (result) {
|
|
255
|
-
const standardPaths = [
|
|
256
|
-
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
257
|
-
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
|
|
258
|
-
];
|
|
259
|
-
for (const standardPath of standardPaths) {
|
|
260
|
-
if (fs.existsSync(standardPath)) {
|
|
261
|
-
console.error(`โ Found Chrome via Registry detection: ${standardPath}`);
|
|
262
|
-
return standardPath;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
catch (error) {
|
|
268
|
-
// Continue to next registry query
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
try {
|
|
272
|
-
const installDirQuery = 'reg query "HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe" /ve 2>nul';
|
|
273
|
-
const result = execSync(installDirQuery, { encoding: 'utf8', timeout: 5000 });
|
|
274
|
-
const match = result.match(/REG_SZ\s+(.+\.exe)/);
|
|
275
|
-
if (match && match[1] && fs.existsSync(match[1])) {
|
|
276
|
-
console.error(`โ Found Chrome via App Paths registry: ${match[1]}`);
|
|
277
|
-
return match[1];
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
// Registry detection failed, will fall back to file system search
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
catch (error) {
|
|
285
|
-
console.error('Windows Registry Chrome detection failed:', error instanceof Error ? error.message : String(error));
|
|
286
|
-
}
|
|
287
|
-
return null;
|
|
288
|
-
}
|
|
289
265
|
// Brave Browser path detection (cross-platform support)
|
|
290
|
-
//
|
|
266
|
+
// Pure Brave-only detection - This project is designed specifically for Brave Browser
|
|
291
267
|
export function detectBravePath() {
|
|
292
268
|
const platform = process.platform;
|
|
293
269
|
// PRIORITY -1: Use brave-real-launcher's professional detection (BEST METHOD)
|
|
@@ -317,20 +293,14 @@ export function detectBravePath() {
|
|
|
317
293
|
catch (error) {
|
|
318
294
|
// Config file not found or invalid, continue with other methods
|
|
319
295
|
}
|
|
320
|
-
// PRIORITY 1: Check environment variables first (BRAVE_PATH
|
|
296
|
+
// PRIORITY 1: Check environment variables first (BRAVE_PATH only)
|
|
321
297
|
const envBravePath = process.env.BRAVE_PATH;
|
|
322
298
|
if (envBravePath && fs.existsSync(envBravePath)) {
|
|
323
299
|
console.error(`โ Found Brave via BRAVE_PATH environment variable: ${envBravePath}`);
|
|
324
300
|
return envBravePath;
|
|
325
301
|
}
|
|
326
|
-
|
|
327
|
-
if (envChromePath && fs.existsSync(envChromePath)) {
|
|
328
|
-
console.error(`โ Found Chrome via environment variable: ${envChromePath}`);
|
|
329
|
-
return envChromePath;
|
|
330
|
-
}
|
|
331
|
-
// PRIORITY 2: Try Brave paths FIRST (this is Brave-Real-Browser project!)
|
|
302
|
+
// PRIORITY 2: Brave paths detection (Brave-only project!)
|
|
332
303
|
let bravePaths = [];
|
|
333
|
-
let chromePaths = [];
|
|
334
304
|
switch (platform) {
|
|
335
305
|
case 'win32':
|
|
336
306
|
// BRAVE PATHS (PRIORITY - Try these first!)
|
|
@@ -342,23 +312,7 @@ export function detectBravePath() {
|
|
|
342
312
|
path.join(process.env.PROGRAMFILES || '', 'BraveSoftware\\Brave-Browser\\Application\\brave.exe'),
|
|
343
313
|
path.join(process.env['PROGRAMFILES(X86)'] || '', 'BraveSoftware\\Brave-Browser\\Application\\brave.exe'),
|
|
344
314
|
];
|
|
345
|
-
//
|
|
346
|
-
chromePaths = [
|
|
347
|
-
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
348
|
-
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
349
|
-
path.join(process.env.LOCALAPPDATA || '', 'Google\\Chrome\\Application\\chrome.exe'),
|
|
350
|
-
path.join(process.env.USERPROFILE || '', 'AppData\\Local\\Google\\Chrome\\Application\\chrome.exe'),
|
|
351
|
-
path.join(process.env.PROGRAMFILES || '', 'Google\\Chrome\\Application\\chrome.exe'),
|
|
352
|
-
path.join(process.env['PROGRAMFILES(X86)'] || '', 'Google\\Chrome\\Application\\chrome.exe'),
|
|
353
|
-
path.join(process.env.LOCALAPPDATA || '', 'Google\\Chrome SxS\\Application\\chrome.exe'),
|
|
354
|
-
'C:\\Program Files\\Google\\Chrome SxS\\Application\\chrome.exe',
|
|
355
|
-
'C:\\Users\\Public\\Desktop\\Google Chrome.exe',
|
|
356
|
-
path.join(process.env.APPDATA || '', 'Google\\Chrome\\Application\\chrome.exe'),
|
|
357
|
-
'C:\\Chrome\\chrome.exe',
|
|
358
|
-
'C:\\google\\chrome\\chrome.exe',
|
|
359
|
-
'C:\\PortableApps\\GoogleChromePortable\\App\\Chrome-bin\\chrome.exe',
|
|
360
|
-
];
|
|
361
|
-
// Try Brave registry first
|
|
315
|
+
// Try Brave registry detection
|
|
362
316
|
try {
|
|
363
317
|
const braveRegistryPath = getWindowsBraveFromRegistry();
|
|
364
318
|
if (braveRegistryPath) {
|
|
@@ -368,16 +322,6 @@ export function detectBravePath() {
|
|
|
368
322
|
catch (error) {
|
|
369
323
|
console.error('Brave registry detection failed, continuing with file system search...');
|
|
370
324
|
}
|
|
371
|
-
// Try Chrome registry as fallback
|
|
372
|
-
try {
|
|
373
|
-
const chromeRegistryPath = getWindowsChromeFromRegistry();
|
|
374
|
-
if (chromeRegistryPath) {
|
|
375
|
-
chromePaths.unshift(chromeRegistryPath);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
catch (error) {
|
|
379
|
-
console.error('Chrome registry detection failed, continuing with file system search...');
|
|
380
|
-
}
|
|
381
325
|
break;
|
|
382
326
|
case 'darwin':
|
|
383
327
|
// BRAVE PATHS (PRIORITY)
|
|
@@ -387,12 +331,6 @@ export function detectBravePath() {
|
|
|
387
331
|
'/Applications/Brave Browser Beta.app/Contents/MacOS/Brave Browser Beta',
|
|
388
332
|
'/Applications/Brave Browser Dev.app/Contents/MacOS/Brave Browser Dev',
|
|
389
333
|
];
|
|
390
|
-
// Chrome paths (fallback)
|
|
391
|
-
chromePaths = [
|
|
392
|
-
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
393
|
-
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
394
|
-
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary'
|
|
395
|
-
];
|
|
396
334
|
break;
|
|
397
335
|
case 'linux':
|
|
398
336
|
// BRAVE PATHS (PRIORITY)
|
|
@@ -405,16 +343,6 @@ export function detectBravePath() {
|
|
|
405
343
|
'/opt/brave/brave-browser',
|
|
406
344
|
'/usr/local/bin/brave-browser',
|
|
407
345
|
];
|
|
408
|
-
// Chrome paths (fallback)
|
|
409
|
-
chromePaths = [
|
|
410
|
-
'/usr/bin/google-chrome',
|
|
411
|
-
'/usr/bin/google-chrome-stable',
|
|
412
|
-
'/usr/bin/chromium-browser',
|
|
413
|
-
'/usr/bin/chromium',
|
|
414
|
-
'/snap/bin/chromium',
|
|
415
|
-
'/usr/bin/chrome',
|
|
416
|
-
'/opt/google/chrome/chrome'
|
|
417
|
-
];
|
|
418
346
|
break;
|
|
419
347
|
default:
|
|
420
348
|
console.error(`Platform ${platform} not explicitly supported for browser path detection`);
|
|
@@ -466,26 +394,20 @@ export function detectBravePath() {
|
|
|
466
394
|
console.error(` - Set BRAVE_PATH environment variable if needed`);
|
|
467
395
|
console.error(`\n 2. Environment Variables:`);
|
|
468
396
|
console.error(` - Set BRAVE_PATH for Brave: set BRAVE_PATH="C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"`);
|
|
469
|
-
console.error(` -
|
|
470
|
-
console.error(
|
|
471
|
-
console.error(`\n 3. Alternative: Install Chrome:`);
|
|
472
|
-
console.error(` - Download Chrome: https://www.google.com/chrome/`);
|
|
473
|
-
console.error(` - Chrome works as fallback when Brave is not available`);
|
|
474
|
-
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:`);
|
|
475
399
|
console.error(` - Run IDE/terminal as Administrator`);
|
|
476
|
-
console.error(` - Add browser to Windows Defender exclusions`);
|
|
477
|
-
console.error(`\n
|
|
478
|
-
console.error(` - Use customConfig.chromePath parameter in browser_init`);
|
|
479
|
-
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`);
|
|
480
404
|
}
|
|
481
405
|
else {
|
|
482
|
-
console.error(`โ
|
|
406
|
+
console.error(`โ Brave Browser not found at any expected paths for platform: ${platform}`);
|
|
483
407
|
console.error(`\n ๐ฆ Brave paths checked:`);
|
|
484
408
|
bravePaths.forEach(p => console.error(` - ${p}`));
|
|
485
|
-
console.error(`\n
|
|
486
|
-
|
|
487
|
-
console.error(`\n ๐ก Install Brave Browser (recommended): https://brave.com/download/`);
|
|
488
|
-
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`);
|
|
489
411
|
}
|
|
490
412
|
return null;
|
|
491
413
|
}
|
|
@@ -617,6 +539,8 @@ export async function initializeBrowser(options) {
|
|
|
617
539
|
rejectInit = reject;
|
|
618
540
|
});
|
|
619
541
|
try {
|
|
542
|
+
// Load brave packages first
|
|
543
|
+
await loadBravePackages();
|
|
620
544
|
const detectedBravePath = detectBravePath();
|
|
621
545
|
const customConfig = options?.customConfig ?? {};
|
|
622
546
|
const platform = process.platform;
|
|
@@ -801,7 +725,7 @@ export async function initializeBrowser(options) {
|
|
|
801
725
|
}
|
|
802
726
|
throw connectionError;
|
|
803
727
|
}
|
|
804
|
-
}, platform === 'win32' ?
|
|
728
|
+
}, platform === 'win32' ? 30000 : 25000, `browser-connection-${strategyName.toLowerCase().replace(/\s+/g, '-')}`);
|
|
805
729
|
const { browser, page } = result;
|
|
806
730
|
browserInstance = browser;
|
|
807
731
|
pageInstance = page;
|
|
@@ -942,31 +866,25 @@ export async function closeBrowser() {
|
|
|
942
866
|
}
|
|
943
867
|
}
|
|
944
868
|
}
|
|
945
|
-
// Force kill all Brave
|
|
869
|
+
// Force kill all Brave browser processes system-wide
|
|
946
870
|
export async function forceKillBraveProcesses() {
|
|
947
871
|
try {
|
|
948
872
|
const { spawn } = await import('child_process');
|
|
949
873
|
if (process.platform !== 'win32') {
|
|
950
|
-
// Kill Brave processes
|
|
874
|
+
// Kill Brave processes on Unix-like systems
|
|
951
875
|
spawn('pkill', ['-f', 'Brave Browser'], { stdio: 'ignore' });
|
|
952
876
|
spawn('pkill', ['-f', 'brave'], { stdio: 'ignore' });
|
|
953
|
-
// Kill Chrome processes (fallback)
|
|
954
|
-
spawn('pkill', ['-f', 'Google Chrome'], { stdio: 'ignore' });
|
|
955
|
-
spawn('pkill', ['-f', 'chrome'], { stdio: 'ignore' });
|
|
956
877
|
}
|
|
957
878
|
else {
|
|
958
|
-
// Windows: Kill Brave processes
|
|
879
|
+
// Windows: Kill Brave processes
|
|
959
880
|
spawn('taskkill', ['/F', '/IM', 'brave.exe'], { stdio: 'ignore' });
|
|
960
|
-
// Windows: Kill Chrome processes (fallback)
|
|
961
|
-
spawn('taskkill', ['/F', '/IM', 'chrome.exe'], { stdio: 'ignore' });
|
|
962
|
-
spawn('taskkill', ['/F', '/IM', 'GoogleChrome.exe'], { stdio: 'ignore' });
|
|
963
881
|
}
|
|
964
882
|
}
|
|
965
883
|
catch (error) {
|
|
966
|
-
console.error('Error force-killing browser processes:', error);
|
|
884
|
+
console.error('Error force-killing Brave browser processes:', error);
|
|
967
885
|
}
|
|
968
886
|
}
|
|
969
|
-
//
|
|
887
|
+
// Aliases for backward compatibility (now all point to Brave-only function)
|
|
970
888
|
export const forceKillChromeProcesses = forceKillBraveProcesses;
|
|
971
889
|
export const forceKillAllChromeProcesses = forceKillBraveProcesses;
|
|
972
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,12 +1,13 @@
|
|
|
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",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"install-brave": "node scripts/install-brave.cjs",
|
|
9
|
-
"
|
|
9
|
+
"update-brave-deps": "node scripts/update-brave-dependencies.cjs",
|
|
10
|
+
"postinstall": "node scripts/setup-brave.cjs && node scripts/update-brave-dependencies.cjs || echo 'Auto-setup skipped'",
|
|
10
11
|
"clean": "rimraf dist",
|
|
11
12
|
"clean:cache": "npm cache clean --force",
|
|
12
13
|
"fix-cache-permissions": "echo 'Run: sudo chown -R $(whoami):$(id -gn) ~/.npm' && echo 'This fixes npm cache permission issues'",
|
|
@@ -33,10 +34,14 @@
|
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
35
36
|
"@modelcontextprotocol/sdk": "^1.20.0",
|
|
37
|
+
"@types/pngjs": "^6.0.5",
|
|
36
38
|
"@types/turndown": "^5.0.5",
|
|
37
39
|
"ajv": "^8.12.0",
|
|
38
|
-
"axios": "^1.
|
|
40
|
+
"axios": "^1.12.2",
|
|
39
41
|
"brave-real-browser": "^1.5.102",
|
|
42
|
+
"brave-real-launcher": "^1.2.16",
|
|
43
|
+
"brave-real-playwright-core": "^1.56.0",
|
|
44
|
+
"brave-real-puppeteer-core": "^24.24.0",
|
|
40
45
|
"cheerio": "^1.0.0-rc.12",
|
|
41
46
|
"chrono-node": "^2.7.0",
|
|
42
47
|
"compromise": "^14.13.0",
|
|
@@ -45,9 +50,9 @@
|
|
|
45
50
|
"natural": "^6.12.0",
|
|
46
51
|
"pixelmatch": "^5.3.0",
|
|
47
52
|
"pngjs": "^7.0.0",
|
|
48
|
-
"puppeteer-screen-recorder": "^3.0.
|
|
53
|
+
"puppeteer-screen-recorder": "^3.0.6",
|
|
49
54
|
"sentiment": "^5.0.2",
|
|
50
|
-
"tesseract.js": "^
|
|
55
|
+
"tesseract.js": "^6.0.1",
|
|
51
56
|
"turndown": "^7.2.1",
|
|
52
57
|
"xml2js": "^0.6.2"
|
|
53
58
|
},
|