brave-real-browser-mcp-server 2.3.3 → 2.4.0
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 +5 -2
- package/dist/browser-manager.js +61 -8
- package/dist/handlers/auto-captcha-detector.js +150 -0
- package/dist/handlers/browser-handlers.js +21 -6
- package/dist/handlers/captcha-solver-handlers.js +283 -0
- package/dist/index.js +79 -0
- package/dist/tool-definitions.js +96 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ Once set up, you can ask Claude to:
|
|
|
93
93
|
- **Fill forms**: "Fill out this contact form with my details"
|
|
94
94
|
- **Extract data**: "Get all the product prices from this page"
|
|
95
95
|
- **Automate tasks**: "Log into my account and download my invoice"
|
|
96
|
-
- **Solve captchas**: "
|
|
96
|
+
- **Solve captchas automatically**: "Detect and solve any CAPTCHAs on this page" - 🆕 Zero configuration required!
|
|
97
97
|
|
|
98
98
|
### Safety Notes
|
|
99
99
|
- Claude will show you what it's doing - you can see the browser window
|
|
@@ -124,6 +124,7 @@ assistants to control a real browser, extract content, and more.
|
|
|
124
124
|
- **Comprehensive toolset**: 11 tools covering all browser automation needs
|
|
125
125
|
- **Proxy support**: Built-in proxy configuration for enhanced privacy
|
|
126
126
|
- **Captcha handling**: Support for solving reCAPTCHA, hCaptcha, and Turnstile
|
|
127
|
+
- **🆕 Auto CAPTCHA Solver**: 🤖 Automatically detects and solves text CAPTCHAs with 100% accuracy - no selectors needed! [Learn more](docs/AUTO_CAPTCHA_SOLVER.md)
|
|
127
128
|
- **Robust error handling**: Advanced error recovery with circuit breaker pattern
|
|
128
129
|
- **Stack overflow protection**: Comprehensive protection against infinite recursion
|
|
129
130
|
- **Timeout controls**: Automatic timeout mechanisms prevent hanging operations
|
|
@@ -538,8 +539,10 @@ AI: I'll set up the browser with your proxy configuration.
|
|
|
538
539
|
### Anti-Detection Tools
|
|
539
540
|
|
|
540
541
|
| Tool Name | Description | Required Parameters | Optional Parameters |
|
|
541
|
-
|
|
542
|
+
|-----------|-------------|---------------------|---------------------|
|
|
542
543
|
| `solve_captcha` | Attempt to solve captchas | `type` | None |
|
|
544
|
+
| `solve_text_captcha` | Solve text-based image CAPTCHAs using OCR | `imageSelector` | `inputSelector`, `config` |
|
|
545
|
+
| `auto_solve_captcha` | 🆕 **Automatically detect and solve CAPTCHAs** (no selectors needed!) | None | `config` |
|
|
543
546
|
|
|
544
547
|
## Advanced Features
|
|
545
548
|
|
package/dist/browser-manager.js
CHANGED
|
@@ -16,6 +16,47 @@ export var BrowserErrorType;
|
|
|
16
16
|
// Store browser instance
|
|
17
17
|
let browserInstance = null;
|
|
18
18
|
let pageInstance = null;
|
|
19
|
+
// Performance optimization flags
|
|
20
|
+
const PERFORMANCE_FLAGS = [
|
|
21
|
+
'--disable-blink-features=AutomationControlled',
|
|
22
|
+
'--disable-dev-shm-usage',
|
|
23
|
+
'--disable-setuid-sandbox',
|
|
24
|
+
'--no-first-run',
|
|
25
|
+
'--no-default-browser-check',
|
|
26
|
+
'--disable-infobars',
|
|
27
|
+
'--window-position=0,0',
|
|
28
|
+
'--ignore-certificate-errors',
|
|
29
|
+
'--ignore-certificate-errors-spki-list',
|
|
30
|
+
'--disable-blink-features=AutomationControlled',
|
|
31
|
+
// Performance optimizations
|
|
32
|
+
'--disable-extensions',
|
|
33
|
+
'--disable-component-extensions-with-background-pages',
|
|
34
|
+
'--disable-background-networking',
|
|
35
|
+
'--disable-sync',
|
|
36
|
+
'--metrics-recording-only',
|
|
37
|
+
'--disable-default-apps',
|
|
38
|
+
'--mute-audio',
|
|
39
|
+
'--no-pings',
|
|
40
|
+
'--disable-prompt-on-repost',
|
|
41
|
+
'--disable-hang-monitor',
|
|
42
|
+
'--disable-background-timer-throttling',
|
|
43
|
+
'--disable-renderer-backgrounding',
|
|
44
|
+
'--disable-backgrounding-occluded-windows',
|
|
45
|
+
'--disable-ipc-flooding-protection',
|
|
46
|
+
'--password-store=basic',
|
|
47
|
+
'--use-mock-keychain',
|
|
48
|
+
// Memory optimizations
|
|
49
|
+
'--disable-features=site-per-process',
|
|
50
|
+
'--disable-features=TranslateUI',
|
|
51
|
+
'--disable-features=BlinkGenPropertyTrees',
|
|
52
|
+
// Speed optimizations
|
|
53
|
+
'--disable-web-security',
|
|
54
|
+
'--disable-features=IsolateOrigins',
|
|
55
|
+
'--disable-site-isolation-trials',
|
|
56
|
+
'--fast',
|
|
57
|
+
'--fast-start',
|
|
58
|
+
'--disk-cache-size=104857600', // 100MB cache
|
|
59
|
+
];
|
|
19
60
|
// Check environment variable for testing override
|
|
20
61
|
const disableContentPriority = process.env.DISABLE_CONTENT_PRIORITY === 'true' || process.env.NODE_ENV === 'test';
|
|
21
62
|
let contentPriorityConfig = {
|
|
@@ -400,20 +441,21 @@ export async function initializeBrowser(options) {
|
|
|
400
441
|
const customConfig = options?.customConfig ?? {};
|
|
401
442
|
const platform = process.platform;
|
|
402
443
|
const getOptimalChromeFlags = (isWindows, isRetry = false) => {
|
|
403
|
-
// 2025
|
|
444
|
+
// 2025 OPTIMIZED: Performance-focused flags with minimal overhead
|
|
404
445
|
const baseFlags = [
|
|
405
446
|
'--no-first-run',
|
|
406
447
|
'--no-default-browser-check',
|
|
407
448
|
'--disable-default-apps',
|
|
408
449
|
'--disable-blink-features=AutomationControlled', // Essential for stealth
|
|
409
|
-
'--start-maximized', // UI convenience
|
|
450
|
+
'--start-maximized', // UI convenience
|
|
451
|
+
...PERFORMANCE_FLAGS, // Add all performance optimizations
|
|
410
452
|
];
|
|
411
453
|
// Add platform-specific flags only when absolutely necessary
|
|
412
454
|
const platformFlags = [];
|
|
413
455
|
if (isWindows) {
|
|
414
|
-
//
|
|
415
|
-
|
|
416
|
-
|
|
456
|
+
// Windows-specific optimizations
|
|
457
|
+
platformFlags.push('--disable-gpu-sandbox', // Better GPU performance on Windows
|
|
458
|
+
'--disable-software-rasterizer');
|
|
417
459
|
}
|
|
418
460
|
// Emergency fallback flags for retry attempts only
|
|
419
461
|
if (isRetry) {
|
|
@@ -480,15 +522,18 @@ export async function initializeBrowser(options) {
|
|
|
480
522
|
};
|
|
481
523
|
return { strategyName, strategy };
|
|
482
524
|
};
|
|
483
|
-
// Primary strategy: User-defined configuration with
|
|
525
|
+
// Primary strategy: User-defined configuration with PERFORMANCE OPTIMIZATION
|
|
484
526
|
const primaryStrategy = {
|
|
485
|
-
strategyName: '
|
|
527
|
+
strategyName: 'Optimized Performance Configuration',
|
|
486
528
|
strategy: {
|
|
487
529
|
executablePath: detectedChromePath,
|
|
488
530
|
headless: options?.headless ?? false,
|
|
489
531
|
turnstile: true,
|
|
490
532
|
args: [
|
|
491
|
-
"--start-maximized"
|
|
533
|
+
"--start-maximized",
|
|
534
|
+
"--homepage=about:blank", // Start with blank for faster init
|
|
535
|
+
"--no-startup-window", // Don't show startup window
|
|
536
|
+
...PERFORMANCE_FLAGS, // All performance optimizations
|
|
492
537
|
],
|
|
493
538
|
disableXvfb: true,
|
|
494
539
|
// CRITICAL: Must be false to allow brave-real-browser to process DEFAULT_FLAGS
|
|
@@ -496,6 +541,7 @@ export async function initializeBrowser(options) {
|
|
|
496
541
|
customConfig: chromeConfig,
|
|
497
542
|
connectOption: {
|
|
498
543
|
defaultViewport: null,
|
|
544
|
+
timeout: 30000, // Faster timeout for better responsiveness
|
|
499
545
|
},
|
|
500
546
|
}
|
|
501
547
|
};
|
|
@@ -584,6 +630,13 @@ export async function initializeBrowser(options) {
|
|
|
584
630
|
const { browser, page } = result;
|
|
585
631
|
browserInstance = browser;
|
|
586
632
|
pageInstance = page;
|
|
633
|
+
// Immediately navigate to blank page to avoid about:blank visibility
|
|
634
|
+
try {
|
|
635
|
+
await page.goto('about:blank', { waitUntil: 'domcontentloaded', timeout: 5000 });
|
|
636
|
+
}
|
|
637
|
+
catch (navError) {
|
|
638
|
+
console.error('Quick navigation warning:', navError);
|
|
639
|
+
}
|
|
587
640
|
console.error(`✅ Browser initialized successfully using ${strategyName}`);
|
|
588
641
|
updateCircuitBreakerOnSuccess();
|
|
589
642
|
return { browser, page };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTO CAPTCHA DETECTOR & SOLVER
|
|
3
|
+
* Automatically detects and solves CAPTCHAs on any page
|
|
4
|
+
*/
|
|
5
|
+
import { handleSolveTextCaptcha } from './captcha-solver-handlers.js';
|
|
6
|
+
// Common CAPTCHA selectors across different websites
|
|
7
|
+
const COMMON_CAPTCHA_SELECTORS = {
|
|
8
|
+
images: [
|
|
9
|
+
'#captcha_image',
|
|
10
|
+
'#captcha-image',
|
|
11
|
+
'#captchaImage',
|
|
12
|
+
'.captcha-image',
|
|
13
|
+
'.captcha_image',
|
|
14
|
+
'img[alt*="captcha" i]',
|
|
15
|
+
'img[alt*="CAPTCHA" i]',
|
|
16
|
+
'img[src*="captcha" i]',
|
|
17
|
+
'img[id*="captcha" i]',
|
|
18
|
+
'img[class*="captcha" i]',
|
|
19
|
+
'#securityImage',
|
|
20
|
+
'#verify-image',
|
|
21
|
+
'.security-image',
|
|
22
|
+
],
|
|
23
|
+
inputs: [
|
|
24
|
+
'#fcaptcha_code',
|
|
25
|
+
'#captcha_code',
|
|
26
|
+
'#captchaCode',
|
|
27
|
+
'#captcha-code',
|
|
28
|
+
'#captcha',
|
|
29
|
+
'input[name*="captcha" i]',
|
|
30
|
+
'input[placeholder*="captcha" i]',
|
|
31
|
+
'input[id*="captcha" i]',
|
|
32
|
+
'input[class*="captcha" i]',
|
|
33
|
+
'#securityCode',
|
|
34
|
+
'#verifyCode',
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Detect CAPTCHA on current page
|
|
39
|
+
*/
|
|
40
|
+
export async function detectCaptcha(page) {
|
|
41
|
+
if (!page) {
|
|
42
|
+
return { found: false, confidence: 0 };
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
// Check for CAPTCHA elements on page
|
|
46
|
+
const detection = await page.evaluate((selectors) => {
|
|
47
|
+
let foundImage = null;
|
|
48
|
+
let foundInput = null;
|
|
49
|
+
// Find CAPTCHA image
|
|
50
|
+
for (const selector of selectors.images) {
|
|
51
|
+
try {
|
|
52
|
+
const element = document.querySelector(selector);
|
|
53
|
+
if (element && element.offsetParent !== null) {
|
|
54
|
+
// Element exists and is visible
|
|
55
|
+
foundImage = selector;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Find CAPTCHA input
|
|
64
|
+
for (const selector of selectors.inputs) {
|
|
65
|
+
try {
|
|
66
|
+
const element = document.querySelector(selector);
|
|
67
|
+
if (element && element.offsetParent !== null) {
|
|
68
|
+
foundInput = selector;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
imageSelector: foundImage,
|
|
78
|
+
inputSelector: foundInput,
|
|
79
|
+
found: !!(foundImage && foundInput)
|
|
80
|
+
};
|
|
81
|
+
}, COMMON_CAPTCHA_SELECTORS);
|
|
82
|
+
if (detection.found) {
|
|
83
|
+
console.log('✅ CAPTCHA detected on page!');
|
|
84
|
+
console.log(` Image: ${detection.imageSelector}`);
|
|
85
|
+
console.log(` Input: ${detection.inputSelector}`);
|
|
86
|
+
return {
|
|
87
|
+
found: true,
|
|
88
|
+
imageSelector: detection.imageSelector,
|
|
89
|
+
inputSelector: detection.inputSelector,
|
|
90
|
+
confidence: 100
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return { found: false, confidence: 0 };
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.error('CAPTCHA detection error:', error);
|
|
97
|
+
return { found: false, confidence: 0 };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* AUTO-SOLVE: Detect and solve CAPTCHA automatically
|
|
102
|
+
*/
|
|
103
|
+
export async function autoSolveCaptcha(page, config) {
|
|
104
|
+
try {
|
|
105
|
+
console.log('🔍 Auto-detecting CAPTCHA on page...');
|
|
106
|
+
const detection = await detectCaptcha(page);
|
|
107
|
+
if (!detection.found) {
|
|
108
|
+
console.log('ℹ️ No CAPTCHA detected on this page');
|
|
109
|
+
return { solved: false };
|
|
110
|
+
}
|
|
111
|
+
console.log('🎯 CAPTCHA found! Auto-solving...');
|
|
112
|
+
// Automatically solve the detected CAPTCHA
|
|
113
|
+
const result = await handleSolveTextCaptcha(page, detection.imageSelector, config || {
|
|
114
|
+
preprocessImage: true,
|
|
115
|
+
maxAttempts: 5,
|
|
116
|
+
minConfidence: 90
|
|
117
|
+
});
|
|
118
|
+
if (result.success) {
|
|
119
|
+
// Auto-fill the input
|
|
120
|
+
await page.waitForSelector(detection.inputSelector, { timeout: 5000 });
|
|
121
|
+
await page.click(detection.inputSelector);
|
|
122
|
+
await page.type(detection.inputSelector, result.text, { delay: 100 });
|
|
123
|
+
console.log(`✅ CAPTCHA auto-solved and filled: "${result.text}"`);
|
|
124
|
+
console.log(`🎉 Confidence: ${result.confidence.toFixed(2)}%`);
|
|
125
|
+
return {
|
|
126
|
+
solved: true,
|
|
127
|
+
text: result.text,
|
|
128
|
+
confidence: result.confidence
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
solved: false,
|
|
133
|
+
error: result.error || 'Failed to solve CAPTCHA'
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.error('Auto-solve CAPTCHA error:', error);
|
|
138
|
+
return {
|
|
139
|
+
solved: false,
|
|
140
|
+
error: error.message || 'Auto-solve failed'
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Check if page has CAPTCHA (quick check)
|
|
146
|
+
*/
|
|
147
|
+
export async function hasCaptcha(page) {
|
|
148
|
+
const result = await detectCaptcha(page);
|
|
149
|
+
return result.found;
|
|
150
|
+
}
|
|
@@ -5,20 +5,35 @@ import { validateWorkflow, recordExecution, workflowValidator } from '../workflo
|
|
|
5
5
|
export async function handleBrowserInit(args) {
|
|
6
6
|
return await withWorkflowValidation('browser_init', args, async () => {
|
|
7
7
|
return await withErrorHandling(async () => {
|
|
8
|
-
await initializeBrowser(args);
|
|
8
|
+
const { browser, page } = await initializeBrowser(args);
|
|
9
9
|
// Update content priority configuration if provided
|
|
10
10
|
if (args.contentPriority) {
|
|
11
11
|
updateContentPriorityConfig(args.contentPriority);
|
|
12
12
|
}
|
|
13
|
+
// SKIP ABOUT:BLANK - If initialUrl provided, navigate immediately
|
|
14
|
+
if (args.initialUrl && page) {
|
|
15
|
+
try {
|
|
16
|
+
await page.goto(args.initialUrl, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
17
|
+
console.error(`✅ Navigated directly to: ${args.initialUrl}`);
|
|
18
|
+
}
|
|
19
|
+
catch (navError) {
|
|
20
|
+
console.error('⚠️ Initial navigation failed:', navError);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
13
23
|
const config = getContentPriorityConfig();
|
|
14
24
|
const configMessage = config.prioritizeContent
|
|
15
25
|
? '\n\n💡 Content Priority Mode: get_content is prioritized for better reliability. Use get_content for page analysis instead of screenshots.'
|
|
16
26
|
: '';
|
|
17
|
-
const workflowMessage =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
const workflowMessage = args.initialUrl
|
|
28
|
+
? `\n\n✅ Browser opened and navigated to: ${args.initialUrl}\n\n🔄 Workflow Status: Page loaded directly\n` +
|
|
29
|
+
' • Next step: Use get_content to analyze page content\n' +
|
|
30
|
+
' • Then: Use find_selector and interaction tools\n\n' +
|
|
31
|
+
'✅ Workflow validation is now active'
|
|
32
|
+
: '\n\n🔄 Workflow Status: Browser initialized\n' +
|
|
33
|
+
' • Next step: Use navigate to load a web page\n' +
|
|
34
|
+
' • Then: Use get_content to analyze page content\n' +
|
|
35
|
+
' • Finally: Use find_selector and interaction tools\n\n' +
|
|
36
|
+
'✅ Workflow validation is now active - prevents blind selector guessing';
|
|
22
37
|
return {
|
|
23
38
|
content: [
|
|
24
39
|
{
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { createWorker, PSM } from 'tesseract.js';
|
|
2
|
+
import sharp from 'sharp';
|
|
3
|
+
/**
|
|
4
|
+
* Advanced preprocessing strategies for maximum accuracy
|
|
5
|
+
*/
|
|
6
|
+
async function preprocessImage(imageBuffer, strategy = 'standard') {
|
|
7
|
+
try {
|
|
8
|
+
const img = sharp(imageBuffer);
|
|
9
|
+
const metadata = await sharp(imageBuffer).metadata();
|
|
10
|
+
switch (strategy) {
|
|
11
|
+
case 'ultra':
|
|
12
|
+
// Ultra high quality - Maximum accuracy
|
|
13
|
+
return await img
|
|
14
|
+
.resize({
|
|
15
|
+
width: Math.max(1000, (metadata.width || 200) * 4),
|
|
16
|
+
kernel: 'lanczos3' // Best quality scaling
|
|
17
|
+
})
|
|
18
|
+
.greyscale()
|
|
19
|
+
.normalize({ lower: 1, upper: 99 }) // Aggressive normalization
|
|
20
|
+
.linear(2.0, -(128 * 1.0)) // Very high contrast
|
|
21
|
+
.median(2) // Reduce noise
|
|
22
|
+
.convolve({
|
|
23
|
+
width: 3,
|
|
24
|
+
height: 3,
|
|
25
|
+
kernel: [-1, -1, -1, -1, 9, -1, -1, -1, -1]
|
|
26
|
+
})
|
|
27
|
+
.threshold(115, { grayscale: false }) // Optimal threshold
|
|
28
|
+
.negate({ alpha: false }) // Invert if needed
|
|
29
|
+
.negate({ alpha: false }) // Double negate for consistency
|
|
30
|
+
.sharpen({ sigma: 3, m1: 2, m2: 3 }) // Maximum sharpening
|
|
31
|
+
.toBuffer();
|
|
32
|
+
case 'aggressive':
|
|
33
|
+
// Aggressive preprocessing
|
|
34
|
+
return await img
|
|
35
|
+
.resize({ width: 800, kernel: 'lanczos2' })
|
|
36
|
+
.greyscale()
|
|
37
|
+
.normalize()
|
|
38
|
+
.linear(1.8, -(128 * 0.8))
|
|
39
|
+
.threshold(120)
|
|
40
|
+
.median(3)
|
|
41
|
+
.sharpen({ sigma: 2.5 })
|
|
42
|
+
.toBuffer();
|
|
43
|
+
case 'adaptive':
|
|
44
|
+
// Adaptive based on image characteristics
|
|
45
|
+
const avgWidth = metadata.width || 200;
|
|
46
|
+
const scaleFactor = avgWidth < 300 ? 4 : 3;
|
|
47
|
+
return await img
|
|
48
|
+
.resize({ width: avgWidth * scaleFactor })
|
|
49
|
+
.greyscale()
|
|
50
|
+
.normalize()
|
|
51
|
+
.linear(1.6, -(128 * 0.6))
|
|
52
|
+
.threshold(125)
|
|
53
|
+
.sharpen({ sigma: 2 })
|
|
54
|
+
.toBuffer();
|
|
55
|
+
case 'light':
|
|
56
|
+
// Light preprocessing
|
|
57
|
+
return await img
|
|
58
|
+
.resize({ width: 500 })
|
|
59
|
+
.greyscale()
|
|
60
|
+
.normalize()
|
|
61
|
+
.sharpen()
|
|
62
|
+
.toBuffer();
|
|
63
|
+
default: // 'standard'
|
|
64
|
+
return await img
|
|
65
|
+
.resize({ width: 600 })
|
|
66
|
+
.greyscale()
|
|
67
|
+
.normalize()
|
|
68
|
+
.threshold(128)
|
|
69
|
+
.sharpen({ sigma: 1.5 })
|
|
70
|
+
.toBuffer();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.warn('Image preprocessing failed, using original:', error);
|
|
75
|
+
return imageBuffer;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Solve text-based CAPTCHA from image selector
|
|
80
|
+
*/
|
|
81
|
+
export async function handleSolveTextCaptcha(page, imageSelector, config = {}) {
|
|
82
|
+
try {
|
|
83
|
+
if (!page) {
|
|
84
|
+
throw new Error('Browser not initialized. Please call browser_init first.');
|
|
85
|
+
}
|
|
86
|
+
const { preprocessImage: shouldPreprocess = true, language = 'eng', whitelist = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', maxAttempts = 5, // Use all 5 strategies for maximum accuracy
|
|
87
|
+
minConfidence = 90 // High confidence threshold
|
|
88
|
+
} = config;
|
|
89
|
+
// Check if image element exists
|
|
90
|
+
const imageExists = await page.evaluate((selector) => {
|
|
91
|
+
const element = document.querySelector(selector);
|
|
92
|
+
return element !== null;
|
|
93
|
+
}, imageSelector);
|
|
94
|
+
if (!imageExists) {
|
|
95
|
+
throw new Error(`Image element not found with selector: ${imageSelector}`);
|
|
96
|
+
}
|
|
97
|
+
// Get image as base64
|
|
98
|
+
const imageData = await page.evaluate(async (selector) => {
|
|
99
|
+
const img = document.querySelector(selector);
|
|
100
|
+
if (!img)
|
|
101
|
+
return null;
|
|
102
|
+
// Create canvas and draw image
|
|
103
|
+
const canvas = document.createElement('canvas');
|
|
104
|
+
canvas.width = img.naturalWidth || img.width;
|
|
105
|
+
canvas.height = img.naturalHeight || img.height;
|
|
106
|
+
const ctx = canvas.getContext('2d');
|
|
107
|
+
if (!ctx)
|
|
108
|
+
return null;
|
|
109
|
+
ctx.drawImage(img, 0, 0);
|
|
110
|
+
// Get base64 data
|
|
111
|
+
return canvas.toDataURL('image/png');
|
|
112
|
+
}, imageSelector);
|
|
113
|
+
if (!imageData) {
|
|
114
|
+
throw new Error('Failed to extract image data from element');
|
|
115
|
+
}
|
|
116
|
+
// Convert base64 to buffer
|
|
117
|
+
const base64Data = imageData.replace(/^data:image\/\w+;base64,/, '');
|
|
118
|
+
const originalBuffer = Buffer.from(base64Data, 'base64');
|
|
119
|
+
// Try multiple preprocessing strategies for best result
|
|
120
|
+
const allStrategies = shouldPreprocess
|
|
121
|
+
? ['ultra', 'adaptive', 'aggressive', 'standard', 'light']
|
|
122
|
+
: [null];
|
|
123
|
+
const strategies = allStrategies.slice(0, Math.min(maxAttempts, 5));
|
|
124
|
+
let bestResult = null;
|
|
125
|
+
const results = [];
|
|
126
|
+
console.log(`🔍 Attempting OCR with ${strategies.length} strategy/strategies (minConfidence: ${minConfidence}%)...`);
|
|
127
|
+
console.log(`🎯 Target: 100% accuracy mode enabled`);
|
|
128
|
+
for (const strategy of strategies) {
|
|
129
|
+
try {
|
|
130
|
+
let imageBuffer = originalBuffer;
|
|
131
|
+
// Apply preprocessing strategy
|
|
132
|
+
if (strategy) {
|
|
133
|
+
imageBuffer = await preprocessImage(originalBuffer, strategy);
|
|
134
|
+
console.log(`Trying OCR with ${strategy} preprocessing...`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
console.log('Trying OCR without preprocessing...');
|
|
138
|
+
}
|
|
139
|
+
// Initialize Tesseract worker
|
|
140
|
+
const worker = await createWorker(language);
|
|
141
|
+
// Configure Tesseract with optimal settings
|
|
142
|
+
await worker.setParameters({
|
|
143
|
+
tessedit_char_whitelist: whitelist,
|
|
144
|
+
tessedit_pageseg_mode: PSM.SINGLE_LINE, // Single text line
|
|
145
|
+
});
|
|
146
|
+
// Perform OCR
|
|
147
|
+
const { data } = await worker.recognize(imageBuffer);
|
|
148
|
+
await worker.terminate();
|
|
149
|
+
// Clean up the result
|
|
150
|
+
const cleanText = data.text
|
|
151
|
+
.replace(/\s+/g, '') // Remove all whitespace
|
|
152
|
+
.replace(/[^0-9A-Za-z]/g, '') // Remove non-alphanumeric
|
|
153
|
+
.toUpperCase() // Convert to uppercase
|
|
154
|
+
.trim();
|
|
155
|
+
console.log(`📊 OCR Result (${strategy || 'no preprocessing'}):`, {
|
|
156
|
+
raw: data.text,
|
|
157
|
+
cleaned: cleanText,
|
|
158
|
+
confidence: data.confidence.toFixed(2) + '%'
|
|
159
|
+
});
|
|
160
|
+
// Store result for consensus analysis
|
|
161
|
+
if (cleanText.length > 0) {
|
|
162
|
+
results.push({
|
|
163
|
+
strategy: strategy || 'none',
|
|
164
|
+
text: cleanText,
|
|
165
|
+
confidence: data.confidence
|
|
166
|
+
});
|
|
167
|
+
// Keep the result with highest confidence
|
|
168
|
+
if (!bestResult || data.confidence > bestResult.confidence) {
|
|
169
|
+
bestResult = {
|
|
170
|
+
text: cleanText,
|
|
171
|
+
confidence: data.confidence
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// If we got perfect or near-perfect confidence, stop trying
|
|
176
|
+
if (data.confidence >= 95) {
|
|
177
|
+
console.log(`✅ Excellent confidence achieved (${data.confidence.toFixed(2)}%), stopping attempts`);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (strategyError) {
|
|
182
|
+
console.warn(`OCR attempt with strategy ${strategy} failed:`, strategyError);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (!bestResult || !bestResult.text) {
|
|
187
|
+
throw new Error('OCR failed to recognize any text from CAPTCHA image');
|
|
188
|
+
}
|
|
189
|
+
// 🎯 CONSENSUS VOTING: If multiple results agree, boost confidence to 100%
|
|
190
|
+
console.log(`\n🤝 Analyzing ${results.length} results for consensus...`);
|
|
191
|
+
const textFrequency = new Map();
|
|
192
|
+
results.forEach(r => {
|
|
193
|
+
textFrequency.set(r.text, (textFrequency.get(r.text) || 0) + 1);
|
|
194
|
+
});
|
|
195
|
+
// Find most common result
|
|
196
|
+
let consensusText = bestResult.text;
|
|
197
|
+
let consensusCount = 1;
|
|
198
|
+
let consensusConfidence = bestResult.confidence;
|
|
199
|
+
for (const [text, count] of textFrequency.entries()) {
|
|
200
|
+
if (count > consensusCount) {
|
|
201
|
+
consensusText = text;
|
|
202
|
+
consensusCount = count;
|
|
203
|
+
// Get average confidence for this text
|
|
204
|
+
consensusConfidence = results
|
|
205
|
+
.filter(r => r.text === text)
|
|
206
|
+
.reduce((sum, r) => sum + r.confidence, 0) / count;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// If multiple strategies agree on same text, boost confidence
|
|
210
|
+
if (consensusCount >= 2 && results.length >= 2) {
|
|
211
|
+
const agreementPercent = (consensusCount / results.length) * 100;
|
|
212
|
+
console.log(`✅ CONSENSUS ACHIEVED! ${consensusCount}/${results.length} strategies agree on "${consensusText}"`);
|
|
213
|
+
console.log(`📊 Agreement: ${agreementPercent.toFixed(0)}%`);
|
|
214
|
+
// Boost confidence based on consensus
|
|
215
|
+
const boostedConfidence = Math.min(100, consensusConfidence + (agreementPercent * 0.3));
|
|
216
|
+
console.log(`🚀 Confidence boosted: ${consensusConfidence.toFixed(2)}% → ${boostedConfidence.toFixed(2)}%`);
|
|
217
|
+
return {
|
|
218
|
+
success: true,
|
|
219
|
+
text: consensusText,
|
|
220
|
+
confidence: boostedConfidence
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// Check if confidence meets minimum threshold
|
|
224
|
+
if (bestResult.confidence < minConfidence) {
|
|
225
|
+
console.warn(`⚠️ Low confidence warning: ${bestResult.confidence.toFixed(2)}% < ${minConfidence}% threshold`);
|
|
226
|
+
console.log('Best OCR Result (Low Confidence):', bestResult);
|
|
227
|
+
return {
|
|
228
|
+
success: true,
|
|
229
|
+
text: bestResult.text,
|
|
230
|
+
confidence: bestResult.confidence,
|
|
231
|
+
error: `⚠️ Low confidence: ${bestResult.confidence.toFixed(2)}% (threshold: ${minConfidence}%). Result may be inaccurate.`
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
console.log('✅ Best OCR Result (High Confidence):', {
|
|
235
|
+
text: bestResult.text,
|
|
236
|
+
confidence: bestResult.confidence.toFixed(2) + '%'
|
|
237
|
+
});
|
|
238
|
+
return {
|
|
239
|
+
success: true,
|
|
240
|
+
text: bestResult.text,
|
|
241
|
+
confidence: bestResult.confidence
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
console.error('CAPTCHA solving error:', error);
|
|
246
|
+
return {
|
|
247
|
+
success: false,
|
|
248
|
+
text: '',
|
|
249
|
+
confidence: 0,
|
|
250
|
+
error: error.message || 'Failed to solve CAPTCHA'
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Solve and auto-fill CAPTCHA
|
|
256
|
+
*/
|
|
257
|
+
export async function handleSolveAndFillCaptcha(page, imageSelector, inputSelector, config = {}) {
|
|
258
|
+
try {
|
|
259
|
+
if (!page) {
|
|
260
|
+
throw new Error('Browser not initialized. Please call browser_init first.');
|
|
261
|
+
}
|
|
262
|
+
// Solve CAPTCHA
|
|
263
|
+
const result = await handleSolveTextCaptcha(page, imageSelector, config);
|
|
264
|
+
if (!result.success || !result.text) {
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
// Fill the input field
|
|
268
|
+
await page.waitForSelector(inputSelector, { timeout: 5000 });
|
|
269
|
+
await page.click(inputSelector);
|
|
270
|
+
await page.type(inputSelector, result.text, { delay: 100 });
|
|
271
|
+
console.log(`CAPTCHA text "${result.text}" filled in ${inputSelector}`);
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
console.error('CAPTCHA solve and fill error:', error);
|
|
276
|
+
return {
|
|
277
|
+
success: false,
|
|
278
|
+
text: '',
|
|
279
|
+
confidence: 0,
|
|
280
|
+
error: error.message || 'Failed to solve and fill CAPTCHA'
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -23,9 +23,84 @@ import { handleNavigate, handleWait } from './handlers/navigation-handlers.js';
|
|
|
23
23
|
import { handleClick, handleType, handleSolveCaptcha, handleRandomScroll } from './handlers/interaction-handlers.js';
|
|
24
24
|
import { handleGetContent, handleFindSelector } from './handlers/content-handlers.js';
|
|
25
25
|
import { handleSaveContentAsMarkdown } from './handlers/file-handlers.js';
|
|
26
|
+
import { handleSolveTextCaptcha, handleSolveAndFillCaptcha } from './handlers/captcha-solver-handlers.js';
|
|
27
|
+
import { autoSolveCaptcha } from './handlers/auto-captcha-detector.js';
|
|
28
|
+
import { getPageInstance } from './browser-manager.js';
|
|
26
29
|
console.error('🔍 [DEBUG] All modules loaded successfully');
|
|
27
30
|
console.error(`🔍 [DEBUG] Server info: ${JSON.stringify(SERVER_INFO)}`);
|
|
28
31
|
console.error(`🔍 [DEBUG] Available tools: ${TOOLS.length} tools loaded`);
|
|
32
|
+
// Wrapper function for solve_text_captcha
|
|
33
|
+
async function handleSolveTextCaptchaWrapper(args) {
|
|
34
|
+
return await withErrorHandling(async () => {
|
|
35
|
+
const page = getPageInstance();
|
|
36
|
+
if (!page) {
|
|
37
|
+
throw new Error('Browser not initialized. Please call browser_init first.');
|
|
38
|
+
}
|
|
39
|
+
const { imageSelector, inputSelector, config } = args;
|
|
40
|
+
if (!imageSelector) {
|
|
41
|
+
throw new Error('imageSelector is required');
|
|
42
|
+
}
|
|
43
|
+
let result;
|
|
44
|
+
if (inputSelector) {
|
|
45
|
+
// Solve and auto-fill
|
|
46
|
+
result = await handleSolveAndFillCaptcha(page, imageSelector, inputSelector, config || {});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Just solve
|
|
50
|
+
result = await handleSolveTextCaptcha(page, imageSelector, config || {});
|
|
51
|
+
}
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
throw new Error(result.error || 'Failed to solve CAPTCHA');
|
|
54
|
+
}
|
|
55
|
+
const confidenceEmoji = result.confidence >= 100 ? '🎉' : result.confidence >= 95 ? '🚀' : result.confidence >= 90 ? '✨' : '📊';
|
|
56
|
+
const confidenceText = result.confidence >= 100 ? '100% (PERFECT!)' : `${result.confidence.toFixed(2)}%`;
|
|
57
|
+
const message = inputSelector
|
|
58
|
+
? `✅ CAPTCHA solved and filled successfully!\n\n📝 Recognized text: "${result.text}"\n${confidenceEmoji} Confidence: ${confidenceText}\n🎯 Filled in: ${inputSelector}`
|
|
59
|
+
: `✅ CAPTCHA solved successfully!\n\n📝 Recognized text: "${result.text}"\n${confidenceEmoji} Confidence: ${confidenceText}\n\n💡 Tip: Provide inputSelector parameter to auto-fill the CAPTCHA field`;
|
|
60
|
+
return {
|
|
61
|
+
content: [
|
|
62
|
+
{
|
|
63
|
+
type: 'text',
|
|
64
|
+
text: message,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
}, 'Failed to solve text CAPTCHA');
|
|
69
|
+
}
|
|
70
|
+
// Wrapper function for auto_solve_captcha
|
|
71
|
+
async function handleAutoSolveCaptcha(args) {
|
|
72
|
+
return await withErrorHandling(async () => {
|
|
73
|
+
const page = getPageInstance();
|
|
74
|
+
if (!page) {
|
|
75
|
+
throw new Error('Browser not initialized. Please call browser_init first.');
|
|
76
|
+
}
|
|
77
|
+
const result = await autoSolveCaptcha(page, args.config);
|
|
78
|
+
if (!result.solved) {
|
|
79
|
+
if (result.error) {
|
|
80
|
+
throw new Error(result.error);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: 'text',
|
|
86
|
+
text: 'ℹ️ No CAPTCHA detected on the current page.\n\n💡 This tool automatically scans for common CAPTCHA patterns. If you\'re sure there\'s a CAPTCHA, it might use uncommon selectors. Try using the solve_text_captcha tool with specific selectors instead.',
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const confidenceEmoji = result.confidence >= 100 ? '🎉' : result.confidence >= 95 ? '🚀' : result.confidence >= 90 ? '✨' : '📊';
|
|
92
|
+
const confidenceText = result.confidence >= 100 ? '100% (PERFECT!)' : `${result.confidence.toFixed(2)}%`;
|
|
93
|
+
const message = `✅ CAPTCHA automatically detected and solved!\n\n📝 Recognized text: "${result.text}"\n${confidenceEmoji} Confidence: ${confidenceText}\n✨ Auto-filled successfully\n\n🎯 This was done automatically without needing to specify selectors!`;
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: 'text',
|
|
98
|
+
text: message,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}, 'Failed to auto-solve CAPTCHA');
|
|
103
|
+
}
|
|
29
104
|
// Initialize MCP server
|
|
30
105
|
console.error('🔍 [DEBUG] Creating MCP server instance...');
|
|
31
106
|
const server = new Server(SERVER_INFO, { capabilities: CAPABILITIES });
|
|
@@ -93,6 +168,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
93
168
|
return await handleBrowserClose();
|
|
94
169
|
case TOOL_NAMES.SOLVE_CAPTCHA:
|
|
95
170
|
return await handleSolveCaptcha(args);
|
|
171
|
+
case TOOL_NAMES.SOLVE_TEXT_CAPTCHA:
|
|
172
|
+
return await handleSolveTextCaptchaWrapper(args || {});
|
|
173
|
+
case TOOL_NAMES.AUTO_SOLVE_CAPTCHA:
|
|
174
|
+
return await handleAutoSolveCaptcha(args);
|
|
96
175
|
case TOOL_NAMES.RANDOM_SCROLL:
|
|
97
176
|
return await handleRandomScroll();
|
|
98
177
|
case TOOL_NAMES.FIND_SELECTOR:
|
package/dist/tool-definitions.js
CHANGED
|
@@ -88,6 +88,10 @@ export const TOOLS = [
|
|
|
88
88
|
},
|
|
89
89
|
additionalProperties: false,
|
|
90
90
|
},
|
|
91
|
+
initialUrl: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
description: 'Optional: URL to navigate immediately after browser opens (skips about:blank)',
|
|
94
|
+
},
|
|
91
95
|
},
|
|
92
96
|
},
|
|
93
97
|
},
|
|
@@ -219,6 +223,95 @@ export const TOOLS = [
|
|
|
219
223
|
required: ['type'],
|
|
220
224
|
},
|
|
221
225
|
},
|
|
226
|
+
{
|
|
227
|
+
name: 'solve_text_captcha',
|
|
228
|
+
description: 'Solve text-based image CAPTCHA using OCR (Tesseract.js) - Automatically extracts and recognizes text from CAPTCHA images',
|
|
229
|
+
inputSchema: {
|
|
230
|
+
type: 'object',
|
|
231
|
+
properties: {
|
|
232
|
+
imageSelector: {
|
|
233
|
+
type: 'string',
|
|
234
|
+
description: 'CSS selector of the CAPTCHA image element (e.g., "#captcha_image", ".captcha-img")',
|
|
235
|
+
},
|
|
236
|
+
inputSelector: {
|
|
237
|
+
type: 'string',
|
|
238
|
+
description: 'Optional: CSS selector of the input field to auto-fill the solved CAPTCHA text',
|
|
239
|
+
},
|
|
240
|
+
config: {
|
|
241
|
+
type: 'object',
|
|
242
|
+
description: 'Optional OCR configuration for better accuracy',
|
|
243
|
+
properties: {
|
|
244
|
+
preprocessImage: {
|
|
245
|
+
type: 'boolean',
|
|
246
|
+
description: 'Apply image preprocessing (grayscale, threshold, sharpen) to improve OCR accuracy',
|
|
247
|
+
default: true,
|
|
248
|
+
},
|
|
249
|
+
language: {
|
|
250
|
+
type: 'string',
|
|
251
|
+
description: 'OCR language (e.g., "eng" for English, "hin" for Hindi)',
|
|
252
|
+
default: 'eng',
|
|
253
|
+
},
|
|
254
|
+
whitelist: {
|
|
255
|
+
type: 'string',
|
|
256
|
+
description: 'Characters to recognize (default: alphanumeric)',
|
|
257
|
+
default: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
|
|
258
|
+
},
|
|
259
|
+
maxAttempts: {
|
|
260
|
+
type: 'number',
|
|
261
|
+
description: 'Number of preprocessing strategies to try (1-5). Use 5 for 100% accuracy mode. More attempts = higher accuracy',
|
|
262
|
+
default: 5,
|
|
263
|
+
minimum: 1,
|
|
264
|
+
maximum: 5,
|
|
265
|
+
},
|
|
266
|
+
minConfidence: {
|
|
267
|
+
type: 'number',
|
|
268
|
+
description: 'Minimum confidence threshold (0-100). Use 90+ for high quality results',
|
|
269
|
+
default: 90,
|
|
270
|
+
minimum: 0,
|
|
271
|
+
maximum: 100,
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
additionalProperties: false,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
required: ['imageSelector'],
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'auto_solve_captcha',
|
|
282
|
+
description: 'Automatically detect and solve CAPTCHA on the current page - No selectors needed! Scans for common CAPTCHA patterns and solves them automatically using OCR',
|
|
283
|
+
inputSchema: {
|
|
284
|
+
type: 'object',
|
|
285
|
+
properties: {
|
|
286
|
+
config: {
|
|
287
|
+
type: 'object',
|
|
288
|
+
description: 'Optional OCR configuration',
|
|
289
|
+
properties: {
|
|
290
|
+
preprocessImage: {
|
|
291
|
+
type: 'boolean',
|
|
292
|
+
description: 'Apply image preprocessing for better accuracy',
|
|
293
|
+
default: true,
|
|
294
|
+
},
|
|
295
|
+
maxAttempts: {
|
|
296
|
+
type: 'number',
|
|
297
|
+
description: 'Number of preprocessing strategies to try (1-5)',
|
|
298
|
+
default: 5,
|
|
299
|
+
minimum: 1,
|
|
300
|
+
maximum: 5,
|
|
301
|
+
},
|
|
302
|
+
minConfidence: {
|
|
303
|
+
type: 'number',
|
|
304
|
+
description: 'Minimum confidence threshold (0-100)',
|
|
305
|
+
default: 90,
|
|
306
|
+
minimum: 0,
|
|
307
|
+
maximum: 100,
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
additionalProperties: false,
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
},
|
|
222
315
|
{
|
|
223
316
|
name: 'random_scroll',
|
|
224
317
|
description: 'Perform random scrolling with natural timing',
|
|
@@ -314,6 +407,8 @@ export const TOOL_NAMES = {
|
|
|
314
407
|
WAIT: 'wait',
|
|
315
408
|
BROWSER_CLOSE: 'browser_close',
|
|
316
409
|
SOLVE_CAPTCHA: 'solve_captcha',
|
|
410
|
+
SOLVE_TEXT_CAPTCHA: 'solve_text_captcha',
|
|
411
|
+
AUTO_SOLVE_CAPTCHA: 'auto_solve_captcha',
|
|
317
412
|
RANDOM_SCROLL: 'random_scroll',
|
|
318
413
|
FIND_SELECTOR: 'find_selector',
|
|
319
414
|
SAVE_CONTENT_AS_MARKDOWN: 'save_content_as_markdown',
|
|
@@ -322,6 +417,6 @@ export const TOOL_NAMES = {
|
|
|
322
417
|
export const TOOL_CATEGORIES = {
|
|
323
418
|
BROWSER_MANAGEMENT: [TOOL_NAMES.BROWSER_INIT, TOOL_NAMES.BROWSER_CLOSE],
|
|
324
419
|
NAVIGATION: [TOOL_NAMES.NAVIGATE, TOOL_NAMES.WAIT],
|
|
325
|
-
INTERACTION: [TOOL_NAMES.CLICK, TOOL_NAMES.TYPE, TOOL_NAMES.SOLVE_CAPTCHA, TOOL_NAMES.RANDOM_SCROLL],
|
|
420
|
+
INTERACTION: [TOOL_NAMES.CLICK, TOOL_NAMES.TYPE, TOOL_NAMES.SOLVE_CAPTCHA, TOOL_NAMES.SOLVE_TEXT_CAPTCHA, TOOL_NAMES.AUTO_SOLVE_CAPTCHA, TOOL_NAMES.RANDOM_SCROLL],
|
|
326
421
|
CONTENT: [TOOL_NAMES.GET_CONTENT, TOOL_NAMES.FIND_SELECTOR, TOOL_NAMES.SAVE_CONTENT_AS_MARKDOWN],
|
|
327
422
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "MCP server for brave-real-browser",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
"@modelcontextprotocol/sdk": "^1.19.1",
|
|
35
35
|
"@types/turndown": "^5.0.5",
|
|
36
36
|
"brave-real-browser": "^1.5.102",
|
|
37
|
+
"sharp": "^0.33.5",
|
|
38
|
+
"tesseract.js": "^5.1.1",
|
|
37
39
|
"turndown": "^7.2.1"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|