brave-real-browser-mcp-server 2.11.1 → 2.11.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.
@@ -0,0 +1,479 @@
1
+ // Optimization Utilities for All MCP Tools
2
+ // @ts-nocheck
3
+ import { sleep } from './system-utils.js';
4
+ /**
5
+ * Tool Optimization Configuration
6
+ * Centralized defaults for all 113 tools
7
+ */
8
+ export const TOOL_OPTIMIZATION_CONFIG = {
9
+ // Network Monitoring Tools (9 tools)
10
+ networkMonitoring: {
11
+ defaultDuration: 10000,
12
+ maxDuration: 30000,
13
+ monitoringTimeout: 15000,
14
+ retryAttempts: 3,
15
+ retryDelay: 2000,
16
+ },
17
+ // Video Extraction Tools (30+ tools)
18
+ videoExtraction: {
19
+ iframeDepthLimit: 5,
20
+ defaultTimeout: 12000,
21
+ hosterPatterns: [
22
+ 'vidcloud',
23
+ 'vidsrc',
24
+ 'filemoon',
25
+ 'streamtape',
26
+ 'doodstream',
27
+ 'mixdrop',
28
+ 'upstream',
29
+ 'streamwish',
30
+ 'vidmoly',
31
+ 'vidlox',
32
+ 'mystream',
33
+ 'cloudemb',
34
+ 'embedsrc',
35
+ 'upns.online',
36
+ 'voe.sx',
37
+ 'streamlare',
38
+ 'dailymotion',
39
+ 'youtube',
40
+ 'vimeo',
41
+ 'twitch',
42
+ ],
43
+ videoSelectors: [
44
+ 'video',
45
+ 'iframe[src*="player"]',
46
+ 'iframe[src*="embed"]',
47
+ 'div[class*="player"]',
48
+ 'div[id*="player"]',
49
+ '[data-video-url]',
50
+ '[data-src*=".mp4"]',
51
+ '[data-src*=".m3u8"]',
52
+ '[data-src*=".mkv"]',
53
+ '[data-src*=".webm"]',
54
+ ],
55
+ downloadButtonSelectors: [
56
+ 'a[download]',
57
+ 'button[download]',
58
+ 'a[href*="download"]',
59
+ 'button[data-download]',
60
+ 'a[href*=".mp4"]',
61
+ 'a[href*=".mkv"]',
62
+ 'a[href*=".webm"]',
63
+ 'a[class*="download"]',
64
+ '.download-btn',
65
+ '.btn-download',
66
+ '[class*="download"]',
67
+ '[id*="download"]',
68
+ ],
69
+ },
70
+ // Data Extraction Tools (40+ tools)
71
+ dataExtraction: {
72
+ deepScanEnabled: true,
73
+ recursiveSearchDepth: 5,
74
+ deduplicationEnabled: true,
75
+ cachingEnabled: true,
76
+ cacheTTL: 300000, // 5 minutes
77
+ defaultTimeout: 10000,
78
+ },
79
+ // Redirect Tracing
80
+ redirectTracing: {
81
+ maxRedirects: 15,
82
+ redirectTimeout: 30000,
83
+ followIframes: true,
84
+ maxDepth: 5,
85
+ },
86
+ // Global Settings
87
+ global: {
88
+ defaultTimeout: 10000,
89
+ retryAttempts: 3,
90
+ retryDelay: 2000,
91
+ enableErrorLogging: true,
92
+ enableCaching: true,
93
+ enableDeduplication: true,
94
+ },
95
+ };
96
+ /**
97
+ * Result Deduplication - Remove duplicate entries
98
+ */
99
+ export function deduplicateResults(results, uniqueKey = 'url') {
100
+ const seen = new Set();
101
+ return results.filter((item) => {
102
+ const key = item[uniqueKey] || JSON.stringify(item);
103
+ if (seen.has(key))
104
+ return false;
105
+ seen.add(key);
106
+ return true;
107
+ });
108
+ }
109
+ /**
110
+ * Deep Deduplication - For complex objects
111
+ */
112
+ export function deepDeduplicateResults(results) {
113
+ const seen = new Set();
114
+ return results.filter((item) => {
115
+ const key = JSON.stringify(item);
116
+ if (seen.has(key))
117
+ return false;
118
+ seen.add(key);
119
+ return true;
120
+ });
121
+ }
122
+ /**
123
+ * Result Caching with TTL
124
+ */
125
+ export class ResultCache {
126
+ cache = new Map();
127
+ set(key, data, ttl = 300000) {
128
+ this.cache.set(key, {
129
+ data,
130
+ timestamp: Date.now(),
131
+ ttl,
132
+ });
133
+ }
134
+ get(key) {
135
+ const cached = this.cache.get(key);
136
+ if (!cached)
137
+ return null;
138
+ const age = Date.now() - cached.timestamp;
139
+ if (age > cached.ttl) {
140
+ this.cache.delete(key);
141
+ return null;
142
+ }
143
+ return cached.data;
144
+ }
145
+ clear() {
146
+ this.cache.clear();
147
+ }
148
+ getStats() {
149
+ return {
150
+ size: this.cache.size,
151
+ entries: Array.from(this.cache.keys()),
152
+ };
153
+ }
154
+ }
155
+ /**
156
+ * Global cache instance for all tools
157
+ */
158
+ export const globalCache = new ResultCache();
159
+ /**
160
+ * Retry with Exponential Backoff
161
+ */
162
+ export async function retryWithBackoff(fn, maxAttempts = 3, initialDelay = 1000) {
163
+ let lastError;
164
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
165
+ try {
166
+ return await fn();
167
+ }
168
+ catch (error) {
169
+ lastError = error instanceof Error ? error : new Error(String(error));
170
+ console.error(`Attempt ${attempt}/${maxAttempts} failed:`, lastError.message);
171
+ if (attempt < maxAttempts) {
172
+ const delay = initialDelay * Math.pow(2, attempt - 1);
173
+ await sleep(delay);
174
+ }
175
+ }
176
+ }
177
+ throw lastError;
178
+ }
179
+ /**
180
+ * Enhanced Error Handler for Tools
181
+ */
182
+ export function createErrorHandler(toolName) {
183
+ return {
184
+ handle(error, context = '') {
185
+ const message = error instanceof Error ? error.message : String(error);
186
+ console.error(`[${toolName}] Error in ${context}: ${message}`);
187
+ return {
188
+ content: [
189
+ {
190
+ type: 'text',
191
+ text: `❌ ${toolName} Error: ${message}`,
192
+ },
193
+ ],
194
+ isError: true,
195
+ toolName,
196
+ context,
197
+ };
198
+ },
199
+ };
200
+ }
201
+ /**
202
+ * Tool Execution Wrapper with Timeout and Retry
203
+ */
204
+ export async function executeToolWithOptimizations(toolName, toolFn, options = {}) {
205
+ const { timeout = TOOL_OPTIMIZATION_CONFIG.global.defaultTimeout, retryAttempts = TOOL_OPTIMIZATION_CONFIG.global.retryAttempts, retryDelay = TOOL_OPTIMIZATION_CONFIG.global.retryDelay, cacheKey, shouldCache = TOOL_OPTIMIZATION_CONFIG.global.enableCaching, } = options;
206
+ // Check cache first
207
+ if (cacheKey && shouldCache) {
208
+ const cached = globalCache.get(cacheKey);
209
+ if (cached) {
210
+ console.log(`[${toolName}] Cache hit for key: ${cacheKey}`);
211
+ return cached;
212
+ }
213
+ }
214
+ try {
215
+ // Execute with timeout
216
+ const result = await Promise.race([
217
+ toolFn(),
218
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`${toolName} timeout after ${timeout}ms`)), timeout)),
219
+ ]);
220
+ // Cache result if enabled
221
+ if (cacheKey && shouldCache) {
222
+ globalCache.set(cacheKey, result);
223
+ }
224
+ return result;
225
+ }
226
+ catch (error) {
227
+ // Retry on failure for network tools
228
+ if (retryAttempts > 0) {
229
+ console.log(`[${toolName}] Retrying after error: ${error instanceof Error ? error.message : String(error)}`);
230
+ return retryWithBackoff(toolFn, retryAttempts, retryDelay);
231
+ }
232
+ throw error;
233
+ }
234
+ }
235
+ /**
236
+ * Resource Cleanup for Tools
237
+ */
238
+ export async function cleanupToolResources(page, context) {
239
+ try {
240
+ // Remove all event listeners
241
+ if (page) {
242
+ await page.removeAllListeners?.();
243
+ }
244
+ // Close unnecessary tabs
245
+ if (context) {
246
+ const pages = await context.pages?.();
247
+ if (pages && pages.length > 1) {
248
+ for (const p of pages.slice(1)) {
249
+ await p.close?.();
250
+ }
251
+ }
252
+ }
253
+ // Clear cache periodically
254
+ const cacheStats = globalCache.getStats();
255
+ if (cacheStats.size > 100) {
256
+ console.log('[Cleanup] Clearing cache - size exceeded 100 items');
257
+ globalCache.clear();
258
+ }
259
+ }
260
+ catch (error) {
261
+ console.error('[Cleanup] Error during cleanup:', error);
262
+ }
263
+ }
264
+ /**
265
+ * Video Hoster Detection Database
266
+ */
267
+ export const VIDEO_HOSTERS_DB = {
268
+ primary: [
269
+ { name: 'vidcloud', patterns: ['vidcloud', 'vidnode'], embedPattern: '/embed/' },
270
+ { name: 'vidsrc', patterns: ['vidsrc.to', 'vidsrc.me', 'vidsrc.xyz'] },
271
+ { name: 'filemoon', patterns: ['filemoon.sx', 'filemoon.to'] },
272
+ { name: 'streamtape', patterns: ['streamtape.com', 'streamtape.to'] },
273
+ { name: 'doodstream', patterns: ['dood.', 'doodstream.'] },
274
+ { name: 'mixdrop', patterns: ['mixdrop.co', 'mixdrop.to'] },
275
+ { name: 'upstream', patterns: ['upstream.to', 'upstreamcdn'] },
276
+ { name: 'streamwish', patterns: ['streamwish.to', 'streamwish.com'] },
277
+ ],
278
+ methods: {
279
+ iframe: { depth: 5, timeout: 12000 },
280
+ directLink: { patterns: ['.m3u8', '.mp4', '.mkv', '/stream/', '.webm'] },
281
+ apiEndpoint: { patterns: ['/api/source', '/api/video', '/get-source', '/embed'] },
282
+ },
283
+ isKnownHoster(url) {
284
+ const lowerUrl = url.toLowerCase();
285
+ for (const hoster of this.primary) {
286
+ if (hoster.patterns.some((p) => lowerUrl.includes(p))) {
287
+ return true;
288
+ }
289
+ }
290
+ return false;
291
+ },
292
+ getHosterName(url) {
293
+ const lowerUrl = url.toLowerCase();
294
+ for (const hoster of this.primary) {
295
+ if (hoster.patterns.some((p) => lowerUrl.includes(p))) {
296
+ return hoster.name;
297
+ }
298
+ }
299
+ return null;
300
+ },
301
+ };
302
+ /**
303
+ * Common Selector Utilities
304
+ */
305
+ export const SELECTOR_UTILS = {
306
+ // Link selectors
307
+ linkSelectors: [
308
+ 'a[href]',
309
+ 'a[href^="http"]',
310
+ 'a[href^="/"]',
311
+ '[data-href]',
312
+ 'button[onclick*="http"]',
313
+ ],
314
+ // Image selectors
315
+ imageSelectors: [
316
+ 'img',
317
+ 'img[src]',
318
+ 'picture img',
319
+ '[style*="background-image"]',
320
+ 'div[data-src*="image"]',
321
+ 'div[data-background*="image"]',
322
+ ],
323
+ // Video selectors: Already defined above
324
+ videoSelectors: TOOL_OPTIMIZATION_CONFIG.videoExtraction.videoSelectors,
325
+ // Download button selectors: Already defined above
326
+ downloadSelectors: TOOL_OPTIMIZATION_CONFIG.videoExtraction.downloadButtonSelectors,
327
+ // Form selectors
328
+ formSelectors: ['form', 'input', 'textarea', 'select', 'button[type="submit"]'],
329
+ // Table selectors
330
+ tableSelectors: ['table', 'tbody tr', 'thead tr', '.table-row', '[role="table"]'],
331
+ // Pagination selectors
332
+ paginationSelectors: [
333
+ 'a[rel="next"]',
334
+ 'button[aria-label*="next"]',
335
+ '.pagination a',
336
+ '.next',
337
+ 'a[href*="page"]',
338
+ 'a[href*="offset"]',
339
+ ],
340
+ // Hidden element detection
341
+ hiddenSelectors: [
342
+ '[style*="display: none"]',
343
+ '[style*="visibility: hidden"]',
344
+ '[hidden]',
345
+ '.hidden',
346
+ '.d-none',
347
+ ],
348
+ // All selectors
349
+ getAllSelectors() {
350
+ return {
351
+ links: this.linkSelectors,
352
+ images: this.imageSelectors,
353
+ videos: this.videoSelectors,
354
+ downloads: this.downloadSelectors,
355
+ forms: this.formSelectors,
356
+ tables: this.tableSelectors,
357
+ pagination: this.paginationSelectors,
358
+ hidden: this.hiddenSelectors,
359
+ };
360
+ },
361
+ };
362
+ /**
363
+ * Performance Metrics Collection
364
+ */
365
+ export class PerformanceMetrics {
366
+ metrics = new Map();
367
+ start(toolName) {
368
+ this.metrics.set(toolName, { start: Date.now() });
369
+ }
370
+ end(toolName) {
371
+ const metric = this.metrics.get(toolName);
372
+ if (metric) {
373
+ metric.end = Date.now();
374
+ metric.duration = metric.end - metric.start;
375
+ return metric.duration;
376
+ }
377
+ return 0;
378
+ }
379
+ getMetrics(toolName) {
380
+ return this.metrics.get(toolName) || null;
381
+ }
382
+ getAllMetrics() {
383
+ return this.metrics;
384
+ }
385
+ getAverageDuration() {
386
+ let totalDuration = 0;
387
+ let count = 0;
388
+ for (const metric of this.metrics.values()) {
389
+ if (metric.duration) {
390
+ totalDuration += metric.duration;
391
+ count++;
392
+ }
393
+ }
394
+ return count > 0 ? totalDuration / count : 0;
395
+ }
396
+ reset() {
397
+ this.metrics.clear();
398
+ }
399
+ }
400
+ /**
401
+ * Global metrics instance
402
+ */
403
+ export const globalMetrics = new PerformanceMetrics();
404
+ export class ToolStatusTracker {
405
+ tools = new Map();
406
+ register(toolName, category, optimizations = []) {
407
+ this.tools.set(toolName, {
408
+ name: toolName,
409
+ category,
410
+ status: 'pending',
411
+ optimizations,
412
+ });
413
+ }
414
+ updateStatus(toolName, status, performanceGain) {
415
+ const tool = this.tools.get(toolName);
416
+ if (tool) {
417
+ tool.status = status;
418
+ if (performanceGain !== undefined) {
419
+ tool.performanceGain = performanceGain;
420
+ }
421
+ }
422
+ }
423
+ recordExecution(toolName, duration) {
424
+ const tool = this.tools.get(toolName);
425
+ if (tool) {
426
+ tool.lastExecutionTime = duration;
427
+ }
428
+ }
429
+ getStatus(toolName) {
430
+ return this.tools.get(toolName) || null;
431
+ }
432
+ getAllStatus() {
433
+ return Array.from(this.tools.values());
434
+ }
435
+ getSummary() {
436
+ let optimized = 0;
437
+ let pending = 0;
438
+ let failed = 0;
439
+ const byCategory = new Map();
440
+ for (const tool of this.tools.values()) {
441
+ if (tool.status === 'optimized')
442
+ optimized++;
443
+ else if (tool.status === 'pending')
444
+ pending++;
445
+ else if (tool.status === 'failed')
446
+ failed++;
447
+ const count = byCategory.get(tool.category) || 0;
448
+ byCategory.set(tool.category, count + 1);
449
+ }
450
+ return {
451
+ total: this.tools.size,
452
+ optimized,
453
+ pending,
454
+ failed,
455
+ byCategory,
456
+ };
457
+ }
458
+ }
459
+ /**
460
+ * Global tool status tracker
461
+ */
462
+ export const globalToolStatus = new ToolStatusTracker();
463
+ export default {
464
+ TOOL_OPTIMIZATION_CONFIG,
465
+ deduplicateResults,
466
+ deepDeduplicateResults,
467
+ ResultCache,
468
+ globalCache,
469
+ retryWithBackoff,
470
+ createErrorHandler,
471
+ executeToolWithOptimizations,
472
+ cleanupToolResources,
473
+ VIDEO_HOSTERS_DB,
474
+ SELECTOR_UTILS,
475
+ PerformanceMetrics,
476
+ globalMetrics,
477
+ ToolStatusTracker,
478
+ globalToolStatus,
479
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-browser-mcp-server",
3
- "version": "2.11.1",
3
+ "version": "2.11.2",
4
4
  "description": "MCP server for brave-real-browser",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",