brave-real-browser-mcp-server 2.24.4 → 2.24.5
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 +274 -12
- package/package.json +2 -2
|
@@ -586,22 +586,78 @@ export async function handleDeepAnalysis(page, args) {
|
|
|
586
586
|
}
|
|
587
587
|
/**
|
|
588
588
|
* Record full network traffic - Uses response events to avoid crashes
|
|
589
|
+
* ULTRA POWERFUL: API detection, media URLs, smart categorization
|
|
589
590
|
*/
|
|
590
591
|
export async function handleNetworkRecorder(page, args) {
|
|
591
592
|
const requests = [];
|
|
592
593
|
const duration = args.duration || 10000;
|
|
593
594
|
let totalSize = 0;
|
|
595
|
+
const categories = {};
|
|
596
|
+
const apis = [];
|
|
597
|
+
const mediaUrls = [];
|
|
598
|
+
const seen = new Set();
|
|
599
|
+
// ============================================================
|
|
600
|
+
// SMART CATEGORIZATION HELPER
|
|
601
|
+
// ============================================================
|
|
602
|
+
const categorizeUrl = (url, resourceType) => {
|
|
603
|
+
const urlLower = url.toLowerCase();
|
|
604
|
+
// API endpoints
|
|
605
|
+
if (/\/api\/|\/v\d+\/|\.json(\?|$)|graphql/i.test(url))
|
|
606
|
+
return 'api';
|
|
607
|
+
// Media
|
|
608
|
+
if (/\.(mp4|webm|m3u8|ts|mp3|flac|ogg)/i.test(url))
|
|
609
|
+
return 'media';
|
|
610
|
+
if (resourceType === 'media' || resourceType === 'video' || resourceType === 'audio')
|
|
611
|
+
return 'media';
|
|
612
|
+
// Images
|
|
613
|
+
if (/\.(jpg|jpeg|png|gif|webp|svg|ico)/i.test(url) || resourceType === 'image')
|
|
614
|
+
return 'image';
|
|
615
|
+
// Scripts
|
|
616
|
+
if (/\.js(\?|$)/i.test(url) || resourceType === 'script')
|
|
617
|
+
return 'script';
|
|
618
|
+
// Styles
|
|
619
|
+
if (/\.css(\?|$)/i.test(url) || resourceType === 'stylesheet')
|
|
620
|
+
return 'style';
|
|
621
|
+
// Fonts
|
|
622
|
+
if (/\.(woff2?|ttf|eot|otf)/i.test(url) || resourceType === 'font')
|
|
623
|
+
return 'font';
|
|
624
|
+
// XHR/Fetch
|
|
625
|
+
if (resourceType === 'xhr' || resourceType === 'fetch')
|
|
626
|
+
return 'xhr';
|
|
627
|
+
// Documents
|
|
628
|
+
if (resourceType === 'document')
|
|
629
|
+
return 'document';
|
|
630
|
+
return 'other';
|
|
631
|
+
};
|
|
594
632
|
// Response handler - safer than request interception
|
|
595
633
|
const responseHandler = (response) => {
|
|
596
634
|
try {
|
|
597
635
|
const url = response.url();
|
|
636
|
+
// Dedup
|
|
637
|
+
if (seen.has(url))
|
|
638
|
+
return;
|
|
639
|
+
seen.add(url);
|
|
598
640
|
if (args.filterUrl && !url.includes(args.filterUrl)) {
|
|
599
641
|
return;
|
|
600
642
|
}
|
|
643
|
+
const resourceType = response.request()?.resourceType?.() || 'unknown';
|
|
644
|
+
const method = response.request()?.method?.() || 'GET';
|
|
645
|
+
const category = categorizeUrl(url, resourceType);
|
|
646
|
+
categories[category] = (categories[category] || 0) + 1;
|
|
647
|
+
// Collect API endpoints
|
|
648
|
+
if (category === 'api' || resourceType === 'xhr' || resourceType === 'fetch') {
|
|
649
|
+
apis.push({ url, method, type: resourceType });
|
|
650
|
+
}
|
|
651
|
+
// Collect media URLs
|
|
652
|
+
if (category === 'media' || /\.(mp4|webm|m3u8|ts|mp3)/i.test(url)) {
|
|
653
|
+
mediaUrls.push(url);
|
|
654
|
+
}
|
|
601
655
|
const entry = {
|
|
602
656
|
url,
|
|
603
657
|
status: response.status(),
|
|
604
|
-
resourceType
|
|
658
|
+
resourceType,
|
|
659
|
+
category,
|
|
660
|
+
method,
|
|
605
661
|
timestamp: Date.now(),
|
|
606
662
|
};
|
|
607
663
|
if (args.includeHeaders) {
|
|
@@ -612,7 +668,6 @@ export async function handleNetworkRecorder(page, args) {
|
|
|
612
668
|
entry.headers = {};
|
|
613
669
|
}
|
|
614
670
|
}
|
|
615
|
-
// Note: Response body requires async handling, skip for stability
|
|
616
671
|
requests.push(entry);
|
|
617
672
|
// Track size from headers
|
|
618
673
|
try {
|
|
@@ -647,6 +702,10 @@ export async function handleNetworkRecorder(page, args) {
|
|
|
647
702
|
requests: requests.slice(0, 500),
|
|
648
703
|
count: requests.length,
|
|
649
704
|
totalSize,
|
|
705
|
+
categories,
|
|
706
|
+
apis: apis.length > 0 ? apis : undefined,
|
|
707
|
+
mediaUrls: mediaUrls.length > 0 ? mediaUrls : undefined,
|
|
708
|
+
message: `📡 Recorded ${requests.length} requests (${Math.round(totalSize / 1024)}KB) | APIs: ${apis.length} | Media: ${mediaUrls.length}`
|
|
650
709
|
};
|
|
651
710
|
}
|
|
652
711
|
/**
|
|
@@ -776,6 +835,7 @@ export async function handleAdProtectionDetector(page, args) {
|
|
|
776
835
|
}
|
|
777
836
|
/**
|
|
778
837
|
* Wait for dynamic AJAX loading
|
|
838
|
+
* ULTRA POWERFUL: Infinite scroll, lazy load, mutation observer
|
|
779
839
|
*/
|
|
780
840
|
export async function handleAjaxContentWaiter(page, args) {
|
|
781
841
|
const timeout = args.timeout || 30000;
|
|
@@ -783,6 +843,79 @@ export async function handleAjaxContentWaiter(page, args) {
|
|
|
783
843
|
const startTime = Date.now();
|
|
784
844
|
let content;
|
|
785
845
|
let loaded = false;
|
|
846
|
+
let newElementsCount = 0;
|
|
847
|
+
let scrollDepth = 0;
|
|
848
|
+
// ============================================================
|
|
849
|
+
// 1. MUTATION OBSERVER: Track DOM changes in real-time
|
|
850
|
+
// ============================================================
|
|
851
|
+
const setupMutationObserver = async () => {
|
|
852
|
+
return await page.evaluate(() => {
|
|
853
|
+
return new Promise((resolve) => {
|
|
854
|
+
let added = 0;
|
|
855
|
+
let modified = 0;
|
|
856
|
+
const observer = new MutationObserver((mutations) => {
|
|
857
|
+
mutations.forEach(m => {
|
|
858
|
+
added += m.addedNodes.length;
|
|
859
|
+
if (m.type === 'attributes' || m.type === 'characterData')
|
|
860
|
+
modified++;
|
|
861
|
+
});
|
|
862
|
+
});
|
|
863
|
+
observer.observe(document.body, {
|
|
864
|
+
childList: true,
|
|
865
|
+
subtree: true,
|
|
866
|
+
attributes: true,
|
|
867
|
+
characterData: true
|
|
868
|
+
});
|
|
869
|
+
// Return after 2 seconds of observation
|
|
870
|
+
setTimeout(() => {
|
|
871
|
+
observer.disconnect();
|
|
872
|
+
resolve({ added, modified });
|
|
873
|
+
}, 2000);
|
|
874
|
+
});
|
|
875
|
+
});
|
|
876
|
+
};
|
|
877
|
+
// ============================================================
|
|
878
|
+
// 2. INFINITE SCROLL DETECTION
|
|
879
|
+
// ============================================================
|
|
880
|
+
const handleInfiniteScroll = async () => {
|
|
881
|
+
const initialHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
882
|
+
const initialCount = await page.evaluate(() => document.querySelectorAll('*').length);
|
|
883
|
+
// Scroll to bottom
|
|
884
|
+
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
885
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
886
|
+
// Check if new content loaded
|
|
887
|
+
const newHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
888
|
+
const newCount = await page.evaluate(() => document.querySelectorAll('*').length);
|
|
889
|
+
return {
|
|
890
|
+
scrolled: newHeight > initialHeight,
|
|
891
|
+
newElements: newCount - initialCount,
|
|
892
|
+
scrollDepth: newHeight
|
|
893
|
+
};
|
|
894
|
+
};
|
|
895
|
+
// ============================================================
|
|
896
|
+
// 3. LAZY LOAD DETECTION
|
|
897
|
+
// ============================================================
|
|
898
|
+
const detectLazyLoad = async () => {
|
|
899
|
+
return await page.evaluate(() => {
|
|
900
|
+
const lazyElements = [];
|
|
901
|
+
// Check for common lazy load patterns
|
|
902
|
+
document.querySelectorAll('[data-src], [data-lazy], [loading="lazy"], .lazy, .lazyload').forEach(el => {
|
|
903
|
+
const dataSrc = el.getAttribute('data-src') || el.getAttribute('data-lazy');
|
|
904
|
+
if (dataSrc)
|
|
905
|
+
lazyElements.push(dataSrc);
|
|
906
|
+
});
|
|
907
|
+
// Intersection Observer based lazy images
|
|
908
|
+
document.querySelectorAll('img[data-src], img.lazy').forEach(img => {
|
|
909
|
+
const dataSrc = img.dataset.src;
|
|
910
|
+
if (dataSrc)
|
|
911
|
+
lazyElements.push(dataSrc);
|
|
912
|
+
});
|
|
913
|
+
return lazyElements;
|
|
914
|
+
});
|
|
915
|
+
};
|
|
916
|
+
// ============================================================
|
|
917
|
+
// 4. MAIN WAITING LOGIC
|
|
918
|
+
// ============================================================
|
|
786
919
|
while (Date.now() - startTime < timeout) {
|
|
787
920
|
if (args.selector) {
|
|
788
921
|
const element = await page.$(args.selector);
|
|
@@ -795,17 +928,35 @@ export async function handleAjaxContentWaiter(page, args) {
|
|
|
795
928
|
}
|
|
796
929
|
}
|
|
797
930
|
else {
|
|
798
|
-
//
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
931
|
+
// Smart waiting: Check for ongoing activity
|
|
932
|
+
const mutationResult = await setupMutationObserver();
|
|
933
|
+
newElementsCount = mutationResult.added;
|
|
934
|
+
if (mutationResult.added === 0 && mutationResult.modified === 0) {
|
|
935
|
+
// No DOM changes, content likely loaded
|
|
936
|
+
loaded = true;
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
// Try infinite scroll to load more content
|
|
941
|
+
const scrollResult = await handleInfiniteScroll();
|
|
942
|
+
if (scrollResult.scrolled) {
|
|
943
|
+
scrollDepth = scrollResult.scrollDepth;
|
|
944
|
+
newElementsCount += scrollResult.newElements;
|
|
802
945
|
}
|
|
803
946
|
await new Promise((r) => setTimeout(r, pollInterval));
|
|
804
947
|
}
|
|
948
|
+
// Detect any lazy-loaded content
|
|
949
|
+
const lazyElements = await detectLazyLoad();
|
|
805
950
|
return {
|
|
806
951
|
loaded,
|
|
807
952
|
waitTime: Date.now() - startTime,
|
|
808
953
|
content,
|
|
954
|
+
newElementsCount,
|
|
955
|
+
scrollDepth,
|
|
956
|
+
lazyElements: lazyElements.length > 0 ? lazyElements : undefined,
|
|
957
|
+
message: loaded
|
|
958
|
+
? `✅ Content loaded in ${Date.now() - startTime}ms (${newElementsCount} new elements, scroll: ${scrollDepth}px)`
|
|
959
|
+
: `⏱️ Timeout after ${timeout}ms`
|
|
809
960
|
};
|
|
810
961
|
}
|
|
811
962
|
/**
|
|
@@ -1002,20 +1153,124 @@ export async function handleVideoRecording(page, args, recorderState) {
|
|
|
1002
1153
|
}
|
|
1003
1154
|
/**
|
|
1004
1155
|
* Harvest all links from page
|
|
1156
|
+
* ULTRA POWERFUL: Pagination detection, smart categorization, file types
|
|
1005
1157
|
*/
|
|
1006
1158
|
export async function handleLinkHarvester(page, args) {
|
|
1007
1159
|
const currentUrl = new URL(page.url());
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1160
|
+
// ============================================================
|
|
1161
|
+
// 1. EXTRACT ALL LINKS WITH SMART CATEGORIZATION
|
|
1162
|
+
// ============================================================
|
|
1163
|
+
const allLinks = await page.evaluate(() => {
|
|
1164
|
+
const links = [];
|
|
1165
|
+
document.querySelectorAll('a[href]').forEach((a) => {
|
|
1166
|
+
const anchor = a;
|
|
1167
|
+
links.push({
|
|
1168
|
+
url: anchor.href,
|
|
1169
|
+
text: a.textContent?.trim()?.substring(0, 100) || '',
|
|
1170
|
+
attrs: {
|
|
1171
|
+
rel: anchor.rel || '',
|
|
1172
|
+
target: anchor.target || '',
|
|
1173
|
+
class: anchor.className || '',
|
|
1174
|
+
id: anchor.id || '',
|
|
1175
|
+
download: anchor.download || '',
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
});
|
|
1179
|
+
return links;
|
|
1180
|
+
});
|
|
1181
|
+
// ============================================================
|
|
1182
|
+
// 2. PAGINATION DETECTION
|
|
1183
|
+
// ============================================================
|
|
1184
|
+
const pagination = await page.evaluate(() => {
|
|
1185
|
+
let nextPage;
|
|
1186
|
+
let prevPage;
|
|
1187
|
+
let totalPages;
|
|
1188
|
+
// Common pagination selectors
|
|
1189
|
+
const nextSelectors = [
|
|
1190
|
+
'a[rel="next"]', 'a.next', 'a.pagination-next',
|
|
1191
|
+
'[aria-label="Next"]', 'a:has-text("Next")', 'a:has-text(">")',
|
|
1192
|
+
'.pagination a:last-child', 'a.page-link:last-child'
|
|
1193
|
+
];
|
|
1194
|
+
const prevSelectors = [
|
|
1195
|
+
'a[rel="prev"]', 'a.prev', 'a.pagination-prev',
|
|
1196
|
+
'[aria-label="Previous"]', 'a:has-text("Prev")', 'a:has-text("<")'
|
|
1197
|
+
];
|
|
1198
|
+
for (const sel of nextSelectors) {
|
|
1199
|
+
try {
|
|
1200
|
+
const el = document.querySelector(sel);
|
|
1201
|
+
if (el?.href) {
|
|
1202
|
+
nextPage = el.href;
|
|
1203
|
+
break;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
catch { /* invalid selector */ }
|
|
1207
|
+
}
|
|
1208
|
+
for (const sel of prevSelectors) {
|
|
1209
|
+
try {
|
|
1210
|
+
const el = document.querySelector(sel);
|
|
1211
|
+
if (el?.href) {
|
|
1212
|
+
prevPage = el.href;
|
|
1213
|
+
break;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
catch { /* invalid selector */ }
|
|
1217
|
+
}
|
|
1218
|
+
// Count page numbers
|
|
1219
|
+
const pageNumbers = Array.from(document.querySelectorAll('.pagination a, .page-numbers a, nav a'))
|
|
1220
|
+
.map(a => parseInt(a.textContent || '0', 10))
|
|
1221
|
+
.filter(n => !isNaN(n) && n > 0);
|
|
1222
|
+
if (pageNumbers.length > 0) {
|
|
1223
|
+
totalPages = Math.max(...pageNumbers);
|
|
1224
|
+
}
|
|
1225
|
+
return { nextPage, prevPage, totalPages };
|
|
1226
|
+
});
|
|
1227
|
+
// ============================================================
|
|
1228
|
+
// 3. SMART LINK CATEGORIZATION
|
|
1229
|
+
// ============================================================
|
|
1230
|
+
const categorizeLink = (url, text, attrs) => {
|
|
1231
|
+
const urlLower = url.toLowerCase();
|
|
1232
|
+
const textLower = text.toLowerCase();
|
|
1233
|
+
// File downloads
|
|
1234
|
+
if (/\.(pdf|doc|docx|xls|xlsx|zip|rar|7z|tar|gz)(\?.*)?$/i.test(url))
|
|
1235
|
+
return 'document';
|
|
1236
|
+
if (/\.(mp4|mkv|avi|mov|webm|flv)(\?.*)?$/i.test(url))
|
|
1237
|
+
return 'video';
|
|
1238
|
+
if (/\.(mp3|wav|flac|aac|ogg)(\?.*)?$/i.test(url))
|
|
1239
|
+
return 'audio';
|
|
1240
|
+
if (/\.(jpg|jpeg|png|gif|webp|svg|bmp)(\?.*)?$/i.test(url))
|
|
1241
|
+
return 'image';
|
|
1242
|
+
if (attrs.download)
|
|
1243
|
+
return 'download';
|
|
1244
|
+
// Navigation
|
|
1245
|
+
if (/\/(next|page|p)\/\d+|[?&]page=\d+/i.test(url))
|
|
1246
|
+
return 'pagination';
|
|
1247
|
+
if (textLower.includes('next') || textLower.includes('prev'))
|
|
1248
|
+
return 'pagination';
|
|
1249
|
+
// Social
|
|
1250
|
+
if (/facebook|twitter|instagram|linkedin|youtube|tiktok/i.test(url))
|
|
1251
|
+
return 'social';
|
|
1252
|
+
// Common patterns
|
|
1253
|
+
if (/login|signin|sign-in/i.test(url))
|
|
1254
|
+
return 'auth';
|
|
1255
|
+
if (/register|signup|sign-up/i.test(url))
|
|
1256
|
+
return 'auth';
|
|
1257
|
+
if (/search|query|q=/i.test(url))
|
|
1258
|
+
return 'search';
|
|
1259
|
+
if (/contact|about|faq|help/i.test(url))
|
|
1260
|
+
return 'info';
|
|
1261
|
+
return 'navigation';
|
|
1262
|
+
};
|
|
1014
1263
|
const processedLinks = [];
|
|
1264
|
+
const categories = {};
|
|
1265
|
+
const seen = new Set();
|
|
1015
1266
|
let internal = 0;
|
|
1016
1267
|
let external = 0;
|
|
1017
1268
|
for (const link of allLinks) {
|
|
1018
1269
|
try {
|
|
1270
|
+
// Dedup by URL
|
|
1271
|
+
if (seen.has(link.url))
|
|
1272
|
+
continue;
|
|
1273
|
+
seen.add(link.url);
|
|
1019
1274
|
const linkUrl = new URL(link.url);
|
|
1020
1275
|
const isInternal = linkUrl.hostname === currentUrl.hostname;
|
|
1021
1276
|
if (args.filter && !link.url.includes(args.filter) && !link.text.includes(args.filter)) {
|
|
@@ -1025,10 +1280,13 @@ export async function handleLinkHarvester(page, args) {
|
|
|
1025
1280
|
continue;
|
|
1026
1281
|
if (!isInternal && args.includeExternal === false)
|
|
1027
1282
|
continue;
|
|
1283
|
+
const category = categorizeLink(link.url, link.text, link.attrs);
|
|
1284
|
+
categories[category] = (categories[category] || 0) + 1;
|
|
1028
1285
|
processedLinks.push({
|
|
1029
1286
|
url: link.url,
|
|
1030
1287
|
text: link.text,
|
|
1031
1288
|
type: isInternal ? 'internal' : 'external',
|
|
1289
|
+
category,
|
|
1032
1290
|
});
|
|
1033
1291
|
if (isInternal)
|
|
1034
1292
|
internal++;
|
|
@@ -1045,6 +1303,10 @@ export async function handleLinkHarvester(page, args) {
|
|
|
1045
1303
|
links: processedLinks,
|
|
1046
1304
|
internal,
|
|
1047
1305
|
external,
|
|
1306
|
+
pagination: (pagination.nextPage || pagination.prevPage || pagination.totalPages) ? pagination : undefined,
|
|
1307
|
+
categories,
|
|
1308
|
+
message: `🔗 Found ${processedLinks.length} links (${internal} internal, ${external} external)` +
|
|
1309
|
+
(pagination.nextPage ? ` | Next: ${pagination.nextPage}` : '')
|
|
1048
1310
|
};
|
|
1049
1311
|
}
|
|
1050
1312
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.24.
|
|
3
|
+
"version": "2.24.5",
|
|
4
4
|
"description": "🦁 MCP server for Brave Real Browser - NPM Workspaces Monorepo with anti-detection features, SSE streaming, and LSP compatibility",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@modelcontextprotocol/sdk": "latest",
|
|
52
52
|
"@types/turndown": "latest",
|
|
53
|
-
"brave-real-browser": "^2.5.
|
|
53
|
+
"brave-real-browser": "^2.5.5",
|
|
54
54
|
"turndown": "latest",
|
|
55
55
|
"vscode-languageserver": "^9.0.1",
|
|
56
56
|
"vscode-languageserver-textdocument": "^1.0.12"
|