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

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.
@@ -49,23 +49,29 @@ export async function handleBreadcrumbNavigator(page, args) {
49
49
  return { success: true, breadcrumbs: breadcrumbTexts };
50
50
  }
51
51
  /**
52
- * Trace standard URL redirects
52
+ * Trace standard URL redirects - Uses response events to avoid interception crashes
53
53
  */
54
54
  export async function handleUrlRedirectTracer(page, args) {
55
55
  const maxRedirects = args.maxRedirects || 10;
56
- const chain = [];
57
- const redirectHandler = (request) => {
56
+ const chain = [args.url];
57
+ const TIMEOUT_MS = 30000; // 30 second timeout
58
+ // Use response events instead of request interception to avoid crashes
59
+ const responseHandler = (response) => {
58
60
  try {
59
- // Check if request is already handled
60
- if (request.isInterceptResolutionHandled && request.isInterceptResolutionHandled()) {
61
- return;
62
- }
63
- if (request.isNavigationRequest()) {
64
- chain.push(request.url());
61
+ const status = response.status();
62
+ const url = response.url();
63
+ // Track redirects (3xx status codes)
64
+ if (status >= 300 && status < 400 && chain.length < maxRedirects) {
65
+ const location = response.headers()['location'];
66
+ if (location) {
67
+ chain.push(location);
68
+ }
65
69
  }
66
- // Only continue if not already handled
67
- if (!request.isInterceptResolutionHandled || !request.isInterceptResolutionHandled()) {
68
- request.continue().catch(() => { });
70
+ // Also track successful navigations
71
+ if (status >= 200 && status < 300) {
72
+ if (!chain.includes(url)) {
73
+ chain.push(url);
74
+ }
69
75
  }
70
76
  }
71
77
  catch (e) {
@@ -73,29 +79,56 @@ export async function handleUrlRedirectTracer(page, args) {
73
79
  }
74
80
  };
75
81
  try {
82
+ page.on('response', responseHandler);
83
+ // Navigate with timeout protection
84
+ const navigationPromise = page.goto(args.url, {
85
+ waitUntil: 'networkidle2',
86
+ timeout: TIMEOUT_MS
87
+ }).catch((err) => {
88
+ // Handle navigation errors gracefully
89
+ return { error: err.message };
90
+ });
91
+ const result = await Promise.race([
92
+ navigationPromise,
93
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Navigation timeout')), TIMEOUT_MS))
94
+ ]).catch((err) => ({ error: err.message }));
95
+ // Get final URL safely
96
+ let finalUrl = args.url;
76
97
  try {
77
- await page.setRequestInterception(true);
98
+ finalUrl = page.url() || args.url;
99
+ if (!chain.includes(finalUrl)) {
100
+ chain.push(finalUrl);
101
+ }
78
102
  }
79
103
  catch (e) {
80
- // Interception might already be enabled
104
+ // Use original URL if page.url() fails
81
105
  }
82
- page.on('request', redirectHandler);
83
- await page.goto(args.url, { waitUntil: 'networkidle2' });
84
- chain.push(page.url());
106
+ const uniqueChain = [...new Set(chain)];
107
+ return {
108
+ chain: uniqueChain,
109
+ finalUrl,
110
+ totalRedirects: Math.max(0, uniqueChain.length - 1),
111
+ error: result?.error
112
+ };
113
+ }
114
+ catch (error) {
115
+ // Return partial results on error instead of crashing
116
+ const uniqueChain = [...new Set(chain)];
117
+ return {
118
+ chain: uniqueChain,
119
+ finalUrl: chain[chain.length - 1] || args.url,
120
+ totalRedirects: Math.max(0, uniqueChain.length - 1),
121
+ error: error instanceof Error ? error.message : String(error)
122
+ };
85
123
  }
86
124
  finally {
87
- page.off('request', redirectHandler);
88
125
  try {
89
- await page.setRequestInterception(false);
126
+ page.off('response', responseHandler);
127
+ }
128
+ catch (e) {
129
+ // Ignore cleanup errors
90
130
  }
91
- catch (e) { }
92
131
  }
93
- const uniqueChain = [...new Set(chain)];
94
- return {
95
- chain: uniqueChain,
96
- finalUrl: page.url(),
97
- totalRedirects: uniqueChain.length - 1,
98
- };
99
132
  }
100
133
  /**
101
134
  * Trace complex/hidden redirects including JavaScript redirects
@@ -462,49 +495,72 @@ export async function handleProgressTracker(_page, args) {
462
495
  }
463
496
  /**
464
497
  * Deep analysis - Logs, Network, DOM, and Screenshot
498
+ * Uses response events instead of request interception to avoid crashes
465
499
  */
466
500
  export async function handleDeepAnalysis(page, args) {
467
501
  const consoleLogs = [];
468
502
  const networkRequests = [];
469
503
  const duration = args.duration || 5000;
470
- // Collect console logs
471
- if (args.includeConsole !== false) {
472
- page.on('console', (msg) => {
504
+ // Console log handler
505
+ const consoleHandler = (msg) => {
506
+ try {
473
507
  consoleLogs.push(`[${msg.type()}] ${msg.text()}`);
474
- });
475
- }
508
+ }
509
+ catch (e) {
510
+ // Ignore
511
+ }
512
+ };
513
+ // Response handler - safer than request interception
514
+ const responseHandler = (response) => {
515
+ try {
516
+ networkRequests.push({
517
+ url: response.url(),
518
+ status: response.status(),
519
+ resourceType: response.request()?.resourceType?.() || 'unknown',
520
+ });
521
+ }
522
+ catch (e) {
523
+ // Ignore
524
+ }
525
+ };
476
526
  try {
477
- // Collect network
527
+ // Collect console logs
528
+ if (args.includeConsole !== false) {
529
+ page.on('console', consoleHandler);
530
+ }
531
+ // Collect network using response events (safer than request interception)
478
532
  if (args.includeNetwork !== false) {
479
- await page.setRequestInterception(true);
480
- page.on('request', (req) => {
481
- networkRequests.push({
482
- url: req.url(),
483
- method: req.method(),
484
- resourceType: req.resourceType(),
485
- });
486
- req.continue();
487
- });
533
+ page.on('response', responseHandler);
488
534
  }
489
535
  // Wait for specified duration
490
536
  await new Promise((r) => setTimeout(r, duration));
491
537
  // Get DOM stats
492
538
  let domStats = {};
493
539
  if (args.includeDom !== false) {
494
- domStats = await page.evaluate(() => ({
495
- totalElements: document.querySelectorAll('*').length,
496
- images: document.querySelectorAll('img').length,
497
- links: document.querySelectorAll('a').length,
498
- forms: document.querySelectorAll('form').length,
499
- scripts: document.querySelectorAll('script').length,
500
- iframes: document.querySelectorAll('iframe').length,
501
- }));
540
+ try {
541
+ domStats = await page.evaluate(() => ({
542
+ totalElements: document.querySelectorAll('*').length,
543
+ images: document.querySelectorAll('img').length,
544
+ links: document.querySelectorAll('a').length,
545
+ forms: document.querySelectorAll('form').length,
546
+ scripts: document.querySelectorAll('script').length,
547
+ iframes: document.querySelectorAll('iframe').length,
548
+ }));
549
+ }
550
+ catch (e) {
551
+ domStats = { error: 'Failed to get DOM stats' };
552
+ }
502
553
  }
503
554
  // Take screenshot
504
555
  let screenshot;
505
556
  if (args.includeScreenshot) {
506
- const buffer = await page.screenshot({ encoding: 'base64' });
507
- screenshot = buffer;
557
+ try {
558
+ const buffer = await page.screenshot({ encoding: 'base64' });
559
+ screenshot = buffer;
560
+ }
561
+ catch (e) {
562
+ // Screenshot failed, continue without it
563
+ }
508
564
  }
509
565
  return {
510
566
  console: consoleLogs,
@@ -514,87 +570,78 @@ export async function handleDeepAnalysis(page, args) {
514
570
  };
515
571
  }
516
572
  finally {
517
- if (args.includeNetwork !== false) {
518
- try {
519
- await page.setRequestInterception(false);
573
+ // Clean up event listeners
574
+ try {
575
+ if (args.includeConsole !== false) {
576
+ page.off('console', consoleHandler);
520
577
  }
521
- catch (e) {
522
- // Ignore
578
+ if (args.includeNetwork !== false) {
579
+ page.off('response', responseHandler);
523
580
  }
524
581
  }
582
+ catch (e) {
583
+ // Ignore cleanup errors
584
+ }
525
585
  }
526
586
  }
527
587
  /**
528
- * Record full network traffic
588
+ * Record full network traffic - Uses response events to avoid crashes
529
589
  */
530
590
  export async function handleNetworkRecorder(page, args) {
531
591
  const requests = [];
532
592
  const duration = args.duration || 10000;
533
593
  let totalSize = 0;
534
- const requestHandler = (request) => {
594
+ // Response handler - safer than request interception
595
+ const responseHandler = (response) => {
535
596
  try {
536
- // Check if request is already handled to prevent crash
537
- if (request.isInterceptResolutionHandled && request.isInterceptResolutionHandled()) {
538
- return;
539
- }
540
- const url = request.url();
597
+ const url = response.url();
541
598
  if (args.filterUrl && !url.includes(args.filterUrl)) {
542
- if (!request.isInterceptResolutionHandled || !request.isInterceptResolutionHandled()) {
543
- request.continue().catch(() => { });
544
- }
545
599
  return;
546
600
  }
547
601
  const entry = {
548
602
  url,
549
- method: request.method(),
550
- resourceType: request.resourceType(),
603
+ status: response.status(),
604
+ resourceType: response.request()?.resourceType?.() || 'unknown',
551
605
  timestamp: Date.now(),
552
606
  };
553
607
  if (args.includeHeaders) {
554
- entry.headers = request.headers();
555
- }
556
- if (args.includeBody) {
557
- entry.postData = request.postData();
608
+ try {
609
+ entry.headers = response.headers();
610
+ }
611
+ catch (e) {
612
+ entry.headers = {};
613
+ }
558
614
  }
615
+ // Note: Response body requires async handling, skip for stability
559
616
  requests.push(entry);
560
- // Only continue if not already handled
561
- if (!request.isInterceptResolutionHandled || !request.isInterceptResolutionHandled()) {
562
- request.continue().catch(() => { });
563
- }
564
- }
565
- catch {
566
- // Ignore all errors in handler to prevent crash
567
- }
568
- };
569
- try {
570
- try {
571
- await page.setRequestInterception(true);
572
- }
573
- catch (e) {
574
- // Interception might already be enabled
575
- }
576
- page.on('request', requestHandler);
577
- page.on('response', (response) => {
617
+ // Track size from headers
578
618
  try {
579
619
  const headers = response.headers();
580
620
  const size = parseInt(headers['content-length'] || '0', 10);
581
621
  totalSize += size;
582
622
  }
583
623
  catch {
584
- // Ignore errors
624
+ // Ignore
585
625
  }
586
- });
626
+ }
627
+ catch {
628
+ // Ignore all errors in handler to prevent crash
629
+ }
630
+ };
631
+ try {
632
+ page.on('response', responseHandler);
587
633
  await new Promise((r) => setTimeout(r, duration));
588
634
  }
589
635
  catch (e) {
590
636
  // Capture setup errors
591
637
  }
592
638
  finally {
593
- page.off('request', requestHandler);
594
639
  try {
595
- await page.setRequestInterception(false);
640
+ page.off('response', responseHandler);
641
+ }
642
+ catch (e) {
643
+ // Ignore cleanup errors
596
644
  }
597
- catch (e) { }
598
645
  }
599
646
  return {
600
647
  requests: requests.slice(0, 500),
@@ -603,57 +650,55 @@ export async function handleNetworkRecorder(page, args) {
603
650
  };
604
651
  }
605
652
  /**
606
- * Discover hidden API endpoints
653
+ * Discover hidden API endpoints - Uses response events to avoid crashes
607
654
  */
608
655
  export async function handleApiFinder(page, args) {
609
656
  const apis = [];
610
657
  const patterns = args.patterns || ['/api/', '/v1/', '/v2/', '/graphql', '/rest/', '.json'];
611
- const requestHandler = (request) => {
658
+ // Response handler - safer than request interception
659
+ const responseHandler = (response) => {
612
660
  try {
613
- // Check if request is already handled
614
- if (request.isInterceptResolutionHandled && request.isInterceptResolutionHandled()) {
661
+ const request = response.request();
662
+ if (!request)
615
663
  return;
616
- }
617
- const url = request.url();
664
+ const url = response.url();
665
+ const resourceType = request.resourceType?.() || 'unknown';
618
666
  const isApi = patterns.some((p) => url.includes(p));
619
- const isXhr = request.resourceType() === 'xhr' || request.resourceType() === 'fetch';
667
+ const isXhr = resourceType === 'xhr' || resourceType === 'fetch';
620
668
  if (isApi || (args.includeInternal !== false && isXhr)) {
621
669
  apis.push({
622
670
  url,
623
- method: request.method(),
624
- type: request.resourceType(),
671
+ method: request.method?.() || 'GET',
672
+ type: resourceType,
625
673
  });
626
674
  }
627
- // Only continue if not already handled
628
- if (!request.isInterceptResolutionHandled || !request.isInterceptResolutionHandled()) {
629
- request.continue().catch(() => { });
630
- }
631
675
  }
632
676
  catch (e) {
633
677
  // Ignore errors in handler
634
678
  }
635
679
  };
636
680
  try {
637
- await page.setRequestInterception(true);
638
- page.on('request', requestHandler);
681
+ page.on('response', responseHandler);
639
682
  // Trigger some interactions to discover more APIs
640
- await page.evaluate(() => {
641
- window.scrollTo(0, document.body.scrollHeight / 2);
642
- });
683
+ try {
684
+ await page.evaluate(() => {
685
+ window.scrollTo(0, document.body.scrollHeight / 2);
686
+ });
687
+ }
688
+ catch (e) {
689
+ // Ignore scroll errors
690
+ }
643
691
  await new Promise((r) => setTimeout(r, 3000));
644
692
  }
645
693
  catch (error) {
646
- // Log error but prioritize returning what we found
647
- console.error('Error in api_finder:', error);
694
+ // Capture errors but continue
648
695
  }
649
696
  finally {
650
- page.off('request', requestHandler);
651
- // Safely disable interception
652
697
  try {
653
- await page.setRequestInterception(false);
698
+ page.off('response', responseHandler);
654
699
  }
655
700
  catch (e) {
656
- // Ignore if already disabled or browser closed
701
+ // Ignore cleanup errors
657
702
  }
658
703
  }
659
704
  // Remove duplicates
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-browser-mcp-server",
3
- "version": "2.21.0",
3
+ "version": "2.21.2",
4
4
  "description": "🦁 MCP server for Brave Real Browser - NPM Workspaces Monorepo with anti-detection features",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -41,7 +41,7 @@
41
41
  "dependencies": {
42
42
  "@modelcontextprotocol/sdk": "latest",
43
43
  "@types/turndown": "latest",
44
- "brave-real-browser": "^2.3.0",
44
+ "brave-real-browser": "^2.3.2",
45
45
  "turndown": "latest"
46
46
  },
47
47
  "peerDependencies": {