brave-real-browser-mcp-server 2.11.4 → 2.11.6
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/browser-manager.js
CHANGED
|
@@ -3,6 +3,9 @@ import * as fs from 'fs';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as net from 'net';
|
|
5
5
|
import { execSync, spawn } from 'child_process';
|
|
6
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
7
|
+
// Load environment variables from .env file
|
|
8
|
+
dotenvConfig();
|
|
6
9
|
// Browser error categorization
|
|
7
10
|
export var BrowserErrorType;
|
|
8
11
|
(function (BrowserErrorType) {
|
|
@@ -461,7 +464,7 @@ export async function initializeBrowser(options) {
|
|
|
461
464
|
braveConfig.chromePath = detectedBravePath;
|
|
462
465
|
}
|
|
463
466
|
const connectOptions = {
|
|
464
|
-
headless: options?.headless ??
|
|
467
|
+
headless: options?.headless ?? (process.env.HEADLESS === 'true'),
|
|
465
468
|
customConfig: braveConfig,
|
|
466
469
|
turnstile: true,
|
|
467
470
|
disableXvfb: options?.disableXvfb ?? true,
|
|
@@ -512,7 +515,7 @@ export async function initializeBrowser(options) {
|
|
|
512
515
|
strategyName: 'User-Defined Configuration',
|
|
513
516
|
strategy: {
|
|
514
517
|
executablePath: detectedBravePath,
|
|
515
|
-
headless: options?.headless ??
|
|
518
|
+
headless: options?.headless ?? (process.env.HEADLESS === 'true'),
|
|
516
519
|
turnstile: true,
|
|
517
520
|
args: [
|
|
518
521
|
"--start-maximized"
|
|
@@ -15,13 +15,15 @@ export async function handleVideoLinkFinder(args) {
|
|
|
15
15
|
});
|
|
16
16
|
const page = getCurrentPage();
|
|
17
17
|
const includeEmbedded = args.includeEmbedded !== false;
|
|
18
|
-
const
|
|
18
|
+
const captureDuration = typeof args.captureDuration === 'number' ? args.captureDuration : 7000;
|
|
19
|
+
// 1) Collect DOM-based links quickly
|
|
20
|
+
const domLinks = await page.evaluate((includeEmbedded) => {
|
|
19
21
|
const results = [];
|
|
20
22
|
// Direct video links
|
|
21
23
|
const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv', '.m3u8', '.mpd'];
|
|
22
24
|
const allLinks = document.querySelectorAll('a[href]');
|
|
23
25
|
allLinks.forEach((link, idx) => {
|
|
24
|
-
const href = link.href.toLowerCase();
|
|
26
|
+
const href = (link.href || '').toLowerCase();
|
|
25
27
|
videoExtensions.forEach(ext => {
|
|
26
28
|
if (href.includes(ext)) {
|
|
27
29
|
results.push({
|
|
@@ -46,23 +48,64 @@ export async function handleVideoLinkFinder(args) {
|
|
|
46
48
|
});
|
|
47
49
|
}
|
|
48
50
|
});
|
|
49
|
-
// Embedded videos
|
|
51
|
+
// Embedded videos (iframes)
|
|
50
52
|
if (includeEmbedded) {
|
|
51
|
-
document.querySelectorAll('iframe
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
document.querySelectorAll('iframe').forEach((iframe, idx) => {
|
|
54
|
+
if (iframe.src) {
|
|
55
|
+
results.push({
|
|
56
|
+
index: idx,
|
|
57
|
+
url: iframe.src,
|
|
58
|
+
type: 'embedded_video',
|
|
59
|
+
title: iframe.title || '',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
58
62
|
});
|
|
59
63
|
}
|
|
60
64
|
return results;
|
|
61
65
|
}, includeEmbedded);
|
|
66
|
+
// 2) Network sniff for streaming links (.m3u8/.mpd/.ts/.vtt)
|
|
67
|
+
const streamingLinks = [];
|
|
68
|
+
const respHandler = (response) => {
|
|
69
|
+
try {
|
|
70
|
+
const url = response.url();
|
|
71
|
+
const ct = (response.headers()['content-type'] || '').toLowerCase();
|
|
72
|
+
const isStream = /\.m3u8(\?|$)|\.mpd(\?|$)|\.ts(\?|$)|\.vtt(\?|$)/i.test(url) ||
|
|
73
|
+
ct.includes('application/vnd.apple.mpegurl') || ct.includes('application/x-mpegurl');
|
|
74
|
+
if (isStream) {
|
|
75
|
+
streamingLinks.push({ url, contentType: ct, status: response.status() });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
};
|
|
80
|
+
page.on('response', respHandler);
|
|
81
|
+
// Try to "play" iframe/player so network requests fire
|
|
82
|
+
try {
|
|
83
|
+
const clickPoint = await page.evaluate(() => {
|
|
84
|
+
const iframe = document.querySelector('iframe');
|
|
85
|
+
if (!iframe)
|
|
86
|
+
return null;
|
|
87
|
+
const r = iframe.getBoundingClientRect();
|
|
88
|
+
return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
|
|
89
|
+
});
|
|
90
|
+
if (clickPoint && typeof clickPoint.x === 'number') {
|
|
91
|
+
await page.mouse.click(clickPoint.x, clickPoint.y, { clickCount: 1 });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch { }
|
|
95
|
+
await sleep(captureDuration);
|
|
96
|
+
page.off('response', respHandler);
|
|
97
|
+
// Dedupe by URL
|
|
98
|
+
const uniqueStreams = Array.from(new Map(streamingLinks.map(i => [i.url, i])).values());
|
|
99
|
+
const resultSummary = {
|
|
100
|
+
domLinksCount: domLinks.length,
|
|
101
|
+
networkStreamsCount: uniqueStreams.length,
|
|
102
|
+
domLinks,
|
|
103
|
+
streamingLinks: uniqueStreams,
|
|
104
|
+
};
|
|
62
105
|
return {
|
|
63
106
|
content: [{
|
|
64
107
|
type: 'text',
|
|
65
|
-
text: `✅
|
|
108
|
+
text: `✅ Video links (DOM + Network)\n\n${JSON.stringify(resultSummary, null, 2)}`,
|
|
66
109
|
}],
|
|
67
110
|
};
|
|
68
111
|
}, 'Failed to find video links');
|
|
@@ -402,13 +445,17 @@ export async function handleNetworkRecordingFinder(args) {
|
|
|
402
445
|
console.log(`[Network Recording] Processed ${totalResponses} responses, ${matchedResponses} matched`);
|
|
403
446
|
}
|
|
404
447
|
let shouldRecord = false;
|
|
405
|
-
|
|
448
|
+
const urlLower = url.toLowerCase();
|
|
449
|
+
const isStreamAsset = /\.m3u8(\?|$)|\.mpd(\?|$)|\.ts(\?|$)|\.vtt(\?|$)|\.mp4(\?|$)|\.webm(\?|$)/i.test(urlLower) ||
|
|
450
|
+
contentType.includes('application/vnd.apple.mpegurl') ||
|
|
451
|
+
contentType.includes('application/x-mpegurl');
|
|
452
|
+
if (filterType === 'video' && (contentType.includes('video') || resourceType === 'media' || isStreamAsset)) {
|
|
406
453
|
shouldRecord = true;
|
|
407
454
|
}
|
|
408
455
|
else if (filterType === 'audio' && contentType.includes('audio')) {
|
|
409
456
|
shouldRecord = true;
|
|
410
457
|
}
|
|
411
|
-
else if (filterType === 'media' && (contentType.includes('video') || contentType.includes('audio'))) {
|
|
458
|
+
else if (filterType === 'media' && (contentType.includes('video') || contentType.includes('audio') || isStreamAsset)) {
|
|
412
459
|
shouldRecord = true;
|
|
413
460
|
}
|
|
414
461
|
if (shouldRecord) {
|
|
@@ -611,6 +658,8 @@ export async function handleVideoLinksFinders(args) {
|
|
|
611
658
|
requirePage: true,
|
|
612
659
|
});
|
|
613
660
|
const page = getCurrentPage();
|
|
661
|
+
const captureDuration = typeof args.captureDuration === 'number' ? args.captureDuration : 7000;
|
|
662
|
+
// DOM discovery first
|
|
614
663
|
const videoLinks = await page.evaluate(() => {
|
|
615
664
|
const results = {
|
|
616
665
|
directLinks: [],
|
|
@@ -620,7 +669,7 @@ export async function handleVideoLinksFinders(args) {
|
|
|
620
669
|
};
|
|
621
670
|
// Direct video links
|
|
622
671
|
document.querySelectorAll('a[href]').forEach((link) => {
|
|
623
|
-
const href = link.href.toLowerCase();
|
|
672
|
+
const href = (link.href || '').toLowerCase();
|
|
624
673
|
if (href.includes('.mp4') || href.includes('.webm') || href.includes('.mov')) {
|
|
625
674
|
results.directLinks.push({
|
|
626
675
|
url: link.href,
|
|
@@ -630,25 +679,24 @@ export async function handleVideoLinksFinders(args) {
|
|
|
630
679
|
});
|
|
631
680
|
// Embedded iframes
|
|
632
681
|
document.querySelectorAll('iframe').forEach((iframe) => {
|
|
633
|
-
|
|
634
|
-
if (src.includes('youtube') || src.includes('vimeo') || src.includes('video')) {
|
|
682
|
+
if (iframe.src) {
|
|
635
683
|
results.embeddedLinks.push({
|
|
636
684
|
url: iframe.src,
|
|
637
685
|
title: iframe.title,
|
|
638
686
|
});
|
|
639
687
|
}
|
|
640
688
|
});
|
|
641
|
-
// Streaming manifests
|
|
689
|
+
// Streaming manifests present in inline scripts
|
|
642
690
|
const scripts = Array.from(document.querySelectorAll('script'));
|
|
643
691
|
scripts.forEach(script => {
|
|
644
692
|
const content = script.textContent || '';
|
|
645
693
|
const m3u8Match = content.match(/https?:\/\/[^\s"']+\.m3u8/g);
|
|
646
694
|
const mpdMatch = content.match(/https?:\/\/[^\s"']+\.mpd/g);
|
|
647
695
|
if (m3u8Match) {
|
|
648
|
-
m3u8Match.forEach(url => results.streamingLinks.push({ url, type: 'HLS' }));
|
|
696
|
+
m3u8Match.forEach(url => results.streamingLinks.push({ url, type: 'HLS', source: 'inline' }));
|
|
649
697
|
}
|
|
650
698
|
if (mpdMatch) {
|
|
651
|
-
mpdMatch.forEach(url => results.streamingLinks.push({ url, type: 'DASH' }));
|
|
699
|
+
mpdMatch.forEach(url => results.streamingLinks.push({ url, type: 'DASH', source: 'inline' }));
|
|
652
700
|
}
|
|
653
701
|
});
|
|
654
702
|
// Video player links
|
|
@@ -663,10 +711,46 @@ export async function handleVideoLinksFinders(args) {
|
|
|
663
711
|
});
|
|
664
712
|
return results;
|
|
665
713
|
});
|
|
714
|
+
// Network enrichment (m3u8/mpd/ts/vtt)
|
|
715
|
+
const networkStreams = [];
|
|
716
|
+
const respHandler = (response) => {
|
|
717
|
+
try {
|
|
718
|
+
const url = response.url();
|
|
719
|
+
const ct = (response.headers()['content-type'] || '').toLowerCase();
|
|
720
|
+
if (/\.m3u8(\?|$)|\.mpd(\?|$)|\.ts(\?|$)|\.vtt(\?|$)/i.test(url) ||
|
|
721
|
+
ct.includes('application/vnd.apple.mpegurl') || ct.includes('application/x-mpegurl')) {
|
|
722
|
+
const type = url.includes('.mpd') ? 'DASH' : url.includes('.m3u8') ? 'HLS' : 'segment';
|
|
723
|
+
networkStreams.push({ url, type, contentType: ct, status: response.status(), source: 'network' });
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
catch { }
|
|
727
|
+
};
|
|
728
|
+
page.on('response', respHandler);
|
|
729
|
+
// Nudge the player by clicking the visible iframe center (if any)
|
|
730
|
+
try {
|
|
731
|
+
const clickPoint = await page.evaluate(() => {
|
|
732
|
+
const iframe = document.querySelector('iframe');
|
|
733
|
+
if (!iframe)
|
|
734
|
+
return null;
|
|
735
|
+
const r = iframe.getBoundingClientRect();
|
|
736
|
+
return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
|
|
737
|
+
});
|
|
738
|
+
if (clickPoint && typeof clickPoint.x === 'number') {
|
|
739
|
+
await page.mouse.click(clickPoint.x, clickPoint.y, { clickCount: 1 });
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
catch { }
|
|
743
|
+
await sleep(captureDuration);
|
|
744
|
+
page.off('response', respHandler);
|
|
745
|
+
// Merge + dedupe
|
|
746
|
+
const merged = {
|
|
747
|
+
...videoLinks,
|
|
748
|
+
streamingLinks: Array.from(new Map([...videoLinks.streamingLinks, ...networkStreams].map((i) => [i.url, i])).values()),
|
|
749
|
+
};
|
|
666
750
|
return {
|
|
667
751
|
content: [{
|
|
668
752
|
type: 'text',
|
|
669
|
-
text: `✅ Video Links Found\n\n${JSON.stringify(
|
|
753
|
+
text: `✅ Video Links Found\n\n${JSON.stringify(merged, null, 2)}`,
|
|
670
754
|
}],
|
|
671
755
|
};
|
|
672
756
|
}, 'Failed to find video links');
|
|
@@ -317,8 +317,9 @@ export async function handleApiFinder(args) {
|
|
|
317
317
|
requirePage: true,
|
|
318
318
|
});
|
|
319
319
|
const page = getCurrentPage();
|
|
320
|
-
const
|
|
321
|
-
|
|
320
|
+
const captureDuration = typeof args.duration === 'number' ? args.duration : 8000;
|
|
321
|
+
// From inline scripts
|
|
322
|
+
const scriptApis = await page.evaluate(() => {
|
|
322
323
|
const results = [];
|
|
323
324
|
const scripts = Array.from(document.querySelectorAll('script'));
|
|
324
325
|
const apiPatterns = [
|
|
@@ -333,32 +334,35 @@ export async function handleApiFinder(args) {
|
|
|
333
334
|
apiPatterns.forEach(pattern => {
|
|
334
335
|
const matches = content.match(pattern);
|
|
335
336
|
if (matches) {
|
|
336
|
-
matches.forEach(match => results.push({
|
|
337
|
-
url: match,
|
|
338
|
-
source: 'script',
|
|
339
|
-
}));
|
|
337
|
+
matches.forEach(match => results.push({ url: match, source: 'script' }));
|
|
340
338
|
}
|
|
341
339
|
});
|
|
342
340
|
});
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
341
|
+
return results;
|
|
342
|
+
});
|
|
343
|
+
// From network (XHR/fetch)
|
|
344
|
+
const networkApis = [];
|
|
345
|
+
const respHandler = async (response) => {
|
|
346
|
+
try {
|
|
347
|
+
const req = response.request();
|
|
348
|
+
const rt = req.resourceType();
|
|
349
|
+
const url = response.url();
|
|
350
|
+
const ct = (response.headers()['content-type'] || '').toLowerCase();
|
|
351
|
+
if ((rt === 'xhr' || rt === 'fetch') && (ct.includes('json') || /\/api\//.test(url))) {
|
|
352
|
+
networkApis.push({ url, status: response.status(), method: req.method(), source: 'network' });
|
|
353
|
+
}
|
|
355
354
|
}
|
|
356
|
-
|
|
357
|
-
}
|
|
355
|
+
catch { }
|
|
356
|
+
};
|
|
357
|
+
page.on('response', respHandler);
|
|
358
|
+
await sleep(captureDuration);
|
|
359
|
+
page.off('response', respHandler);
|
|
360
|
+
const all = [...scriptApis, ...networkApis];
|
|
361
|
+
const dedup = Array.from(new Map(all.map(i => [i.url, i])).values());
|
|
358
362
|
return {
|
|
359
363
|
content: [{
|
|
360
364
|
type: 'text',
|
|
361
|
-
text: `✅ Found ${
|
|
365
|
+
text: `✅ Found ${dedup.length} API endpoints\n\n${JSON.stringify(dedup, null, 2)}`,
|
|
362
366
|
}],
|
|
363
367
|
};
|
|
364
368
|
}, 'Failed to find APIs');
|
|
@@ -518,6 +522,8 @@ export async function handleVideoSourceExtractor(args) {
|
|
|
518
522
|
requirePage: true,
|
|
519
523
|
});
|
|
520
524
|
const page = getCurrentPage();
|
|
525
|
+
const captureDuration = typeof args.captureDuration === 'number' ? args.captureDuration : 6000;
|
|
526
|
+
// DOM video elements
|
|
521
527
|
const videos = await page.evaluate(() => {
|
|
522
528
|
const videoElements = document.querySelectorAll('video');
|
|
523
529
|
const results = [];
|
|
@@ -545,10 +551,44 @@ export async function handleVideoSourceExtractor(args) {
|
|
|
545
551
|
});
|
|
546
552
|
return results;
|
|
547
553
|
});
|
|
554
|
+
// Network capture for manifests and segments
|
|
555
|
+
const manifests = [];
|
|
556
|
+
const segments = [];
|
|
557
|
+
const respHandler = async (response) => {
|
|
558
|
+
try {
|
|
559
|
+
const url = response.url();
|
|
560
|
+
const ct = (response.headers()['content-type'] || '').toLowerCase();
|
|
561
|
+
if (/\.m3u8(\?|$)|\.mpd(\?|$)/i.test(url) || ct.includes('application/vnd.apple.mpegurl') || ct.includes('application/x-mpegurl')) {
|
|
562
|
+
const content = await response.text().catch(() => '');
|
|
563
|
+
manifests.push({ url, type: url.includes('.mpd') ? 'DASH' : 'HLS', status: response.status(), content: content.slice(0, 2000) });
|
|
564
|
+
}
|
|
565
|
+
else if (/\.ts(\?|$)|\.m4s(\?|$)|\.mp4(\?|$)/i.test(url)) {
|
|
566
|
+
segments.push({ url, status: response.status(), size: response.headers()['content-length'] });
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
catch { }
|
|
570
|
+
};
|
|
571
|
+
page.on('response', respHandler);
|
|
572
|
+
// Best-effort: click center of first iframe to trigger playback
|
|
573
|
+
try {
|
|
574
|
+
const pt = await page.evaluate(() => {
|
|
575
|
+
const ifr = document.querySelector('iframe');
|
|
576
|
+
if (!ifr)
|
|
577
|
+
return null;
|
|
578
|
+
const r = ifr.getBoundingClientRect();
|
|
579
|
+
return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
|
|
580
|
+
});
|
|
581
|
+
if (pt)
|
|
582
|
+
await page.mouse.click(pt.x, pt.y);
|
|
583
|
+
}
|
|
584
|
+
catch { }
|
|
585
|
+
await sleep(captureDuration);
|
|
586
|
+
page.off('response', respHandler);
|
|
587
|
+
const result = { videos, manifests, segments };
|
|
548
588
|
return {
|
|
549
589
|
content: [{
|
|
550
590
|
type: 'text',
|
|
551
|
-
text: `✅ Extracted
|
|
591
|
+
text: `✅ Extracted video sources\n\n${JSON.stringify(result, null, 2)}`,
|
|
552
592
|
}],
|
|
553
593
|
};
|
|
554
594
|
}, 'Failed to extract video sources');
|
|
@@ -655,6 +695,7 @@ export async function handleOriginalVideoHosterFinder(args) {
|
|
|
655
695
|
requirePage: true,
|
|
656
696
|
});
|
|
657
697
|
const page = getCurrentPage();
|
|
698
|
+
const captureDuration = typeof args.captureDuration === 'number' ? args.captureDuration : 6000;
|
|
658
699
|
const videoData = await page.evaluate(() => {
|
|
659
700
|
const results = {
|
|
660
701
|
directVideos: [],
|
|
@@ -665,33 +706,58 @@ export async function handleOriginalVideoHosterFinder(args) {
|
|
|
665
706
|
document.querySelectorAll('video').forEach((video) => {
|
|
666
707
|
const src = video.src || video.currentSrc;
|
|
667
708
|
if (src) {
|
|
668
|
-
results.directVideos.push({
|
|
669
|
-
src,
|
|
670
|
-
type: 'direct',
|
|
671
|
-
poster: video.poster,
|
|
672
|
-
});
|
|
709
|
+
results.directVideos.push({ src, type: 'direct', poster: video.poster });
|
|
673
710
|
}
|
|
674
711
|
video.querySelectorAll('source').forEach((source) => {
|
|
675
|
-
|
|
676
|
-
src: source.src,
|
|
677
|
-
|
|
678
|
-
quality: source.dataset.quality || 'unknown',
|
|
679
|
-
});
|
|
712
|
+
if (source.src) {
|
|
713
|
+
results.directVideos.push({ src: source.src, type: source.type, quality: source.dataset.quality || 'unknown' });
|
|
714
|
+
}
|
|
680
715
|
});
|
|
681
716
|
});
|
|
682
717
|
// Iframe videos
|
|
683
|
-
document.querySelectorAll('iframe
|
|
684
|
-
|
|
685
|
-
src: iframe.src,
|
|
686
|
-
|
|
687
|
-
});
|
|
718
|
+
document.querySelectorAll('iframe').forEach((iframe) => {
|
|
719
|
+
if (iframe.src) {
|
|
720
|
+
results.iframeVideos.push({ src: iframe.src, type: 'iframe' });
|
|
721
|
+
}
|
|
688
722
|
});
|
|
689
723
|
return results;
|
|
690
724
|
});
|
|
725
|
+
// Network-derived hosts (m3u8/mpd)
|
|
726
|
+
const hosts = new Set();
|
|
727
|
+
const respHandler = (response) => {
|
|
728
|
+
try {
|
|
729
|
+
const url = response.url();
|
|
730
|
+
if (/\.m3u8(\?|$)|\.mpd(\?|$)/i.test(url)) {
|
|
731
|
+
try {
|
|
732
|
+
hosts.add(new URL(url).hostname);
|
|
733
|
+
}
|
|
734
|
+
catch { }
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
catch { }
|
|
738
|
+
};
|
|
739
|
+
page.on('response', respHandler);
|
|
740
|
+
// Kick the player once
|
|
741
|
+
try {
|
|
742
|
+
const pt = await page.evaluate(() => {
|
|
743
|
+
const ifr = document.querySelector('iframe');
|
|
744
|
+
if (!ifr)
|
|
745
|
+
return null;
|
|
746
|
+
const r = ifr.getBoundingClientRect();
|
|
747
|
+
return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
|
|
748
|
+
});
|
|
749
|
+
if (pt)
|
|
750
|
+
await page.mouse.click(pt.x, pt.y);
|
|
751
|
+
}
|
|
752
|
+
catch { }
|
|
753
|
+
await sleep(captureDuration);
|
|
754
|
+
page.off('response', respHandler);
|
|
755
|
+
const possibleSources = Array.from(hosts).map(h => ({ host: h }));
|
|
756
|
+
const enriched = { ...videoData, possibleSources };
|
|
691
757
|
return {
|
|
692
758
|
content: [{
|
|
693
759
|
type: 'text',
|
|
694
|
-
text: `✅ Video sources found\n\n${JSON.stringify(
|
|
760
|
+
text: `✅ Video sources found\n\n${JSON.stringify(enriched, null, 2)}`,
|
|
695
761
|
}],
|
|
696
762
|
};
|
|
697
763
|
}, 'Failed to find original video hoster');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.6",
|
|
4
4
|
"description": "MCP server for brave-real-browser",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"cheerio": "^1.0.0-rc.12",
|
|
42
42
|
"chrono-node": "^2.7.0",
|
|
43
43
|
"compromise": "^14.13.0",
|
|
44
|
+
"dotenv": "^17.2.3",
|
|
44
45
|
"franc": "^6.2.0",
|
|
45
46
|
"libphonenumber-js": "^1.10.51",
|
|
46
47
|
"natural": "^6.12.0",
|