brave-real-browser-mcp-server 2.27.32 → 2.28.1
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.
|
@@ -1707,6 +1707,7 @@ export async function handleExtractJson(page, args) {
|
|
|
1707
1707
|
try {
|
|
1708
1708
|
const key = args.decryptAES.key || "kiemtienmua911ca";
|
|
1709
1709
|
const ivList = args.decryptAES.ivList || ["1234567890oiuytr", "0123456789abcdef"];
|
|
1710
|
+
const autoIV = args.decryptAES.autoIV !== false; // Default to true for hubstream-style encryption
|
|
1710
1711
|
let input = args.decryptAES.input || "";
|
|
1711
1712
|
// Option: Fetch encrypted data from API URL directly (recommended for hubstream)
|
|
1712
1713
|
if (!input && args.decryptAES.fetchUrl) {
|
|
@@ -1733,24 +1734,43 @@ export async function handleExtractJson(page, args) {
|
|
|
1733
1734
|
}
|
|
1734
1735
|
return bytes;
|
|
1735
1736
|
};
|
|
1736
|
-
|
|
1737
|
+
// Build IV list - if autoIV, try extracting from first 16 bytes first
|
|
1738
|
+
const ivsToTry = [];
|
|
1739
|
+
if (params.autoIV && encryptedHex.length >= 32) {
|
|
1740
|
+
// First 32 hex chars = first 16 bytes = IV (hubstream style)
|
|
1741
|
+
const ivHex = encryptedHex.substring(0, 32);
|
|
1742
|
+
ivsToTry.push({ iv: hexToBytes(ivHex), label: 'autoIV (first 16 bytes)' });
|
|
1743
|
+
}
|
|
1744
|
+
// Add manual IV list
|
|
1745
|
+
for (const ivStr of params.ivList) {
|
|
1746
|
+
ivsToTry.push({ iv: new TextEncoder().encode(ivStr), label: ivStr });
|
|
1747
|
+
}
|
|
1748
|
+
for (const { iv: ivData, label: ivLabel } of ivsToTry) {
|
|
1737
1749
|
try {
|
|
1738
1750
|
const keyData = new TextEncoder().encode(params.key);
|
|
1739
|
-
|
|
1740
|
-
const
|
|
1751
|
+
// If using autoIV, encrypted data starts after first 32 hex chars (16 bytes)
|
|
1752
|
+
const isAutoIV = ivLabel.startsWith('autoIV');
|
|
1753
|
+
const dataHex = isAutoIV ? encryptedHex.substring(32) : encryptedHex;
|
|
1754
|
+
const encryptedData = hexToBytes(dataHex);
|
|
1741
1755
|
const cryptoKey = await crypto.subtle.importKey("raw", keyData, "AES-CBC", false, ["decrypt"]);
|
|
1742
1756
|
const decrypted = await crypto.subtle.decrypt({ name: "AES-CBC", iv: ivData }, cryptoKey, encryptedData);
|
|
1743
1757
|
const decryptedText = new TextDecoder().decode(decrypted);
|
|
1744
|
-
// Extract
|
|
1758
|
+
// Extract URLs from decrypted JSON
|
|
1745
1759
|
const sourceMatch = /"source":"([^"]+)"/.exec(decryptedText);
|
|
1760
|
+
const mp4Match = /"mp4":"([^"]+)"/.exec(decryptedText);
|
|
1761
|
+
const hlsMatch = /"hls":"([^"]+)"/.exec(decryptedText);
|
|
1746
1762
|
const streamUrl = sourceMatch ? sourceMatch[1].replace(/\\\//g, '/') : null;
|
|
1763
|
+
const mp4Url = mp4Match ? mp4Match[1].replace(/\\\//g, '/') : null;
|
|
1764
|
+
const hlsUrl = hlsMatch ? hlsMatch[1].replace(/\\\//g, '/') : null;
|
|
1747
1765
|
return {
|
|
1748
1766
|
success: true,
|
|
1749
|
-
iv:
|
|
1767
|
+
iv: ivLabel,
|
|
1750
1768
|
decrypted: decryptedText,
|
|
1751
1769
|
decryptedLength: decryptedText.length,
|
|
1752
1770
|
extractedStreamUrl: streamUrl,
|
|
1753
|
-
|
|
1771
|
+
extractedMp4Url: mp4Url,
|
|
1772
|
+
extractedHlsUrl: hlsUrl,
|
|
1773
|
+
isStreamUrl: (streamUrl && (streamUrl.includes('m3u8') || streamUrl.includes('.mp4'))) || !!mp4Url,
|
|
1754
1774
|
inputLength: encryptedHex.length
|
|
1755
1775
|
};
|
|
1756
1776
|
}
|
|
@@ -1763,7 +1783,7 @@ export async function handleExtractJson(page, args) {
|
|
|
1763
1783
|
catch (e) {
|
|
1764
1784
|
return { success: false, error: String(e) };
|
|
1765
1785
|
}
|
|
1766
|
-
}, { input, key, ivList, fetchUrl: args.decryptAES.fetchUrl });
|
|
1786
|
+
}, { input, key, ivList, autoIV, fetchUrl: args.decryptAES.fetchUrl });
|
|
1767
1787
|
decoded = {
|
|
1768
1788
|
source: 'decryptAES',
|
|
1769
1789
|
...result,
|
|
@@ -4801,9 +4821,16 @@ export async function handleExecuteJs(page, args) {
|
|
|
4801
4821
|
const waitTime = args.waitForResult || 5000;
|
|
4802
4822
|
try {
|
|
4803
4823
|
let result;
|
|
4824
|
+
// Auto-detect if code contains await and wrap with async IIFE
|
|
4825
|
+
const hasAwait = /\bawait\b/.test(args.code);
|
|
4826
|
+
const isAlreadyAsync = /^\s*\(async\s+function|\(\s*async\s*\(|\(async\s*\(\)\s*=>/.test(args.code.trim());
|
|
4827
|
+
// Wrap code with async IIFE if it contains await but isn't already async
|
|
4828
|
+
const codeToExecute = (hasAwait && !isAlreadyAsync)
|
|
4829
|
+
? `(async () => { ${args.code} })()`
|
|
4830
|
+
: args.code;
|
|
4804
4831
|
if (args.context === 'isolated') {
|
|
4805
4832
|
// Execute in isolated context (sandboxed)
|
|
4806
|
-
result = await page.evaluate(
|
|
4833
|
+
result = await page.evaluate(codeToExecute);
|
|
4807
4834
|
}
|
|
4808
4835
|
else {
|
|
4809
4836
|
// Execute in page context with full access
|
|
@@ -4815,7 +4842,7 @@ export async function handleExecuteJs(page, args) {
|
|
|
4815
4842
|
catch (e) {
|
|
4816
4843
|
return { error: String(e) };
|
|
4817
4844
|
}
|
|
4818
|
-
},
|
|
4845
|
+
}, codeToExecute);
|
|
4819
4846
|
}
|
|
4820
4847
|
// Handle async results
|
|
4821
4848
|
if (result instanceof Promise) {
|
|
@@ -5,6 +5,10 @@ import { getProgressNotifier } from '../transport/progress-notifier.js';
|
|
|
5
5
|
import { getSharedEventBus } from '../transport/index.js';
|
|
6
6
|
// Get event bus instance
|
|
7
7
|
const eventBus = getSharedEventBus();
|
|
8
|
+
// Helper to detect API endpoints
|
|
9
|
+
function isApiEndpoint(url) {
|
|
10
|
+
return /\/api\/|\.json(\?|$)|\?.*=.*&|\/v\d+\//.test(url);
|
|
11
|
+
}
|
|
8
12
|
// Navigation handler with real-time progress
|
|
9
13
|
export async function handleNavigate(args) {
|
|
10
14
|
const progressNotifier = getProgressNotifier();
|
|
@@ -42,6 +46,37 @@ export async function handleNavigate(args) {
|
|
|
42
46
|
}
|
|
43
47
|
catch (error) {
|
|
44
48
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
49
|
+
// Check if this is an API endpoint that failed with ERR_ABORTED
|
|
50
|
+
const isAborted = lastError.message.includes('ERR_ABORTED') ||
|
|
51
|
+
lastError.message.includes('net::ERR_');
|
|
52
|
+
if (isAborted && isApiEndpoint(url)) {
|
|
53
|
+
// Fallback: Use fetch to get API response directly
|
|
54
|
+
tracker.setProgress(50, '📡 Detected API endpoint, using fetch fallback...');
|
|
55
|
+
try {
|
|
56
|
+
const apiResponse = await pageInstance.evaluate(async (apiUrl) => {
|
|
57
|
+
const response = await fetch(apiUrl);
|
|
58
|
+
const text = await response.text();
|
|
59
|
+
return {
|
|
60
|
+
status: response.status,
|
|
61
|
+
contentType: response.headers.get('content-type'),
|
|
62
|
+
data: text
|
|
63
|
+
};
|
|
64
|
+
}, url);
|
|
65
|
+
tracker.complete('📡 API endpoint fetched successfully');
|
|
66
|
+
return {
|
|
67
|
+
content: [
|
|
68
|
+
{
|
|
69
|
+
type: 'text',
|
|
70
|
+
text: `API Response from ${url}\n\nStatus: ${apiResponse.status}\nContent-Type: ${apiResponse.contentType}\n\nData:\n${apiResponse.data}`,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch (fetchError) {
|
|
76
|
+
// Fetch also failed, continue with normal retry
|
|
77
|
+
tracker.setProgress(55, '❌ Fetch fallback failed, retrying navigation...');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
45
80
|
tracker.setProgress(20 + attempt * 20, `❌ Attempt ${attempt} failed: ${lastError.message}`);
|
|
46
81
|
if (attempt < maxRetries) {
|
|
47
82
|
const delay = 1000 * Math.pow(2, attempt - 1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.28.1",
|
|
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.33",
|
|
54
54
|
"puppeteer-core": "^24.35.0",
|
|
55
55
|
"turndown": "latest",
|
|
56
56
|
"vscode-languageserver": "^9.0.1",
|