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 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**: "Handle any captchas that appear"
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
 
@@ -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 best practices: Minimal, secure, performance-focused flags
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, minimal performance impact
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
- // Only add Windows-specific flags if there are compatibility issues
415
- // Note: --no-sandbox removed for security (not needed for desktop automation)
416
- // Note: --disable-gpu removed unless headless mode has issues
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 modified flags
525
+ // Primary strategy: User-defined configuration with PERFORMANCE OPTIMIZATION
484
526
  const primaryStrategy = {
485
- strategyName: 'User-Defined Configuration',
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 = '\n\n🔄 Workflow Status: Browser initialized\n' +
18
- ' • Next step: Use navigate to load a web page\n' +
19
- ' • Then: Use get_content to analyze page content\n' +
20
- ' • Finally: Use find_selector and interaction tools\n\n' +
21
- '✅ Workflow validation is now active - prevents blind selector guessing';
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:
@@ -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.3",
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": {