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 +6 -1
- package/dist/handlers/advanced-tools.js +210 -1
- package/dist/tool-definitions.js +7 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -31,7 +31,12 @@
|
|
|
31
31
|
"mcpServers": {
|
|
32
32
|
"brave-real-browser": {
|
|
33
33
|
"command": "node",
|
|
34
|
-
"args": [
|
|
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.
|
|
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) => {
|
package/dist/tool-definitions.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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",
|