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.
- package/dist/handlers/advanced-tools.js +164 -119
- package/package.json +2 -2
|
@@ -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
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
//
|
|
67
|
-
if (
|
|
68
|
-
|
|
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
|
-
|
|
98
|
+
finalUrl = page.url() || args.url;
|
|
99
|
+
if (!chain.includes(finalUrl)) {
|
|
100
|
+
chain.push(finalUrl);
|
|
101
|
+
}
|
|
78
102
|
}
|
|
79
103
|
catch (e) {
|
|
80
|
-
//
|
|
104
|
+
// Use original URL if page.url() fails
|
|
81
105
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
471
|
-
|
|
472
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
507
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
573
|
+
// Clean up event listeners
|
|
574
|
+
try {
|
|
575
|
+
if (args.includeConsole !== false) {
|
|
576
|
+
page.off('console', consoleHandler);
|
|
520
577
|
}
|
|
521
|
-
|
|
522
|
-
|
|
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
|
-
|
|
594
|
+
// Response handler - safer than request interception
|
|
595
|
+
const responseHandler = (response) => {
|
|
535
596
|
try {
|
|
536
|
-
|
|
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
|
-
|
|
550
|
-
resourceType: request
|
|
603
|
+
status: response.status(),
|
|
604
|
+
resourceType: response.request()?.resourceType?.() || 'unknown',
|
|
551
605
|
timestamp: Date.now(),
|
|
552
606
|
};
|
|
553
607
|
if (args.includeHeaders) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
658
|
+
// Response handler - safer than request interception
|
|
659
|
+
const responseHandler = (response) => {
|
|
612
660
|
try {
|
|
613
|
-
|
|
614
|
-
if (request
|
|
661
|
+
const request = response.request();
|
|
662
|
+
if (!request)
|
|
615
663
|
return;
|
|
616
|
-
|
|
617
|
-
const
|
|
664
|
+
const url = response.url();
|
|
665
|
+
const resourceType = request.resourceType?.() || 'unknown';
|
|
618
666
|
const isApi = patterns.some((p) => url.includes(p));
|
|
619
|
-
const isXhr =
|
|
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:
|
|
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
|
-
|
|
638
|
-
page.on('request', requestHandler);
|
|
681
|
+
page.on('response', responseHandler);
|
|
639
682
|
// Trigger some interactions to discover more APIs
|
|
640
|
-
|
|
641
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
698
|
+
page.off('response', responseHandler);
|
|
654
699
|
}
|
|
655
700
|
catch (e) {
|
|
656
|
-
// Ignore
|
|
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.
|
|
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.
|
|
44
|
+
"brave-real-browser": "^2.3.2",
|
|
45
45
|
"turndown": "latest"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|