brave-real-browser-mcp-server 2.10.0 ā 2.11.1
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-extraction-handlers.js +6 -2
- package/dist/handlers/advanced-video-media-handlers.js +52 -4
- package/dist/handlers/api-integration-handlers.js +1 -1
- package/dist/handlers/data-extraction-handlers.js +12 -5
- package/dist/handlers/smart-data-extractors.js +22 -6
- package/dist/tool-definitions.js +8 -6
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ export async function handleAdvancedVideoExtraction(args) {
|
|
|
14
14
|
requirePage: true,
|
|
15
15
|
});
|
|
16
16
|
const page = getCurrentPage();
|
|
17
|
-
const waitTime = args.waitTime ||
|
|
17
|
+
const waitTime = args.waitTime || 20000;
|
|
18
18
|
// Collect all video-related data
|
|
19
19
|
const videoData = {
|
|
20
20
|
directVideoUrls: [],
|
|
@@ -183,7 +183,11 @@ export async function handleAdvancedVideoExtraction(args) {
|
|
|
183
183
|
'button[data-download]',
|
|
184
184
|
'a[href*=".mp4"]',
|
|
185
185
|
'a[href*=".mkv"]',
|
|
186
|
-
'a[class*="download"]'
|
|
186
|
+
'a[class*="download"]',
|
|
187
|
+
'a[href*=".webm"]',
|
|
188
|
+
'.download-btn',
|
|
189
|
+
'.btn-download',
|
|
190
|
+
'[class*="download"]'
|
|
187
191
|
];
|
|
188
192
|
downloadSelectors.forEach(selector => {
|
|
189
193
|
document.querySelectorAll(selector).forEach((el) => {
|
|
@@ -387,12 +387,20 @@ export async function handleNetworkRecordingFinder(args) {
|
|
|
387
387
|
const page = getCurrentPage();
|
|
388
388
|
const duration = args.duration || 10000;
|
|
389
389
|
const filterType = args.filterType || 'video'; // video, audio, media
|
|
390
|
+
const navigateTo = args.navigateTo; // Optional URL to navigate to
|
|
391
|
+
const verbose = args.verbose !== false; // Default true for detailed logging
|
|
390
392
|
const recordings = [];
|
|
393
|
+
let totalResponses = 0;
|
|
394
|
+
let matchedResponses = 0;
|
|
391
395
|
const responseHandler = async (response) => {
|
|
392
396
|
try {
|
|
397
|
+
totalResponses++;
|
|
393
398
|
const url = response.url();
|
|
394
399
|
const contentType = response.headers()['content-type'] || '';
|
|
395
400
|
const resourceType = response.request().resourceType();
|
|
401
|
+
if (verbose && totalResponses % 10 === 0) {
|
|
402
|
+
console.log(`[Network Recording] Processed ${totalResponses} responses, ${matchedResponses} matched`);
|
|
403
|
+
}
|
|
396
404
|
let shouldRecord = false;
|
|
397
405
|
if (filterType === 'video' && (contentType.includes('video') || resourceType === 'media')) {
|
|
398
406
|
shouldRecord = true;
|
|
@@ -404,6 +412,10 @@ export async function handleNetworkRecordingFinder(args) {
|
|
|
404
412
|
shouldRecord = true;
|
|
405
413
|
}
|
|
406
414
|
if (shouldRecord) {
|
|
415
|
+
matchedResponses++;
|
|
416
|
+
if (verbose) {
|
|
417
|
+
console.log(`[Network Recording] ā
Matched ${filterType}: ${url.substring(0, 100)}`);
|
|
418
|
+
}
|
|
407
419
|
try {
|
|
408
420
|
const buffer = await response.buffer();
|
|
409
421
|
recordings.push({
|
|
@@ -428,21 +440,33 @@ export async function handleNetworkRecordingFinder(args) {
|
|
|
428
440
|
// Ignore individual response errors
|
|
429
441
|
}
|
|
430
442
|
};
|
|
443
|
+
console.log(`[Network Recording] š¬ Starting monitoring for ${filterType} (${duration}ms)${navigateTo ? ` + navigating to ${navigateTo}` : ''}`);
|
|
431
444
|
page.on('response', responseHandler);
|
|
445
|
+
// If navigateTo is provided, navigate first, then wait
|
|
446
|
+
if (navigateTo) {
|
|
447
|
+
try {
|
|
448
|
+
await page.goto(navigateTo, { waitUntil: 'networkidle2', timeout: 30000 });
|
|
449
|
+
console.log(`[Network Recording] ā
Navigation complete, continuing monitoring...`);
|
|
450
|
+
}
|
|
451
|
+
catch (e) {
|
|
452
|
+
console.log(`[Network Recording] ā ļø Navigation error (continuing anyway): ${e}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
432
455
|
await sleep(duration);
|
|
433
456
|
page.off('response', responseHandler);
|
|
457
|
+
console.log(`[Network Recording] š Monitoring stopped. Total: ${totalResponses}, Matched: ${matchedResponses}, Recorded: ${recordings.length}`);
|
|
434
458
|
if (recordings.length === 0) {
|
|
435
459
|
return {
|
|
436
460
|
content: [{
|
|
437
461
|
type: 'text',
|
|
438
|
-
text: `ā¹ļø No ${filterType} recordings found
|
|
462
|
+
text: `ā¹ļø No ${filterType} recordings found\n\nš Statistics:\n ⢠Total responses checked: ${totalResponses}\n ⢠Matched ${filterType} responses: ${matchedResponses}\n ⢠Duration: ${duration}ms\n ⢠Navigation: ${navigateTo || 'None'}\n\nš” Suggestions:\n ${navigateTo ? '⢠Try longer duration if page loads slowly\n ⢠Check if page actually has video/media content' : '⢠Use navigateTo parameter to capture requests during page load\n ⢠Example: {"navigateTo": "https://example.com", "duration": 15000}'}\n ⢠Consider 'advanced_video_extraction' for analyzing loaded content`,
|
|
439
463
|
}],
|
|
440
464
|
};
|
|
441
465
|
}
|
|
442
466
|
return {
|
|
443
467
|
content: [{
|
|
444
468
|
type: 'text',
|
|
445
|
-
text: `ā
Network Recordings Found: ${recordings.length}\n\n${JSON.stringify(recordings, null, 2)}`,
|
|
469
|
+
text: `ā
Network Recordings Found: ${recordings.length}\n\nš Statistics:\n ⢠Total responses: ${totalResponses}\n ⢠Matched: ${matchedResponses}\n ⢠Recorded: ${recordings.length}\n\n${JSON.stringify(recordings, null, 2)}`,
|
|
446
470
|
}],
|
|
447
471
|
};
|
|
448
472
|
}
|
|
@@ -476,18 +500,24 @@ export async function handleNetworkRecordingExtractors(args) {
|
|
|
476
500
|
}
|
|
477
501
|
const page = getCurrentPage();
|
|
478
502
|
const duration = args.duration || 10000;
|
|
503
|
+
const navigateTo = args.navigateTo; // Optional URL to navigate to
|
|
504
|
+
const verbose = args.verbose !== false; // Default true
|
|
479
505
|
const extractedData = {
|
|
480
506
|
videos: [],
|
|
481
507
|
audio: [],
|
|
482
508
|
manifests: [],
|
|
483
509
|
apis: [],
|
|
484
510
|
};
|
|
511
|
+
let totalResponses = 0;
|
|
485
512
|
const responseHandler = async (response) => {
|
|
513
|
+
totalResponses++;
|
|
486
514
|
const url = response.url();
|
|
487
515
|
const contentType = response.headers()['content-type'] || '';
|
|
488
516
|
try {
|
|
489
517
|
// Video files
|
|
490
518
|
if (contentType.includes('video') || url.includes('.mp4') || url.includes('.webm')) {
|
|
519
|
+
if (verbose)
|
|
520
|
+
console.log(`[Extractor] š„ Video found: ${url.substring(0, 80)}`);
|
|
491
521
|
extractedData.videos.push({
|
|
492
522
|
url,
|
|
493
523
|
contentType,
|
|
@@ -496,6 +526,8 @@ export async function handleNetworkRecordingExtractors(args) {
|
|
|
496
526
|
}
|
|
497
527
|
// Audio files
|
|
498
528
|
if (contentType.includes('audio') || url.includes('.mp3') || url.includes('.m4a')) {
|
|
529
|
+
if (verbose)
|
|
530
|
+
console.log(`[Extractor] šµ Audio found: ${url.substring(0, 80)}`);
|
|
499
531
|
extractedData.audio.push({
|
|
500
532
|
url,
|
|
501
533
|
contentType,
|
|
@@ -503,6 +535,8 @@ export async function handleNetworkRecordingExtractors(args) {
|
|
|
503
535
|
}
|
|
504
536
|
// Manifest files (HLS, DASH)
|
|
505
537
|
if (url.includes('.m3u8') || url.includes('.mpd')) {
|
|
538
|
+
if (verbose)
|
|
539
|
+
console.log(`[Extractor] š Manifest found: ${url.substring(0, 80)}`);
|
|
506
540
|
const text = await response.text();
|
|
507
541
|
extractedData.manifests.push({
|
|
508
542
|
url,
|
|
@@ -512,6 +546,8 @@ export async function handleNetworkRecordingExtractors(args) {
|
|
|
512
546
|
}
|
|
513
547
|
// API responses with video data
|
|
514
548
|
if (contentType.includes('json') && (url.includes('video') || url.includes('media'))) {
|
|
549
|
+
if (verbose)
|
|
550
|
+
console.log(`[Extractor] š” API found: ${url.substring(0, 80)}`);
|
|
515
551
|
const json = await response.json();
|
|
516
552
|
extractedData.apis.push({
|
|
517
553
|
url,
|
|
@@ -523,23 +559,35 @@ export async function handleNetworkRecordingExtractors(args) {
|
|
|
523
559
|
// Response not available
|
|
524
560
|
}
|
|
525
561
|
};
|
|
562
|
+
console.log(`[Extractor] š¬ Starting extraction (${duration}ms)${navigateTo ? ` + navigating to ${navigateTo}` : ''}`);
|
|
526
563
|
page.on('response', responseHandler);
|
|
564
|
+
// If navigateTo is provided, navigate first, then wait
|
|
565
|
+
if (navigateTo) {
|
|
566
|
+
try {
|
|
567
|
+
await page.goto(navigateTo, { waitUntil: 'networkidle2', timeout: 30000 });
|
|
568
|
+
console.log(`[Extractor] ā
Navigation complete, continuing extraction...`);
|
|
569
|
+
}
|
|
570
|
+
catch (e) {
|
|
571
|
+
console.log(`[Extractor] ā ļø Navigation error (continuing): ${e}`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
527
574
|
await sleep(duration);
|
|
528
575
|
page.off('response', responseHandler);
|
|
529
576
|
const totalFound = extractedData.videos.length + extractedData.audio.length +
|
|
530
577
|
extractedData.manifests.length + extractedData.apis.length;
|
|
578
|
+
console.log(`[Extractor] š Extraction complete. Total responses: ${totalResponses}, Extracted: ${totalFound}`);
|
|
531
579
|
if (totalFound === 0) {
|
|
532
580
|
return {
|
|
533
581
|
content: [{
|
|
534
582
|
type: 'text',
|
|
535
|
-
text: `ā¹ļø No media content extracted
|
|
583
|
+
text: `ā¹ļø No media content extracted\n\nš Statistics:\n ⢠Total responses checked: ${totalResponses}\n ⢠Duration: ${duration}ms\n ⢠Navigation: ${navigateTo || 'None'}\n\nš” Suggestions:\n ${navigateTo ? '⢠Try longer duration (15000-20000ms)\n ⢠Verify page actually contains video/media' : '⢠Add navigateTo parameter: {"navigateTo": "https://example.com", "duration": 15000}'}\n ⢠Use 'advanced_video_extraction' for analyzing loaded content\n ⢠Check browser console logs for detailed monitoring`,
|
|
536
584
|
}],
|
|
537
585
|
};
|
|
538
586
|
}
|
|
539
587
|
return {
|
|
540
588
|
content: [{
|
|
541
589
|
type: 'text',
|
|
542
|
-
text: `ā
Network Recording Extraction Complete\n\
|
|
590
|
+
text: `ā
Network Recording Extraction Complete\n\nš Results:\n ⢠Videos: ${extractedData.videos.length}\n ⢠Audio: ${extractedData.audio.length}\n ⢠Manifests: ${extractedData.manifests.length}\n ⢠APIs: ${extractedData.apis.length}\n ⢠Total responses: ${totalResponses}\n\n${JSON.stringify(extractedData, null, 2)}`,
|
|
543
591
|
}],
|
|
544
592
|
};
|
|
545
593
|
}
|
|
@@ -6,7 +6,7 @@ import { sleep } from '../system-utils.js';
|
|
|
6
6
|
* REST API Endpoint Finder - Discover REST API endpoints
|
|
7
7
|
*/
|
|
8
8
|
export async function handleRESTAPIEndpointFinder(args) {
|
|
9
|
-
const { url, analyzeNetworkRequests = true, scanDuration =
|
|
9
|
+
const { url, analyzeNetworkRequests = true, scanDuration = 10000 } = args;
|
|
10
10
|
try {
|
|
11
11
|
const page = getPageInstance();
|
|
12
12
|
if (!page) {
|
|
@@ -195,7 +195,8 @@ export async function handleExtractJSON(args) {
|
|
|
195
195
|
const results = [];
|
|
196
196
|
// Extract JSON from script tags
|
|
197
197
|
if (source === 'script' || source === 'all') {
|
|
198
|
-
const
|
|
198
|
+
const defaultSelector = selector || 'script[type="application/json"], script[type="application/ld+json"], script';
|
|
199
|
+
const scripts = document.querySelectorAll(defaultSelector);
|
|
199
200
|
scripts.forEach((script, index) => {
|
|
200
201
|
try {
|
|
201
202
|
const content = script.textContent || '';
|
|
@@ -346,7 +347,7 @@ export async function handleExtractSchema(args) {
|
|
|
346
347
|
});
|
|
347
348
|
const page = getCurrentPage();
|
|
348
349
|
const format = args.format || 'all';
|
|
349
|
-
const schemaType = args.schemaType;
|
|
350
|
+
const schemaType = args.schemaType || ['WebPage', 'Organization', 'Product', 'BreadcrumbList'];
|
|
350
351
|
const schemaData = await page.evaluate(({ format, schemaType }) => {
|
|
351
352
|
const results = [];
|
|
352
353
|
// Extract JSON-LD
|
|
@@ -358,7 +359,9 @@ export async function handleExtractSchema(args) {
|
|
|
358
359
|
// Filter by schema type if specified
|
|
359
360
|
if (schemaType) {
|
|
360
361
|
const type = data['@type'] || '';
|
|
361
|
-
|
|
362
|
+
const types = Array.isArray(schemaType) ? schemaType : [schemaType];
|
|
363
|
+
const typeMatch = types.some(t => type.toLowerCase().includes(t.toLowerCase()));
|
|
364
|
+
if (!typeMatch) {
|
|
362
365
|
return;
|
|
363
366
|
}
|
|
364
367
|
}
|
|
@@ -378,8 +381,12 @@ export async function handleExtractSchema(args) {
|
|
|
378
381
|
const items = document.querySelectorAll('[itemscope]');
|
|
379
382
|
items.forEach((item) => {
|
|
380
383
|
const itemType = item.getAttribute('itemtype') || '';
|
|
381
|
-
if (schemaType
|
|
382
|
-
|
|
384
|
+
if (schemaType) {
|
|
385
|
+
const types = Array.isArray(schemaType) ? schemaType : [schemaType];
|
|
386
|
+
const typeMatch = types.some(t => itemType.toLowerCase().includes(t.toLowerCase()));
|
|
387
|
+
if (!typeMatch) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
383
390
|
}
|
|
384
391
|
const properties = {};
|
|
385
392
|
const props = item.querySelectorAll('[itemprop]');
|
|
@@ -184,7 +184,7 @@ export async function handleAjaxExtractor(args) {
|
|
|
184
184
|
requirePage: true,
|
|
185
185
|
});
|
|
186
186
|
const page = getCurrentPage();
|
|
187
|
-
const duration = args.duration ||
|
|
187
|
+
const duration = args.duration || 15000;
|
|
188
188
|
const url = args.url;
|
|
189
189
|
const requests = [];
|
|
190
190
|
const requestHandler = (request) => {
|
|
@@ -223,7 +223,7 @@ export async function handleFetchXHR(args) {
|
|
|
223
223
|
requirePage: true,
|
|
224
224
|
});
|
|
225
225
|
const page = getCurrentPage();
|
|
226
|
-
const duration = args.duration ||
|
|
226
|
+
const duration = args.duration || 15000;
|
|
227
227
|
const xhrData = [];
|
|
228
228
|
const responseHandler = async (response) => {
|
|
229
229
|
const request = response.request();
|
|
@@ -267,8 +267,8 @@ export async function handleNetworkRecorder(args) {
|
|
|
267
267
|
requirePage: true,
|
|
268
268
|
});
|
|
269
269
|
const page = getCurrentPage();
|
|
270
|
-
const duration = args.duration ||
|
|
271
|
-
const filterTypes = args.filterTypes || ['
|
|
270
|
+
const duration = args.duration || 20000;
|
|
271
|
+
const filterTypes = args.filterTypes || ['video', 'xhr', 'fetch', 'media'];
|
|
272
272
|
const networkActivity = [];
|
|
273
273
|
const requestHandler = (request) => {
|
|
274
274
|
const resourceType = request.resourceType();
|
|
@@ -317,13 +317,16 @@ export async function handleApiFinder(args) {
|
|
|
317
317
|
requirePage: true,
|
|
318
318
|
});
|
|
319
319
|
const page = getCurrentPage();
|
|
320
|
-
const
|
|
320
|
+
const deepScan = args.deepScan !== false;
|
|
321
|
+
const apis = await page.evaluate(({ deepScan }) => {
|
|
321
322
|
const results = [];
|
|
322
323
|
const scripts = Array.from(document.querySelectorAll('script'));
|
|
323
324
|
const apiPatterns = [
|
|
324
325
|
/https?:\/\/[^"'\s]+\/api\/[^"'\s]*/gi,
|
|
325
326
|
/https?:\/\/api\.[^"'\s]+/gi,
|
|
326
327
|
/\/api\/v?\d*\/[^"'\s]*/gi,
|
|
328
|
+
/graphql/gi,
|
|
329
|
+
/rest\/v?\d*/gi,
|
|
327
330
|
];
|
|
328
331
|
scripts.forEach(script => {
|
|
329
332
|
const content = script.textContent || '';
|
|
@@ -337,8 +340,21 @@ export async function handleApiFinder(args) {
|
|
|
337
340
|
}
|
|
338
341
|
});
|
|
339
342
|
});
|
|
343
|
+
// Deep scan: also check HTML content for API references
|
|
344
|
+
if (deepScan) {
|
|
345
|
+
const htmlContent = document.body.innerHTML;
|
|
346
|
+
apiPatterns.forEach(pattern => {
|
|
347
|
+
const matches = htmlContent.match(pattern);
|
|
348
|
+
if (matches) {
|
|
349
|
+
matches.forEach(match => results.push({
|
|
350
|
+
url: match,
|
|
351
|
+
source: 'html_content',
|
|
352
|
+
}));
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
340
356
|
return [...new Set(results.map(r => r.url))].map(url => ({ url, source: 'script' }));
|
|
341
|
-
});
|
|
357
|
+
}, { deepScan });
|
|
342
358
|
return {
|
|
343
359
|
content: [{
|
|
344
360
|
type: 'text',
|
package/dist/tool-definitions.js
CHANGED
|
@@ -942,7 +942,7 @@ export const TOOLS = [
|
|
|
942
942
|
properties: {
|
|
943
943
|
url: { type: 'string' },
|
|
944
944
|
analyzeNetworkRequests: { type: 'boolean', default: true },
|
|
945
|
-
scanDuration: { type: 'number', default:
|
|
945
|
+
scanDuration: { type: 'number', default: 10000 },
|
|
946
946
|
},
|
|
947
947
|
},
|
|
948
948
|
},
|
|
@@ -1024,7 +1024,7 @@ export const TOOLS = [
|
|
|
1024
1024
|
inputSchema: {
|
|
1025
1025
|
type: 'object',
|
|
1026
1026
|
properties: {
|
|
1027
|
-
duration: { type: 'number', default:
|
|
1027
|
+
duration: { type: 'number', default: 15000 },
|
|
1028
1028
|
url: { type: 'string' },
|
|
1029
1029
|
},
|
|
1030
1030
|
},
|
|
@@ -1035,7 +1035,7 @@ export const TOOLS = [
|
|
|
1035
1035
|
inputSchema: {
|
|
1036
1036
|
type: 'object',
|
|
1037
1037
|
properties: {
|
|
1038
|
-
duration: { type: 'number', default:
|
|
1038
|
+
duration: { type: 'number', default: 15000 },
|
|
1039
1039
|
},
|
|
1040
1040
|
},
|
|
1041
1041
|
},
|
|
@@ -1045,8 +1045,8 @@ export const TOOLS = [
|
|
|
1045
1045
|
inputSchema: {
|
|
1046
1046
|
type: 'object',
|
|
1047
1047
|
properties: {
|
|
1048
|
-
duration: { type: 'number', default:
|
|
1049
|
-
filterTypes: { type: 'array', items: { type: 'string' }, default: ['
|
|
1048
|
+
duration: { type: 'number', default: 20000 },
|
|
1049
|
+
filterTypes: { type: 'array', items: { type: 'string' }, default: ['video', 'xhr', 'fetch', 'media'] },
|
|
1050
1050
|
},
|
|
1051
1051
|
},
|
|
1052
1052
|
},
|
|
@@ -1055,7 +1055,9 @@ export const TOOLS = [
|
|
|
1055
1055
|
description: 'Discover API endpoints on page',
|
|
1056
1056
|
inputSchema: {
|
|
1057
1057
|
type: 'object',
|
|
1058
|
-
properties: {
|
|
1058
|
+
properties: {
|
|
1059
|
+
deepScan: { type: 'boolean', default: true },
|
|
1060
|
+
},
|
|
1059
1061
|
},
|
|
1060
1062
|
},
|
|
1061
1063
|
{
|