brave-real-browser-mcp-server 2.9.8 → 2.9.9

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.
@@ -907,3 +907,10 @@ export function getContentPriorityConfig() {
907
907
  export function updateContentPriorityConfig(config) {
908
908
  contentPriorityConfig = { ...contentPriorityConfig, ...config };
909
909
  }
910
+ // Additional convenience exports for video extraction modules
911
+ export function getBrowser() {
912
+ return browserInstance;
913
+ }
914
+ export function getPage() {
915
+ return pageInstance;
916
+ }
@@ -0,0 +1,373 @@
1
+ // Video extraction handlers for MCP tools
2
+ import { ExtractionStrategy } from '../video-extraction/types.js';
3
+ import { htmlElementsFinder, videoSelectors } from '../video-extraction/dom-extractors.js';
4
+ import { networkRecorder, requestChainTracer } from '../video-extraction/network-extractors.js';
5
+ import { validateWorkflow, recordExecution } from '../workflow-validation.js';
6
+ import { withErrorHandling } from '../system-utils.js';
7
+ import { getPage } from '../browser-manager.js';
8
+ // Wrapper function for workflow validation
9
+ async function withVideoExtractionWorkflowValidation(toolName, operation) {
10
+ // Validate workflow state
11
+ const workflowResult = validateWorkflow(toolName);
12
+ if (!workflowResult.isValid) {
13
+ throw new Error(`Workflow validation failed for ${toolName}: ${workflowResult.errorMessage}`);
14
+ }
15
+ try {
16
+ const result = await operation();
17
+ recordExecution(toolName, {}, true);
18
+ return result;
19
+ }
20
+ catch (error) {
21
+ recordExecution(toolName, {}, false, error instanceof Error ? error.message : String(error));
22
+ throw error;
23
+ }
24
+ }
25
+ // HTML Elements Video Extractor
26
+ export async function handleHtmlElementsExtraction(args) {
27
+ return withVideoExtractionWorkflowValidation('html_elements_extraction', async () => {
28
+ return withErrorHandling(async () => {
29
+ const startTime = Date.now();
30
+ const allSources = [];
31
+ const errors = [];
32
+ const warnings = [];
33
+ try {
34
+ // Initialize HTML elements finder
35
+ await htmlElementsFinder.initialize();
36
+ // Extract from video tags
37
+ if (args.extractVideoTags !== false) {
38
+ try {
39
+ const videoTagSources = await htmlElementsFinder.findVideoTags();
40
+ allSources.push(...videoTagSources);
41
+ }
42
+ catch (error) {
43
+ errors.push({
44
+ strategy: ExtractionStrategy.DOM_ANALYSIS,
45
+ message: 'Failed to extract video tags',
46
+ details: error,
47
+ isFatal: false
48
+ });
49
+ }
50
+ }
51
+ // Extract from iframes
52
+ if (args.extractIframes !== false) {
53
+ try {
54
+ const iframeSources = await htmlElementsFinder.findIframeSources();
55
+ allSources.push(...iframeSources);
56
+ }
57
+ catch (error) {
58
+ errors.push({
59
+ strategy: ExtractionStrategy.DOM_ANALYSIS,
60
+ message: 'Failed to extract iframe sources',
61
+ details: error,
62
+ isFatal: false
63
+ });
64
+ }
65
+ }
66
+ // Extract from embeds
67
+ if (args.extractEmbeds !== false) {
68
+ try {
69
+ const embedSources = await htmlElementsFinder.findEmbedSources();
70
+ allSources.push(...embedSources);
71
+ }
72
+ catch (error) {
73
+ errors.push({
74
+ strategy: ExtractionStrategy.DOM_ANALYSIS,
75
+ message: 'Failed to extract embed sources',
76
+ details: error,
77
+ isFatal: false
78
+ });
79
+ }
80
+ }
81
+ // Extract from meta tags
82
+ if (args.extractMetaTags !== false) {
83
+ try {
84
+ const metaSources = await htmlElementsFinder.findMetaVideoSources();
85
+ allSources.push(...metaSources);
86
+ }
87
+ catch (error) {
88
+ errors.push({
89
+ strategy: ExtractionStrategy.DOM_ANALYSIS,
90
+ message: 'Failed to extract meta tag sources',
91
+ details: error,
92
+ isFatal: false
93
+ });
94
+ }
95
+ }
96
+ // Extract from data attributes
97
+ if (args.extractDataAttributes !== false) {
98
+ try {
99
+ const dataSources = await htmlElementsFinder.findDataAttributesSources();
100
+ allSources.push(...dataSources);
101
+ }
102
+ catch (error) {
103
+ errors.push({
104
+ strategy: ExtractionStrategy.DOM_ANALYSIS,
105
+ message: 'Failed to extract data attribute sources',
106
+ details: error,
107
+ isFatal: false
108
+ });
109
+ }
110
+ }
111
+ // Get current page info for metadata
112
+ const page = getPage();
113
+ const pageUrl = page ? await page.url() : 'unknown';
114
+ const pageTitle = page ? await page.title() : undefined;
115
+ // Deduplicate sources
116
+ const uniqueSources = deduplicateSources(allSources);
117
+ const result = {
118
+ sources: uniqueSources,
119
+ metadata: {
120
+ title: pageTitle,
121
+ extractionTime: Date.now() - startTime,
122
+ strategiesUsed: [ExtractionStrategy.DOM_ANALYSIS],
123
+ hostersFound: [...new Set(uniqueSources.map(s => s.hoster).filter(Boolean))]
124
+ },
125
+ errors,
126
+ warnings
127
+ };
128
+ return {
129
+ content: result,
130
+ isError: false
131
+ };
132
+ }
133
+ catch (error) {
134
+ throw new Error(`HTML elements extraction failed: ${error instanceof Error ? error.message : String(error)}`);
135
+ }
136
+ }, 'handleHtmlElementsExtraction');
137
+ });
138
+ }
139
+ // Network Recording Video Extractor
140
+ export async function handleNetworkVideoExtraction(args) {
141
+ return withVideoExtractionWorkflowValidation('network_video_extraction', async () => {
142
+ return withErrorHandling(async () => {
143
+ const startTime = Date.now();
144
+ const errors = [];
145
+ const warnings = [];
146
+ try {
147
+ // Start network recording
148
+ await networkRecorder.startRecording({
149
+ interceptRequests: args.interceptRequests || false,
150
+ captureResponses: args.captureResponses || true,
151
+ filterResourceTypes: args.filterResourceTypes,
152
+ maxRecords: args.maxRecords || 1000
153
+ });
154
+ // Record for specified duration or default 10 seconds
155
+ const duration = args.recordingDuration || 10000;
156
+ await new Promise(resolve => setTimeout(resolve, duration));
157
+ // Stop recording and get video sources
158
+ const requests = await networkRecorder.stopRecording();
159
+ const videoSources = await networkRecorder.getVideoRequests();
160
+ // Get API endpoints that might contain video data
161
+ const apiEndpoints = await networkRecorder.getAPIEndpoints();
162
+ if (apiEndpoints.length > 0) {
163
+ warnings.push(`Found ${apiEndpoints.length} API endpoints that might contain video data`);
164
+ }
165
+ // Extract tokens for authentication
166
+ const tokens = await networkRecorder.extractTokensFromRequests();
167
+ if (tokens.authTokens.length > 0) {
168
+ warnings.push(`Found ${tokens.authTokens.length} authentication tokens`);
169
+ }
170
+ // Get current page info for metadata
171
+ const page = getPage();
172
+ const pageUrl = page ? await page.url() : 'unknown';
173
+ const pageTitle = page ? await page.title() : undefined;
174
+ const result = {
175
+ sources: videoSources,
176
+ metadata: {
177
+ title: pageTitle,
178
+ extractionTime: Date.now() - startTime,
179
+ strategiesUsed: [ExtractionStrategy.NETWORK_RECORDING],
180
+ hostersFound: [...new Set(videoSources.map(s => s.hoster).filter(Boolean))]
181
+ },
182
+ errors,
183
+ warnings
184
+ };
185
+ return {
186
+ content: result,
187
+ isError: false
188
+ };
189
+ }
190
+ catch (error) {
191
+ throw new Error(`Network video extraction failed: ${error instanceof Error ? error.message : String(error)}`);
192
+ }
193
+ }, 'handleNetworkVideoExtraction');
194
+ });
195
+ }
196
+ // Video Selectors Generator
197
+ export async function handleVideoSelectorGeneration(args) {
198
+ return withVideoExtractionWorkflowValidation('video_selector_generation', async () => {
199
+ return withErrorHandling(async () => {
200
+ try {
201
+ await videoSelectors.initialize();
202
+ const selectors = await videoSelectors.generateVideoSelectors();
203
+ return {
204
+ content: selectors,
205
+ isError: false
206
+ };
207
+ }
208
+ catch (error) {
209
+ throw new Error(`Video selector generation failed: ${error instanceof Error ? error.message : String(error)}`);
210
+ }
211
+ }, 'handleVideoSelectorGeneration');
212
+ });
213
+ }
214
+ // Comprehensive Video Extraction (combines all strategies)
215
+ export async function handleComprehensiveVideoExtraction(args) {
216
+ return withVideoExtractionWorkflowValidation('comprehensive_video_extraction', async () => {
217
+ return withErrorHandling(async () => {
218
+ const startTime = Date.now();
219
+ const allSources = [];
220
+ const errors = [];
221
+ const warnings = [];
222
+ const strategiesUsed = [];
223
+ try {
224
+ // DOM Analysis
225
+ if (args.enableDOMAnalysis !== false) {
226
+ try {
227
+ const domResult = await handleHtmlElementsExtraction({
228
+ extractVideoTags: true,
229
+ extractIframes: true,
230
+ extractEmbeds: true,
231
+ extractMetaTags: true,
232
+ extractDataAttributes: true
233
+ });
234
+ allSources.push(...domResult.content.sources);
235
+ errors.push(...domResult.content.errors);
236
+ warnings.push(...domResult.content.warnings);
237
+ strategiesUsed.push(ExtractionStrategy.DOM_ANALYSIS);
238
+ }
239
+ catch (error) {
240
+ errors.push({
241
+ strategy: ExtractionStrategy.DOM_ANALYSIS,
242
+ message: 'DOM analysis failed',
243
+ details: error,
244
+ isFatal: false
245
+ });
246
+ }
247
+ }
248
+ // Network Recording
249
+ if (args.enableNetworkRecording !== false) {
250
+ try {
251
+ const networkResult = await handleNetworkVideoExtraction({
252
+ interceptRequests: false,
253
+ captureResponses: true,
254
+ recordingDuration: args.recordingDuration || 10000
255
+ });
256
+ allSources.push(...networkResult.content.sources);
257
+ errors.push(...networkResult.content.errors);
258
+ warnings.push(...networkResult.content.warnings);
259
+ strategiesUsed.push(ExtractionStrategy.NETWORK_RECORDING);
260
+ }
261
+ catch (error) {
262
+ errors.push({
263
+ strategy: ExtractionStrategy.NETWORK_RECORDING,
264
+ message: 'Network recording failed',
265
+ details: error,
266
+ isFatal: false
267
+ });
268
+ }
269
+ }
270
+ // Filter by formats if specified
271
+ let filteredSources = allSources;
272
+ if (args.filterFormats && args.filterFormats.length > 0) {
273
+ filteredSources = allSources.filter(source => args.filterFormats.includes(source.format));
274
+ if (filteredSources.length < allSources.length) {
275
+ warnings.push(`Filtered ${allSources.length - filteredSources.length} sources by format`);
276
+ }
277
+ }
278
+ // Deduplicate and limit sources
279
+ let uniqueSources = deduplicateSources(filteredSources);
280
+ if (args.maxSources && uniqueSources.length > args.maxSources) {
281
+ // Sort by confidence and keep top sources
282
+ uniqueSources = uniqueSources
283
+ .sort((a, b) => b.confidence - a.confidence)
284
+ .slice(0, args.maxSources);
285
+ warnings.push(`Limited results to top ${args.maxSources} sources by confidence`);
286
+ }
287
+ // Get current page info for metadata
288
+ const page = getPage();
289
+ const pageUrl = page ? await page.url() : 'unknown';
290
+ const pageTitle = page ? await page.title() : undefined;
291
+ const result = {
292
+ sources: uniqueSources,
293
+ metadata: {
294
+ title: pageTitle,
295
+ extractionTime: Date.now() - startTime,
296
+ strategiesUsed,
297
+ hostersFound: [...new Set(uniqueSources.map(s => s.hoster).filter(Boolean))]
298
+ },
299
+ errors,
300
+ warnings
301
+ };
302
+ return {
303
+ content: result,
304
+ isError: false
305
+ };
306
+ }
307
+ catch (error) {
308
+ throw new Error(`Comprehensive video extraction failed: ${error instanceof Error ? error.message : String(error)}`);
309
+ }
310
+ }, 'handleComprehensiveVideoExtraction');
311
+ });
312
+ }
313
+ // URL Redirect Tracer
314
+ export async function handleURLRedirectTrace(args) {
315
+ return withVideoExtractionWorkflowValidation('url_redirect_trace', async () => {
316
+ return withErrorHandling(async () => {
317
+ try {
318
+ // Start recording to capture redirects
319
+ await networkRecorder.startRecording({
320
+ interceptRequests: false,
321
+ captureResponses: true
322
+ });
323
+ // Record for specified duration
324
+ const duration = args.recordingDuration || 5000;
325
+ await new Promise(resolve => setTimeout(resolve, duration));
326
+ // Stop recording
327
+ await networkRecorder.stopRecording();
328
+ // Get redirect information
329
+ const finalUrls = await requestChainTracer.getFinalUrls();
330
+ let redirectChains = {};
331
+ if (args.targetUrl) {
332
+ const chainForTarget = await requestChainTracer.traceRedirects(args.targetUrl);
333
+ redirectChains[args.targetUrl] = chainForTarget;
334
+ }
335
+ else {
336
+ // Get redirect chains for all URLs
337
+ for (const [originalUrl] of Object.entries(finalUrls)) {
338
+ const chain = await requestChainTracer.traceRedirects(originalUrl);
339
+ if (chain.length > 1) {
340
+ redirectChains[originalUrl] = chain;
341
+ }
342
+ }
343
+ }
344
+ return {
345
+ content: {
346
+ redirectChains,
347
+ finalUrls
348
+ },
349
+ isError: false
350
+ };
351
+ }
352
+ catch (error) {
353
+ throw new Error(`URL redirect trace failed: ${error instanceof Error ? error.message : String(error)}`);
354
+ }
355
+ }, 'handleURLRedirectTrace');
356
+ });
357
+ }
358
+ // Helper function to deduplicate video sources
359
+ function deduplicateSources(sources) {
360
+ const seen = new Set();
361
+ const unique = [];
362
+ for (const source of sources) {
363
+ // Create a key based on URL and format
364
+ const key = `${source.url}|${source.format}`;
365
+ if (!seen.has(key)) {
366
+ seen.add(key);
367
+ unique.push(source);
368
+ }
369
+ }
370
+ return unique;
371
+ }
372
+ // Export all handlers
373
+ export { withVideoExtractionWorkflowValidation };
package/dist/index.js CHANGED
@@ -43,6 +43,8 @@ import { handleOCREngine, handleAudioCaptchaSolver, handlePuzzleCaptchaHandler }
43
43
  import { handleFullPageScreenshot, handleElementScreenshot, handlePDFGeneration, handleVideoRecording, handleVisualComparison } from './handlers/visual-tools-handlers.js';
44
44
  // Import API integration handlers
45
45
  import { handleRESTAPIEndpointFinder, handleWebhookSupport, handleAllWebsiteAPIFinder } from './handlers/api-integration-handlers.js';
46
+ // Import video extraction handlers
47
+ import { handleHtmlElementsExtraction, handleNetworkVideoExtraction, handleVideoSelectorGeneration, handleComprehensiveVideoExtraction, handleURLRedirectTrace } from './handlers/video-extraction-handlers.js';
46
48
  console.error('🔍 [DEBUG] All modules loaded successfully');
47
49
  console.error(`🔍 [DEBUG] Server info: ${JSON.stringify(SERVER_INFO)}`);
48
50
  console.error(`🔍 [DEBUG] Available tools: ${TOOLS.length} tools loaded`);
@@ -233,6 +235,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
233
235
  return await handleWebhookSupport(args);
234
236
  case TOOL_NAMES.ALL_WEBSITE_API_FINDER:
235
237
  return await handleAllWebsiteAPIFinder(args);
238
+ // Video Extraction Tools
239
+ case TOOL_NAMES.HTML_ELEMENTS_EXTRACTION:
240
+ return await handleHtmlElementsExtraction(args);
241
+ case TOOL_NAMES.NETWORK_VIDEO_EXTRACTION:
242
+ return await handleNetworkVideoExtraction(args);
243
+ case TOOL_NAMES.VIDEO_SELECTOR_GENERATION:
244
+ return await handleVideoSelectorGeneration(args);
245
+ case TOOL_NAMES.COMPREHENSIVE_VIDEO_EXTRACTION:
246
+ return await handleComprehensiveVideoExtraction(args);
247
+ case TOOL_NAMES.URL_REDIRECT_TRACE:
248
+ return await handleURLRedirectTrace(args);
236
249
  default:
237
250
  throw new Error(`Unknown tool: ${name}`);
238
251
  }
