brave-real-browser-mcp-server 2.14.7 → 2.14.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.
- package/dist/browser-manager.js +5 -0
- package/dist/handlers/APPLY_OPTIMIZATION_PATTERN.js +2 -2
- package/dist/handlers/advanced-video-media-handlers.js +7 -7
- package/dist/handlers/ai-powered-handlers.js +0 -197
- package/dist/handlers/data-extraction-handlers.js +0 -98
- package/dist/handlers/data-processing-handlers.js +0 -275
- package/dist/handlers/data-quality-handlers.js +0 -220
- package/dist/handlers/dynamic-session-handlers.js +0 -204
- package/dist/handlers/monitoring-reporting-handlers.js +0 -170
- package/dist/handlers/multi-element-handlers.js +0 -144
- package/dist/handlers/pagination-handlers.js +0 -191
- package/dist/handlers/visual-tools-handlers.js +0 -56
- package/dist/index.js +10 -85
- package/dist/mcp-server.js +2 -11
- package/dist/optimization-utils.js +3 -3
- package/dist/tool-definitions.js +0 -313
- package/package.json +3 -12
package/dist/browser-manager.js
CHANGED
|
@@ -5,7 +5,12 @@ import * as net from 'net';
|
|
|
5
5
|
import { execSync, spawn } from 'child_process';
|
|
6
6
|
import { config as dotenvConfig } from 'dotenv';
|
|
7
7
|
// Load environment variables from .env file
|
|
8
|
+
// Silence dotenv output
|
|
9
|
+
const originalWrite = process.stdout.write;
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
process.stdout.write = () => true;
|
|
8
12
|
dotenvConfig();
|
|
13
|
+
process.stdout.write = originalWrite;
|
|
9
14
|
// Browser error categorization
|
|
10
15
|
export var BrowserErrorType;
|
|
11
16
|
(function (BrowserErrorType) {
|
|
@@ -32,7 +32,7 @@ export async function handleUNIVERSAL_TOOL_TEMPLATE(args) {
|
|
|
32
32
|
if (shouldCache) {
|
|
33
33
|
const cached = globalCache.get(cacheKey);
|
|
34
34
|
if (cached) {
|
|
35
|
-
console.
|
|
35
|
+
console.error(`[${toolName}] Cache hit for key: ${cacheKey}`);
|
|
36
36
|
return {
|
|
37
37
|
content: [
|
|
38
38
|
{
|
|
@@ -87,7 +87,7 @@ export async function handleUNIVERSAL_TOOL_TEMPLATE(args) {
|
|
|
87
87
|
// Cleanup and metrics
|
|
88
88
|
const duration = globalMetrics.end(toolName);
|
|
89
89
|
globalToolStatus.recordExecution(toolName, duration);
|
|
90
|
-
console.
|
|
90
|
+
console.error(`[${toolName}] Execution time: ${duration}ms`);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
// ============================================================================
|
|
@@ -580,7 +580,7 @@ export async function handleNetworkRecordingFinder(args) {
|
|
|
580
580
|
const contentType = response.headers()['content-type'] || '';
|
|
581
581
|
const resourceType = response.request().resourceType();
|
|
582
582
|
if (verbose && totalResponses % 10 === 0) {
|
|
583
|
-
console.
|
|
583
|
+
console.error(`[Network Recording] Processed ${totalResponses} responses, ${matchedResponses} matched`);
|
|
584
584
|
}
|
|
585
585
|
let shouldRecord = false;
|
|
586
586
|
const urlLower = url.toLowerCase();
|
|
@@ -603,7 +603,7 @@ export async function handleNetworkRecordingFinder(args) {
|
|
|
603
603
|
if (shouldRecord) {
|
|
604
604
|
matchedResponses++;
|
|
605
605
|
if (verbose) {
|
|
606
|
-
console.
|
|
606
|
+
console.error(`[Network Recording] ✅ Matched ${filterType}: ${url.substring(0, 100)}`);
|
|
607
607
|
}
|
|
608
608
|
try {
|
|
609
609
|
const buffer = await response.buffer();
|
|
@@ -629,21 +629,21 @@ export async function handleNetworkRecordingFinder(args) {
|
|
|
629
629
|
// Ignore individual response errors
|
|
630
630
|
}
|
|
631
631
|
};
|
|
632
|
-
console.
|
|
632
|
+
console.error(`[Network Recording] 🎬 Starting monitoring for ${filterType} (${duration}ms)${navigateTo ? ` + navigating to ${navigateTo}` : ''}`);
|
|
633
633
|
page.on('response', responseHandler);
|
|
634
634
|
// If navigateTo is provided, navigate first, then wait
|
|
635
635
|
if (navigateTo) {
|
|
636
636
|
try {
|
|
637
637
|
await page.goto(navigateTo, { waitUntil: 'networkidle2', timeout: 30000 });
|
|
638
|
-
console.
|
|
638
|
+
console.error(`[Network Recording] ✅ Navigation complete, continuing monitoring...`);
|
|
639
639
|
}
|
|
640
640
|
catch (e) {
|
|
641
|
-
console.
|
|
641
|
+
console.error(`[Network Recording] ⚠️ Navigation error (continuing anyway): ${e}`);
|
|
642
642
|
}
|
|
643
643
|
}
|
|
644
644
|
await sleep(duration);
|
|
645
645
|
page.off('response', responseHandler);
|
|
646
|
-
console.
|
|
646
|
+
console.error(`[Network Recording] 🛑 Monitoring stopped. Total: ${totalResponses}, Matched: ${matchedResponses}, Recorded: ${recordings.length}`);
|
|
647
647
|
if (recordings.length === 0) {
|
|
648
648
|
return {
|
|
649
649
|
content: [{
|
|
@@ -717,7 +717,7 @@ export async function handleNetworkRecordingExtractors(args) {
|
|
|
717
717
|
(contentType.includes('application/octet-stream') && url.includes('video'));
|
|
718
718
|
if (isVideoFile || isVideoAPI) {
|
|
719
719
|
if (verbose)
|
|
720
|
-
console.
|
|
720
|
+
console.error(`[Extractor] 🎥 Video found: ${url.substring(0, 80)}`);
|
|
721
721
|
extractedData.videos.push({
|
|
722
722
|
url,
|
|
723
723
|
contentType,
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import { getPageInstance } from '../browser-manager.js';
|
|
3
|
-
import natural from 'natural';
|
|
4
|
-
import Sentiment from 'sentiment';
|
|
5
|
-
import { franc } from 'franc';
|
|
6
|
-
const sentiment = new Sentiment();
|
|
7
|
-
const tokenizer = new natural.WordTokenizer();
|
|
8
3
|
/**
|
|
9
4
|
* Smart Selector Generator - AI-powered CSS selector generation
|
|
10
5
|
*/
|
|
@@ -136,201 +131,9 @@ export async function handleContentClassification(args) {
|
|
|
136
131
|
/**
|
|
137
132
|
* Sentiment Analysis - Analyze sentiment of page content
|
|
138
133
|
*/
|
|
139
|
-
export async function handleSentimentAnalysis(args) {
|
|
140
|
-
const { url, selector, text } = args;
|
|
141
|
-
try {
|
|
142
|
-
let contentToAnalyze = text;
|
|
143
|
-
// If no text provided, get from page
|
|
144
|
-
if (!contentToAnalyze) {
|
|
145
|
-
const page = getPageInstance();
|
|
146
|
-
if (!page) {
|
|
147
|
-
throw new Error('Browser not initialized. Call browser_init first.');
|
|
148
|
-
}
|
|
149
|
-
// Navigate if URL provided and different from current
|
|
150
|
-
if (url && page.url() !== url) {
|
|
151
|
-
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
|
|
152
|
-
}
|
|
153
|
-
// Extract content from selector or entire page
|
|
154
|
-
if (selector) {
|
|
155
|
-
contentToAnalyze = await page.evaluate((sel) => {
|
|
156
|
-
const element = document.querySelector(sel);
|
|
157
|
-
return element ? element.textContent : '';
|
|
158
|
-
}, selector);
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
contentToAnalyze = await page.evaluate(() => document.body.innerText);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
if (!contentToAnalyze) {
|
|
165
|
-
throw new Error('No content to analyze');
|
|
166
|
-
}
|
|
167
|
-
const result = sentiment.analyze(contentToAnalyze);
|
|
168
|
-
// Additional analysis using natural
|
|
169
|
-
const tokens = tokenizer.tokenize(contentToAnalyze);
|
|
170
|
-
const sentenceTokenizer = new natural.SentenceTokenizer();
|
|
171
|
-
const sentences = sentenceTokenizer.tokenize(contentToAnalyze);
|
|
172
|
-
// Classify sentiment per sentence
|
|
173
|
-
const sentenceSentiments = sentences.map(sentence => {
|
|
174
|
-
const s = sentiment.analyze(sentence);
|
|
175
|
-
return {
|
|
176
|
-
sentence: sentence.substring(0, 100),
|
|
177
|
-
score: s.score,
|
|
178
|
-
sentiment: s.score > 0 ? 'positive' : s.score < 0 ? 'negative' : 'neutral'
|
|
179
|
-
};
|
|
180
|
-
});
|
|
181
|
-
const stats = {
|
|
182
|
-
totalSentences: sentences.length,
|
|
183
|
-
positiveSentences: sentenceSentiments.filter(s => s.sentiment === 'positive').length,
|
|
184
|
-
negativeSentences: sentenceSentiments.filter(s => s.sentiment === 'negative').length,
|
|
185
|
-
neutralSentences: sentenceSentiments.filter(s => s.sentiment === 'neutral').length
|
|
186
|
-
};
|
|
187
|
-
const resultText = `✅ Sentiment Analysis\n\nOverall Sentiment: ${result.score > 0 ? 'Positive' : result.score < 0 ? 'Negative' : 'Neutral'}\nScore: ${result.score}\nComparative: ${result.comparative.toFixed(4)}\n\nStatistics:\n- Total Sentences: ${stats.totalSentences}\n- Positive: ${stats.positiveSentences}\n- Negative: ${stats.negativeSentences}\n- Neutral: ${stats.neutralSentences}\n\nPositive Words: ${result.positive.join(', ')}\nNegative Words: ${result.negative.join(', ')}`;
|
|
188
|
-
return {
|
|
189
|
-
content: [{
|
|
190
|
-
type: 'text',
|
|
191
|
-
text: resultText
|
|
192
|
-
}]
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
catch (error) {
|
|
196
|
-
return { content: [{ type: 'text', text: `? Error: ${error.message}` }], isError: true };
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
134
|
/**
|
|
200
135
|
* Summary Generator - Generate summary of page content
|
|
201
136
|
*/
|
|
202
|
-
export async function handleSummaryGenerator(args) {
|
|
203
|
-
const { url, maxSentences = 5, selector } = args;
|
|
204
|
-
try {
|
|
205
|
-
const page = getPageInstance();
|
|
206
|
-
if (!page) {
|
|
207
|
-
throw new Error('Browser not initialized. Call browser_init first.');
|
|
208
|
-
}
|
|
209
|
-
if (url && page.url() !== url) {
|
|
210
|
-
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
|
|
211
|
-
}
|
|
212
|
-
let content;
|
|
213
|
-
if (selector) {
|
|
214
|
-
content = await page.evaluate((sel) => {
|
|
215
|
-
const element = document.querySelector(sel);
|
|
216
|
-
return element ? element.textContent : '';
|
|
217
|
-
}, selector);
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
content = await page.evaluate(() => {
|
|
221
|
-
// Extract main content
|
|
222
|
-
const main = document.querySelector('main, article, .content, .post, #content');
|
|
223
|
-
if (main)
|
|
224
|
-
return main.textContent || '';
|
|
225
|
-
return document.body.innerText;
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
if (!content) {
|
|
229
|
-
throw new Error('No content found');
|
|
230
|
-
}
|
|
231
|
-
// Use TF-IDF for extractive summarization
|
|
232
|
-
const TfIdf = natural.TfIdf;
|
|
233
|
-
const tfidf = new TfIdf();
|
|
234
|
-
const sentenceTokenizer = new natural.SentenceTokenizer();
|
|
235
|
-
const sentences = sentenceTokenizer.tokenize(content);
|
|
236
|
-
if (sentences.length === 0) {
|
|
237
|
-
throw new Error('No sentences found to summarize');
|
|
238
|
-
}
|
|
239
|
-
// Add each sentence as a document
|
|
240
|
-
sentences.forEach(sentence => {
|
|
241
|
-
tfidf.addDocument(sentence);
|
|
242
|
-
});
|
|
243
|
-
// Score each sentence
|
|
244
|
-
const sentenceScores = sentences.map((sentence, idx) => {
|
|
245
|
-
let score = 0;
|
|
246
|
-
tfidf.listTerms(idx).forEach(term => {
|
|
247
|
-
score += term.tfidf;
|
|
248
|
-
});
|
|
249
|
-
return { sentence, score, index: idx };
|
|
250
|
-
});
|
|
251
|
-
// Sort by score and take top N
|
|
252
|
-
sentenceScores.sort((a, b) => b.score - a.score);
|
|
253
|
-
const topSentences = sentenceScores.slice(0, maxSentences);
|
|
254
|
-
// Sort by original order
|
|
255
|
-
topSentences.sort((a, b) => a.index - b.index);
|
|
256
|
-
const summary = topSentences.map(s => s.sentence).join(' ');
|
|
257
|
-
const compressionRatio = (summary.length / content.length * 100).toFixed(2);
|
|
258
|
-
const resultText = `✅ Summary Generated\n\nSummary:\n${summary}\n\nStatistics:\n- Original Length: ${content.length} characters\n- Summary Length: ${summary.length} characters\n- Compression Ratio: ${compressionRatio}%\n- Original Sentences: ${sentences.length}\n- Summary Sentences: ${topSentences.length}`;
|
|
259
|
-
return {
|
|
260
|
-
content: [{
|
|
261
|
-
type: 'text',
|
|
262
|
-
text: resultText
|
|
263
|
-
}]
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
catch (error) {
|
|
267
|
-
return { content: [{ type: 'text', text: `? Error: ${error.message}` }], isError: true };
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
137
|
/**
|
|
271
138
|
* Translation Support - Detect language and provide translation info
|
|
272
139
|
*/
|
|
273
|
-
export async function handleTranslationSupport(args) {
|
|
274
|
-
const { url, selector, text, targetLanguage = 'en' } = args;
|
|
275
|
-
try {
|
|
276
|
-
let contentToTranslate = text;
|
|
277
|
-
if (!contentToTranslate && url) {
|
|
278
|
-
const page = getPageInstance();
|
|
279
|
-
if (!page) {
|
|
280
|
-
throw new Error('Browser not initialized. Call browser_init first.');
|
|
281
|
-
}
|
|
282
|
-
if (page.url() !== url) {
|
|
283
|
-
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
|
|
284
|
-
}
|
|
285
|
-
if (selector) {
|
|
286
|
-
contentToTranslate = await page.evaluate((sel) => {
|
|
287
|
-
const element = document.querySelector(sel);
|
|
288
|
-
return element ? element.textContent : '';
|
|
289
|
-
}, selector);
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
contentToTranslate = await page.evaluate(() => document.body.innerText);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if (!contentToTranslate) {
|
|
296
|
-
throw new Error('No content to translate');
|
|
297
|
-
}
|
|
298
|
-
// Detect language using franc
|
|
299
|
-
const detectedLang = franc(contentToTranslate, { minLength: 10 });
|
|
300
|
-
// Get language name
|
|
301
|
-
const langNames = {
|
|
302
|
-
'eng': 'English',
|
|
303
|
-
'spa': 'Spanish',
|
|
304
|
-
'fra': 'French',
|
|
305
|
-
'deu': 'German',
|
|
306
|
-
'ita': 'Italian',
|
|
307
|
-
'por': 'Portuguese',
|
|
308
|
-
'rus': 'Russian',
|
|
309
|
-
'jpn': 'Japanese',
|
|
310
|
-
'kor': 'Korean',
|
|
311
|
-
'cmn': 'Chinese (Mandarin)',
|
|
312
|
-
'ara': 'Arabic',
|
|
313
|
-
'hin': 'Hindi',
|
|
314
|
-
'und': 'Undetermined'
|
|
315
|
-
};
|
|
316
|
-
const languageName = langNames[detectedLang] || detectedLang;
|
|
317
|
-
// Extract key phrases using TF-IDF
|
|
318
|
-
const TfIdf = natural.TfIdf;
|
|
319
|
-
const tfidf = new TfIdf();
|
|
320
|
-
tfidf.addDocument(contentToTranslate);
|
|
321
|
-
const keyPhrases = tfidf.listTerms(0)
|
|
322
|
-
.slice(0, 10)
|
|
323
|
-
.map(term => term.term);
|
|
324
|
-
const needsTranslation = detectedLang !== targetLanguage && detectedLang !== 'und';
|
|
325
|
-
const resultText = `✅ Translation Support\n\nDetected Language: ${languageName} (${detectedLang})\nTarget Language: ${targetLanguage}\nNeeds Translation: ${needsTranslation ? 'Yes' : 'No'}\n\nContent Length: ${contentToTranslate.length} characters\nContent Preview: ${contentToTranslate.substring(0, 200)}...\n\nKey Phrases: ${keyPhrases.join(', ')}\n\nNote: Use external translation API (Google Translate, DeepL) for actual translation`;
|
|
326
|
-
return {
|
|
327
|
-
content: [{
|
|
328
|
-
type: 'text',
|
|
329
|
-
text: resultText
|
|
330
|
-
}]
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
catch (error) {
|
|
334
|
-
return { content: [{ type: 'text', text: `? Error: ${error.message}` }], isError: true };
|
|
335
|
-
}
|
|
336
|
-
}
|
|
@@ -5,104 +5,6 @@
|
|
|
5
5
|
import { getCurrentPage } from '../browser-manager.js';
|
|
6
6
|
import { validateWorkflow } from '../workflow-validation.js';
|
|
7
7
|
import { withErrorHandling } from '../system-utils.js';
|
|
8
|
-
/**
|
|
9
|
-
* HTML Tables से structured data extract करता है
|
|
10
|
-
*/
|
|
11
|
-
export async function handleScrapeTable(args) {
|
|
12
|
-
return await withErrorHandling(async () => {
|
|
13
|
-
// Workflow validation
|
|
14
|
-
validateWorkflow('scrape_table', {
|
|
15
|
-
requireBrowser: true,
|
|
16
|
-
requirePage: true,
|
|
17
|
-
});
|
|
18
|
-
const page = getCurrentPage();
|
|
19
|
-
const selector = args.selector || 'table';
|
|
20
|
-
const includeHeaders = args.includeHeaders !== false;
|
|
21
|
-
const cleanData = args.cleanData !== false;
|
|
22
|
-
const maxRows = args.maxRows || 1000;
|
|
23
|
-
// Extract table data from page
|
|
24
|
-
const tableData = await page.evaluate(({ selector, includeHeaders, cleanData, maxRows }) => {
|
|
25
|
-
const tables = document.querySelectorAll(selector);
|
|
26
|
-
const results = [];
|
|
27
|
-
tables.forEach((table) => {
|
|
28
|
-
const headers = [];
|
|
29
|
-
const rows = [];
|
|
30
|
-
// Extract headers
|
|
31
|
-
if (includeHeaders) {
|
|
32
|
-
const headerCells = table.querySelectorAll('thead th, thead td');
|
|
33
|
-
headerCells.forEach((cell) => {
|
|
34
|
-
const text = cell.textContent?.trim() || '';
|
|
35
|
-
headers.push(cleanData ? text.replace(/\s+/g, ' ') : text);
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
// If no headers found in thead, try first row
|
|
39
|
-
if (headers.length === 0) {
|
|
40
|
-
const firstRow = table.querySelector('tr');
|
|
41
|
-
if (firstRow) {
|
|
42
|
-
const cells = firstRow.querySelectorAll('th, td');
|
|
43
|
-
cells.forEach((cell) => {
|
|
44
|
-
const text = cell.textContent?.trim() || '';
|
|
45
|
-
headers.push(cleanData ? text.replace(/\s+/g, ' ') : text);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
// Extract rows
|
|
50
|
-
const bodyRows = table.querySelectorAll('tbody tr, tr');
|
|
51
|
-
let rowCount = 0;
|
|
52
|
-
bodyRows.forEach((row) => {
|
|
53
|
-
if (rowCount >= maxRows)
|
|
54
|
-
return;
|
|
55
|
-
const cells = row.querySelectorAll('td, th');
|
|
56
|
-
if (cells.length === 0)
|
|
57
|
-
return;
|
|
58
|
-
const rowData = {};
|
|
59
|
-
cells.forEach((cell, index) => {
|
|
60
|
-
const text = cell.textContent?.trim() || '';
|
|
61
|
-
const cleanedText = cleanData ? text.replace(/\s+/g, ' ') : text;
|
|
62
|
-
const header = headers[index] || `column_${index}`;
|
|
63
|
-
// Try to parse as number
|
|
64
|
-
const numValue = parseFloat(cleanedText);
|
|
65
|
-
rowData[header] = isNaN(numValue) ? cleanedText : numValue;
|
|
66
|
-
});
|
|
67
|
-
if (Object.keys(rowData).length > 0) {
|
|
68
|
-
rows.push(rowData);
|
|
69
|
-
rowCount++;
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
if (rows.length > 0) {
|
|
73
|
-
results.push({
|
|
74
|
-
headers,
|
|
75
|
-
rows,
|
|
76
|
-
summary: {
|
|
77
|
-
totalRows: rows.length,
|
|
78
|
-
totalColumns: headers.length,
|
|
79
|
-
extractedAt: new Date().toISOString(),
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
return results;
|
|
85
|
-
}, { selector, includeHeaders, cleanData, maxRows });
|
|
86
|
-
if (tableData.length === 0) {
|
|
87
|
-
return {
|
|
88
|
-
content: [
|
|
89
|
-
{
|
|
90
|
-
type: 'text',
|
|
91
|
-
text: `❌ No tables found with selector "${selector}"`,
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
content: [
|
|
98
|
-
{
|
|
99
|
-
type: 'text',
|
|
100
|
-
text: `✅ Extracted ${tableData.length} table(s)\n\n${JSON.stringify(tableData, null, 2)}`,
|
|
101
|
-
},
|
|
102
|
-
],
|
|
103
|
-
};
|
|
104
|
-
}, 'Failed to scrape table');
|
|
105
|
-
}
|
|
106
8
|
/**
|
|
107
9
|
* Bullet lists और numbered lists से data extract करता है
|
|
108
10
|
*/
|