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.
@@ -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.log(`[${toolName}] Cache hit for key: ${cacheKey}`);
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.log(`[${toolName}] Execution time: ${duration}ms`);
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.log(`[Network Recording] Processed ${totalResponses} responses, ${matchedResponses} matched`);
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.log(`[Network Recording] ✅ Matched ${filterType}: ${url.substring(0, 100)}`);
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.log(`[Network Recording] 🎬 Starting monitoring for ${filterType} (${duration}ms)${navigateTo ? ` + navigating to ${navigateTo}` : ''}`);
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.log(`[Network Recording] ✅ Navigation complete, continuing monitoring...`);
638
+ console.error(`[Network Recording] ✅ Navigation complete, continuing monitoring...`);
639
639
  }
640
640
  catch (e) {
641
- console.log(`[Network Recording] ⚠️ Navigation error (continuing anyway): ${e}`);
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.log(`[Network Recording] 🛑 Monitoring stopped. Total: ${totalResponses}, Matched: ${matchedResponses}, Recorded: ${recordings.length}`);
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.log(`[Extractor] 🎥 Video found: ${url.substring(0, 80)}`);
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
  */