brave-real-browser-mcp-server 2.27.13 → 2.27.15

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/README.md CHANGED
@@ -31,7 +31,12 @@
31
31
  "mcpServers": {
32
32
  "brave-real-browser": {
33
33
  "command": "node",
34
- "args": ["c:\\Users\\Admin\\Desktop\\Workspace-For-Brave-Real-browser-Mcp-Server\\Brave-Real-Browser-Mcp-Server\\dist\\index.js"]
34
+ "args": [
35
+ "c:\\Users\\Admin\\Desktop\\Workspace-For-Brave-Real-browser-Mcp-Server\\Brave-Real-Browser-Mcp-Server\\dist\\index.js"
36
+ ],
37
+ "env": {
38
+ "headless": "false"
39
+ }
35
40
  }
36
41
  }
37
42
  }
@@ -408,6 +408,173 @@ function decodeRot13(input) {
408
408
  function reverseString(input) {
409
409
  return input.split('').reverse().join('');
410
410
  }
411
+ /**
412
+ * Unpack obfuscated JavaScript code using eval(function(p,a,c,k,e,d)) pattern
413
+ * This is the most common packer used by streaming sites
414
+ *
415
+ * Format: eval(function(p,a,c,k,e,d){while(c--)...}('packed_code',36,623,'dict'.split('|'),0,{}))
416
+ */
417
+ function unpackJs(input) {
418
+ try {
419
+ // Check if input contains packed JS pattern
420
+ if (!input.includes('function(p,a,c,k,e,d)') && !input.includes('function (p,a,c,k,e,d)')) {
421
+ return input;
422
+ }
423
+ // Strategy 1: Extract dictionary from the end (most reliable)
424
+ // Pattern: 'dictionary_words'.split('|')
425
+ const dictRegex = /'([^']{50,})'\.split\s*\(\s*'\|'\s*\)/;
426
+ const dictMatch = input.match(dictRegex);
427
+ if (!dictMatch) {
428
+ return input;
429
+ }
430
+ const dictionary = dictMatch[1].split('|');
431
+ // Strategy 2: Position-based extraction (handles escaped quotes in packed code)
432
+ // Find start marker: }('
433
+ const startMarker = "}('";
434
+ const startIdx = input.indexOf(startMarker);
435
+ if (startIdx === -1) {
436
+ return input;
437
+ }
438
+ // Find radix and count pattern: ,radix,count,'
439
+ // Common radix values: 36, 62, 10, 16
440
+ const radixPattern = /,(\d{1,2}),(\d+),'/;
441
+ const radixMatch = input.match(radixPattern);
442
+ if (!radixMatch) {
443
+ return input;
444
+ }
445
+ const radix = parseInt(radixMatch[1]);
446
+ const count = parseInt(radixMatch[2]);
447
+ // Validate radix (must be between 2-62)
448
+ if (radix < 2 || radix > 62) {
449
+ return input;
450
+ }
451
+ // Find position of radix pattern in input
452
+ const radixSearchPattern = ',' + radixMatch[1] + ',' + radixMatch[2] + ",'";
453
+ const radixPos = input.indexOf(radixSearchPattern);
454
+ if (radixPos === -1 || radixPos <= startIdx) {
455
+ return input;
456
+ }
457
+ // Extract packed code between }(' and the radix position
458
+ const packedStart = startIdx + startMarker.length;
459
+ const packed = input.substring(packedStart, radixPos);
460
+ // Validate packed code (must end with quote)
461
+ if (!packed.endsWith("'")) {
462
+ // Try to trim trailing quote
463
+ const cleanPacked = packed.replace(/'$/, '');
464
+ return unpackPACKer(cleanPacked, radix, count, dictionary);
465
+ }
466
+ // Remove trailing quote
467
+ const cleanPacked = packed.slice(0, -1);
468
+ return unpackPACKer(cleanPacked, radix, count, dictionary);
469
+ }
470
+ catch (e) {
471
+ return input;
472
+ }
473
+ }
474
+ /**
475
+ * Core P.A.C.K.E.R. decoding algorithm
476
+ * Replaces placeholders in packed code with dictionary values
477
+ */
478
+ function unpackPACKer(p, a, c, k) {
479
+ // Validate inputs
480
+ if (!p || a < 2 || a > 62 || c <= 0 || !k || k.length === 0) {
481
+ return p || '';
482
+ }
483
+ // Decode function to convert number to base-a representation
484
+ function decode(d) {
485
+ const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
486
+ if (d === 0)
487
+ return '0';
488
+ let result = '';
489
+ while (d > 0) {
490
+ result = chars[d % a] + result;
491
+ d = Math.floor(d / a);
492
+ }
493
+ return result;
494
+ }
495
+ // Replace encoded values with dictionary words (iterate from high to low)
496
+ let unpacked = p;
497
+ for (let i = Math.min(c, k.length) - 1; i >= 0; i--) {
498
+ if (k[i] && k[i].length > 0) {
499
+ const encoded = decode(i);
500
+ // Use word boundary matching
501
+ const pattern = new RegExp('\\b' + encoded + '\\b', 'g');
502
+ unpacked = unpacked.replace(pattern, k[i]);
503
+ }
504
+ }
505
+ return unpacked;
506
+ }
507
+ /**
508
+ * Extract all packed scripts from HTML and decode them
509
+ */
510
+ function extractAndUnpackAll(html) {
511
+ const scripts = [];
512
+ const urls = [];
513
+ // Find script contents that contain packed JS signature
514
+ // Split by script tags and process each one
515
+ const scriptBlocks = [];
516
+ // Method 1: Find eval(function(p,a,c,k,e,d) blocks by searching for the pattern
517
+ // and extracting until the matching closing ))
518
+ const evalStarts = [...html.matchAll(/eval\s*\(\s*function\s*\(\s*p\s*,\s*a\s*,\s*c\s*,\s*k\s*,\s*e\s*,\s*d\s*\)/g)];
519
+ for (const evalStart of evalStarts) {
520
+ if (evalStart.index !== undefined) {
521
+ // Find the end by looking for .split('|')))
522
+ const searchStart = evalStart.index;
523
+ const searchEnd = html.indexOf(".split('|')))", searchStart);
524
+ if (searchEnd !== -1) {
525
+ const block = html.substring(searchStart, searchEnd + 14); // +14 for ".split('|')))"
526
+ scriptBlocks.push(block);
527
+ }
528
+ }
529
+ }
530
+ // Method 2: Fallback - look for any scripts containing the signature
531
+ if (scriptBlocks.length === 0) {
532
+ const signature = "function(p,a,c,k,e,d)";
533
+ let searchPos = 0;
534
+ while ((searchPos = html.indexOf(signature, searchPos)) !== -1) {
535
+ // Extract surrounding context (up to 15000 chars)
536
+ const start = Math.max(0, searchPos - 100);
537
+ const end = Math.min(html.length, searchPos + 15000);
538
+ const context = html.substring(start, end);
539
+ // Check if this looks like a full packed script
540
+ if (context.includes(".split('|')")) {
541
+ scriptBlocks.push(context);
542
+ }
543
+ searchPos += signature.length;
544
+ }
545
+ }
546
+ // Process all found script blocks
547
+ for (const block of scriptBlocks) {
548
+ try {
549
+ const unpacked = unpackJs(block);
550
+ if (unpacked && unpacked !== block && unpacked.length > 100) {
551
+ scripts.push(unpacked);
552
+ // Extract URLs from unpacked script
553
+ const urlPatterns = [
554
+ /https?:\/\/[^\s"'<>\\]+\.m3u8[^\s"'<>\\]*/gi,
555
+ /https?:\/\/[^\s"'<>\\]+\.txt\?[^\s"'<>\\]*/gi,
556
+ /https?:\/\/[^\s"'<>\\]+\/stream\/[^\s"'<>\\]*/gi,
557
+ /https?:\/\/[^\s"'<>\\]+\/hls\/[^\s"'<>\\]*/gi,
558
+ /https?:\/\/[^\s"'<>\\]+pureluxury[^\s"'<>\\]*/gi,
559
+ /https?:\/\/[^\s"'<>\\]+minochinos[^\s"'<>\\]*/gi,
560
+ ];
561
+ for (const pattern of urlPatterns) {
562
+ let urlMatch;
563
+ while ((urlMatch = pattern.exec(unpacked)) !== null) {
564
+ const url = urlMatch[0].replace(/['"\\,;}\]]+$/, ''); // Clean trailing chars
565
+ if (!urls.includes(url) && url.length > 20) {
566
+ urls.push(url);
567
+ }
568
+ }
569
+ }
570
+ }
571
+ }
572
+ catch (e) {
573
+ // Skip failed unpacking
574
+ }
575
+ }
576
+ return { scripts, urls };
577
+ }
411
578
  function applyDecodeLayer(input, layer) {
412
579
  switch (layer) {
413
580
  case 'base64': return decodeBase64(input);
@@ -415,6 +582,7 @@ function applyDecodeLayer(input, layer) {
415
582
  case 'hex': return decodeHex(input);
416
583
  case 'rot13': return decodeRot13(input);
417
584
  case 'reverse': return reverseString(input);
585
+ case 'unpackJs': return unpackJs(input);
418
586
  default: return input;
419
587
  }
420
588
  }
@@ -577,7 +745,48 @@ export async function handleExtractJson(page, args) {
577
745
  }
578
746
  }
579
747
  // ============================================================
580
- // 5. ORIGINAL JSON EXTRACTION (preserved)
748
+ // 5. DECODE PACKED JAVASCRIPT (AUTO-DETECT)
749
+ // ============================================================
750
+ if (args.decodePackedJs) {
751
+ try {
752
+ // Get all script contents from page
753
+ const scriptContents = await page.evaluate(() => {
754
+ const scripts = Array.from(document.querySelectorAll('script'));
755
+ return scripts.map(s => s.textContent || '').filter(s => s.length > 0);
756
+ });
757
+ const allUnpacked = { scripts: [], urls: [] };
758
+ for (const script of scriptContents) {
759
+ const result = extractAndUnpackAll(script);
760
+ allUnpacked.scripts.push(...result.scripts);
761
+ allUnpacked.urls.push(...result.urls);
762
+ }
763
+ // Remove duplicates from URLs
764
+ const uniqueUrls = [...new Set(allUnpacked.urls)];
765
+ if (allUnpacked.scripts.length > 0 || uniqueUrls.length > 0) {
766
+ decoded = {
767
+ source: 'decodePackedJs',
768
+ packedScriptsFound: allUnpacked.scripts.length,
769
+ unpackedScripts: allUnpacked.scripts.slice(0, 5), // Limit to first 5
770
+ extractedUrls: uniqueUrls,
771
+ streamUrls: uniqueUrls.filter(u => u.includes('m3u8') || u.includes('.txt?') ||
772
+ u.includes('/stream/') || u.includes('/hls/')),
773
+ };
774
+ data.push(decoded);
775
+ }
776
+ else {
777
+ decoded = {
778
+ source: 'decodePackedJs',
779
+ message: 'No packed JavaScript found in page',
780
+ hint: 'Try using execute_js or player_api_hook for dynamically loaded content',
781
+ };
782
+ }
783
+ }
784
+ catch (e) {
785
+ decoded = { source: 'decodePackedJs', error: String(e) };
786
+ }
787
+ }
788
+ // ============================================================
789
+ // 6. ORIGINAL JSON EXTRACTION (preserved)
581
790
  // ============================================================
582
791
  // Extract from script tags with type="application/json" or type="application/ld+json"
583
792
  const scriptData = await page.evaluate((selector) => {
@@ -2,7 +2,7 @@
2
2
  // Server metadata
3
3
  export const SERVER_INFO = {
4
4
  name: 'brave-real-browser-mcp-server',
5
- version: '2.24.0',
5
+ version: '2.25.0',
6
6
  };
7
7
  // MCP capabilities with LSP-compatible streaming support
8
8
  export const CAPABILITIES = {
@@ -470,7 +470,7 @@ export const TOOLS = [
470
470
  },
471
471
  {
472
472
  name: 'extract_json',
473
- description: 'Extract embedded JSON/API data from page (LD+JSON, __NEXT_DATA__, etc.) + Advanced Decode capabilities for complex websites (base64, URL, hex, rot13, multi-layer)',
473
+ description: 'Extract embedded JSON/API data from page (LD+JSON, __NEXT_DATA__, etc.) + Advanced Decode capabilities for complex websites (base64, URL, hex, rot13, multi-layer) + Packed JavaScript decoder (eval/function(p,a,c,k,e,d) unpacker)',
474
474
  inputSchema: {
475
475
  type: 'object',
476
476
  additionalProperties: false,
@@ -484,15 +484,17 @@ export const TOOLS = [
484
484
  decodeFromUrl: { type: 'string', description: 'URL to extract and decode encoded parameter from (e.g., gadgetsweb.xyz/?id=xxx)' },
485
485
  decodeUrlParam: { type: 'string', description: 'URL parameter name to decode (default: id)', default: 'id' },
486
486
  decodePattern: { type: 'string', description: 'Regex pattern to find and decode base64 strings in page content' },
487
+ // NEW: Packed JavaScript decoder
488
+ decodePackedJs: { type: 'boolean', description: 'Auto-detect and decode packed JavaScript (eval/function(p,a,c,k,e,d)) in page scripts. Extracts m3u8/stream URLs automatically.', default: false },
487
489
  advancedDecode: {
488
490
  type: 'object',
489
- description: 'Multi-layer decode for complex obfuscation (supports base64, url, hex, rot13, reverse)',
491
+ description: 'Multi-layer decode for complex obfuscation (supports base64, url, hex, rot13, reverse, unpackJs)',
490
492
  properties: {
491
493
  input: { type: 'string', description: 'Input string to decode (if not using page content)' },
492
494
  layers: {
493
495
  type: 'array',
494
- items: { type: 'string', enum: ['base64', 'url', 'hex', 'rot13', 'reverse'] },
495
- description: 'Decode layers to apply in order (e.g., ["base64", "url"])'
496
+ items: { type: 'string', enum: ['base64', 'url', 'hex', 'rot13', 'reverse', 'unpackJs'] },
497
+ description: 'Decode layers to apply in order (e.g., ["base64", "url", "unpackJs"])'
496
498
  },
497
499
  extractFromPage: { type: 'boolean', description: 'Extract input from current page content', default: false },
498
500
  pageSelector: { type: 'string', description: 'CSS selector to extract from (for extractFromPage)' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-browser-mcp-server",
3
- "version": "2.27.13",
3
+ "version": "2.27.15",
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.8.13",
53
+ "brave-real-browser": "^2.8.15",
54
54
  "puppeteer-core": "^24.35.0",
55
55
  "turndown": "latest",
56
56
  "vscode-languageserver": "^9.0.1",