@@ -581,6 +581,138 @@ export const TOOLS = [
581
581
  required: ['text'],
582
582
  },
583
583
  },
584
+ // Video Extraction Tools
585
+ {
586
+ name: 'html_elements_extraction',
587
+ description: 'Extract video sources from HTML elements (video tags, iframes, embeds, meta tags, data attributes)',
588
+ inputSchema: {
589
+ type: 'object',
590
+ properties: {
591
+ extractVideoTags: {
592
+ type: 'boolean',
593
+ description: 'Extract sources from <video> and <source> tags',
594
+ default: true
595
+ },
596
+ extractIframes: {
597
+ type: 'boolean',
598
+ description: 'Extract sources from <iframe> elements',
599
+ default: true
600
+ },
601
+ extractEmbeds: {
602
+ type: 'boolean',
603
+ description: 'Extract sources from <embed> and <object> elements',
604
+ default: true
605
+ },
606
+ extractMetaTags: {
607
+ type: 'boolean',
608
+ description: 'Extract sources from Open Graph and Twitter Card meta tags',
609
+ default: true
610
+ },
611
+ extractDataAttributes: {
612
+ type: 'boolean',
613
+ description: 'Extract sources from data-* attributes',
614
+ default: true
615
+ }
616
+ }
617
+ }
618
+ },
619
+ {
620
+ name: 'network_video_extraction',
621
+ description: 'Extract video sources from network requests and responses',
622
+ inputSchema: {
623
+ type: 'object',
624
+ properties: {
625
+ interceptRequests: {
626
+ type: 'boolean',
627
+ description: 'Intercept and analyze network requests',
628
+ default: false
629
+ },
630
+ captureResponses: {
631
+ type: 'boolean',
632
+ description: 'Capture and analyze network responses',
633
+ default: true
634
+ },
635
+ filterResourceTypes: {
636
+ type: 'array',
637
+ description: 'Filter by resource types (xhr, fetch, media, etc.)',
638
+ items: { type: 'string' }
639
+ },
640
+ maxRecords: {
641
+ type: 'number',
642
+ description: 'Maximum number of network requests to record',
643
+ default: 1000
644
+ },
645
+ recordingDuration: {
646
+ type: 'number',
647
+ description: 'Duration to record network traffic (ms)',
648
+ default: 10000
649
+ }
650
+ }
651
+ }
652
+ },
653
+ {
654
+ name: 'video_selector_generation',
655
+ description: 'Generate CSS selectors for video-related elements on the page',
656
+ inputSchema: {
657
+ type: 'object',
658
+ properties: {}
659
+ }
660
+ },
661
+ {
662
+ name: 'comprehensive_video_extraction',
663
+ description: 'Extract video sources using all available strategies (DOM + Network)',
664
+ inputSchema: {
665
+ type: 'object',
666
+ properties: {
667
+ enableDOMAnalysis: {
668
+ type: 'boolean',
669
+ description: 'Enable DOM-based video extraction',
670
+ default: true
671
+ },
672
+ enableNetworkRecording: {
673
+ type: 'boolean',
674
+ description: 'Enable network-based video extraction',
675
+ default: true
676
+ },
677
+ recordingDuration: {
678
+ type: 'number',
679
+ description: 'Duration to record network traffic (ms)',
680
+ default: 10000
681
+ },
682
+ maxSources: {
683
+ type: 'number',
684
+ description: 'Maximum number of video sources to return',
685
+ default: 50
686
+ },
687
+ filterFormats: {
688
+ type: 'array',
689
+ description: 'Filter results by video format',
690
+ items: {
691
+ type: 'string',
692
+ enum: ['mp4', 'webm', 'avi', 'mkv', 'hls', 'dash', 'ts', 'flv', 'blob']
693
+ }
694
+ }
695
+ }
696
+ }
697
+ },
698
+ {
699
+ name: 'url_redirect_trace',
700
+ description: 'Trace URL redirects and get final destinations',
701
+ inputSchema: {
702
+ type: 'object',
703
+ properties: {
704
+ targetUrl: {
705
+ type: 'string',
706
+ description: 'Specific URL to trace redirects for'
707
+ },
708
+ recordingDuration: {
709
+ type: 'number',
710
+ description: 'Duration to record network traffic (ms)',
711
+ default: 5000
712
+ }
713
+ }
714
+ }
715
+ },
584
716
  // Data Validation Tools
585
717
  {
586
718
  name: 'schema_validator',
@@ -1051,6 +1183,12 @@ export const TOOL_NAMES = {
1051
1183
  REST_API_ENDPOINT_FINDER: 'rest_api_endpoint_finder',
1052
1184
  WEBHOOK_SUPPORT: 'webhook_support',
1053
1185
  ALL_WEBSITE_API_FINDER: 'all_website_api_finder',
1186
+ // Video Extraction Tools
1187
+ HTML_ELEMENTS_EXTRACTION: 'html_elements_extraction',
1188
+ NETWORK_VIDEO_EXTRACTION: 'network_video_extraction',
1189
+ VIDEO_SELECTOR_GENERATION: 'video_selector_generation',
1190
+ COMPREHENSIVE_VIDEO_EXTRACTION: 'comprehensive_video_extraction',
1191
+ URL_REDIRECT_TRACE: 'url_redirect_trace',
1054
1192
  };
1055
1193
  // Tool categories for organization
1056
1194
  export const TOOL_CATEGORIES = {