brave-real-browser-mcp-server 2.27.26 → 2.27.28

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.
@@ -195,21 +195,137 @@ export async function handleMultiLayerRedirectTrace(page, args) {
195
195
  };
196
196
  }
197
197
  /**
198
- * 🔥 ULTRA-POWERFUL Regex Engine (like regex101.com)
198
+ * 🔥 ULTRA-POWERFUL Regex Engine (like regex101.com) + Simple Search Mode
199
199
  * Features: Named capture groups, all regex flags, match highlighting,
200
- * detailed match info, replace mode, timeout protection
200
+ * detailed match info, replace mode, timeout protection,
201
+ * NEW: Simple search mode, Pattern explanation, Hierarchical group tree
201
202
  */
203
+ // Helper: Escape regex special characters for simple search mode
204
+ function escapeRegex(str) {
205
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
206
+ }
207
+ // Helper: Generate pattern explanation
208
+ function explainPattern(pattern) {
209
+ const explanations = [];
210
+ const regexTokens = [
211
+ { regex: /^\^/, meaning: () => 'Start of string/line' },
212
+ { regex: /^\$/, meaning: () => 'End of string/line' },
213
+ { regex: /^\\d/, meaning: () => 'Any digit (0-9)' },
214
+ { regex: /^\\D/, meaning: () => 'Any non-digit character' },
215
+ { regex: /^\\w/, meaning: () => 'Word character (a-z, A-Z, 0-9, _)' },
216
+ { regex: /^\\W/, meaning: () => 'Non-word character' },
217
+ { regex: /^\\s/, meaning: () => 'Whitespace character (space, tab, newline)' },
218
+ { regex: /^\\S/, meaning: () => 'Non-whitespace character' },
219
+ { regex: /^\\b/, meaning: () => 'Word boundary' },
220
+ { regex: /^\\B/, meaning: () => 'Non-word boundary' },
221
+ { regex: /^\\n/, meaning: () => 'Newline character' },
222
+ { regex: /^\\r/, meaning: () => 'Carriage return' },
223
+ { regex: /^\\t/, meaning: () => 'Tab character' },
224
+ { regex: /^\./, meaning: () => 'Any character (except newline unless s flag)' },
225
+ { regex: /^\*/, meaning: () => 'Zero or more of the preceding' },
226
+ { regex: /^\+/, meaning: () => 'One or more of the preceding' },
227
+ { regex: /^\?/, meaning: () => 'Zero or one of the preceding (optional)' },
228
+ { regex: /^\*\?/, meaning: () => 'Zero or more (lazy/non-greedy)' },
229
+ { regex: /^\+\?/, meaning: () => 'One or more (lazy/non-greedy)' },
230
+ { regex: /^\?\?/, meaning: () => 'Zero or one (lazy/non-greedy)' },
231
+ { regex: /^\|/, meaning: () => 'OR - matches either side' },
232
+ { regex: /^\(\?:([^)]*)\)/, meaning: (m) => `Non-capturing group: ${m}` },
233
+ { regex: /^\(\?=([^)]*)\)/, meaning: (m) => `Positive lookahead: must be followed by "${m}"` },
234
+ { regex: /^\(\?!([^)]*)\)/, meaning: (m) => `Negative lookahead: must NOT be followed by "${m}"` },
235
+ { regex: /^\(\?<=([^)]*)\)/, meaning: (m) => `Positive lookbehind: must be preceded by "${m}"` },
236
+ { regex: /^\(\?<!([^)]*)\)/, meaning: (m) => `Negative lookbehind: must NOT be preceded by "${m}"` },
237
+ { regex: /^\(\?<(\w+)>/, meaning: (m) => `Named capture group: "${m}"` },
238
+ { regex: /^\(/, meaning: () => 'Capture group start' },
239
+ { regex: /^\)/, meaning: () => 'Capture group end' },
240
+ { regex: /^\[(\^)?([^\]]+)\]/, meaning: (m) => m.startsWith('^') ? `Any character NOT in: ${m.slice(1)}` : `Any character in: ${m}` },
241
+ { regex: /^\{(\d+)\}/, meaning: (m) => `Exactly ${m} occurrences` },
242
+ { regex: /^\{(\d+),\}/, meaning: (m) => `${m} or more occurrences` },
243
+ { regex: /^\{(\d+),(\d+)\}/, meaning: (m) => `Between ${m.split(',')[0]} and ${m.split(',')[1]} occurrences` },
244
+ { regex: /^\\([0-9])/, meaning: (m) => `Backreference to group ${m}` },
245
+ { regex: /^\\k<(\w+)>/, meaning: (m) => `Backreference to named group "${m}"` },
246
+ { regex: /^https?:\/\//, meaning: () => 'HTTP or HTTPS protocol' },
247
+ { regex: /^[a-zA-Z0-9]+/, meaning: (m) => `Literal text: "${m}"` },
248
+ { regex: /^\\(.)/, meaning: (m) => `Escaped character: "${m}"` },
249
+ ];
250
+ let remaining = pattern;
251
+ let position = 0;
252
+ while (remaining.length > 0) {
253
+ let matched = false;
254
+ for (const token of regexTokens) {
255
+ const match = remaining.match(token.regex);
256
+ if (match) {
257
+ const part = match[0];
258
+ const captured = match[1] || match[0];
259
+ explanations.push({
260
+ part,
261
+ meaning: token.meaning(captured),
262
+ position,
263
+ });
264
+ remaining = remaining.slice(part.length);
265
+ position += part.length;
266
+ matched = true;
267
+ break;
268
+ }
269
+ }
270
+ if (!matched) {
271
+ // Single character literal
272
+ const char = remaining[0];
273
+ explanations.push({
274
+ part: char,
275
+ meaning: `Literal character: "${char}"`,
276
+ position,
277
+ });
278
+ remaining = remaining.slice(1);
279
+ position += 1;
280
+ }
281
+ }
282
+ return explanations;
283
+ }
284
+ // Helper: Build hierarchical group tree
285
+ function buildGroupTree(pattern, match) {
286
+ const groups = [];
287
+ // Extract named groups from pattern
288
+ const namedGroupPattern = /\(\?<(\w+)>/g;
289
+ const namedGroups = [];
290
+ let namedMatch;
291
+ while ((namedMatch = namedGroupPattern.exec(pattern)) !== null) {
292
+ namedGroups.push(namedMatch[1]);
293
+ }
294
+ // Build group entries
295
+ for (let i = 1; i < match.length; i++) {
296
+ if (match[i] !== undefined) {
297
+ const value = match[i];
298
+ const fullMatchText = match[0];
299
+ const valueIndex = fullMatchText.indexOf(value);
300
+ groups.push({
301
+ index: i,
302
+ name: match.groups && Object.keys(match.groups)[i - 1] ? Object.keys(match.groups).find(k => match.groups[k] === value) : undefined,
303
+ value,
304
+ start: valueIndex >= 0 ? match.index + valueIndex : match.index,
305
+ end: valueIndex >= 0 ? match.index + valueIndex + value.length : match.index + value.length,
306
+ nested: i > 1 && match[i - 1]?.includes(value),
307
+ });
308
+ }
309
+ }
310
+ return {
311
+ fullMatch: match[0],
312
+ groups,
313
+ };
314
+ }
202
315
  export async function handleSearchRegex(page, args) {
203
316
  const progressNotifier = getProgressNotifier();
204
317
  const tracker = progressNotifier.createTracker(`search-regex-${Date.now()}`);
205
- tracker.start(100, `🔎 Regex Search: "${args.pattern}"`);
318
+ // Determine search mode
319
+ const isSimpleSearch = args.simpleSearch === true;
320
+ const searchMode = isSimpleSearch ? '🔤 Simple Search' : '🔎 Regex Search';
321
+ tracker.start(100, `${searchMode}: "${args.pattern}"`);
206
322
  // Build regex flags
207
323
  const flags = args.flags || {};
208
324
  let flagString = '';
209
325
  if (flags.global !== false)
210
326
  flagString += 'g';
211
- if (flags.ignoreCase)
212
- flagString += 'i';
327
+ if (flags.ignoreCase || isSimpleSearch)
328
+ flagString += 'i'; // Simple search defaults to case-insensitive
213
329
  if (flags.multiline)
214
330
  flagString += 'm';
215
331
  if (flags.dotAll)
@@ -218,11 +334,24 @@ export async function handleSearchRegex(page, args) {
218
334
  flagString += 'u';
219
335
  if (flags.sticky)
220
336
  flagString += 'y';
337
+ // Process pattern based on mode
338
+ let processedPattern = args.pattern;
339
+ const originalPattern = args.pattern;
340
+ if (isSimpleSearch) {
341
+ // Escape all regex special characters for literal search
342
+ processedPattern = escapeRegex(args.pattern);
343
+ tracker.setProgress(10, '🔤 Simple search mode - treating as literal text');
344
+ }
221
345
  // Validate regex
222
346
  let regex;
223
- let patternInfo = { flags: flagString, isValid: true, errorMessage: undefined };
347
+ let patternInfo = {
348
+ flags: flagString,
349
+ isValid: true,
350
+ isSimpleSearch,
351
+ originalPattern: isSimpleSearch ? originalPattern : undefined,
352
+ };
224
353
  try {
225
- regex = new RegExp(args.pattern, flagString);
354
+ regex = new RegExp(processedPattern, flagString);
226
355
  }
227
356
  catch (e) {
228
357
  tracker.fail(`Invalid regex: ${e.message}`);
@@ -230,9 +359,15 @@ export async function handleSearchRegex(page, args) {
230
359
  found: false,
231
360
  matches: [],
232
361
  count: 0,
233
- patternInfo: { flags: flagString, isValid: false, errorMessage: e.message },
362
+ patternInfo: { ...patternInfo, isValid: false, errorMessage: e.message },
234
363
  };
235
364
  }
365
+ // Generate pattern explanation if requested
366
+ let patternExplanation;
367
+ if (args.showExplanation && !isSimpleSearch) {
368
+ tracker.setProgress(15, '📖 Generating pattern explanation...');
369
+ patternExplanation = explainPattern(args.pattern);
370
+ }
236
371
  tracker.setProgress(20, '📄 Extracting content...');
237
372
  // Get content based on sourceType
238
373
  let content = '';
@@ -288,9 +423,9 @@ export async function handleSearchRegex(page, args) {
288
423
  }
289
424
  if (!content) {
290
425
  tracker.fail('No content to search');
291
- return { found: false, matches: [], count: 0, patternInfo };
426
+ return { found: false, matches: [], count: 0, patternInfo, patternExplanation };
292
427
  }
293
- tracker.setProgress(50, '🔍 Running regex match...');
428
+ tracker.setProgress(50, '🔍 Running match...');
294
429
  const matches = [];
295
430
  const maxMatches = args.maxMatches || 100;
296
431
  const contextChars = args.contextChars || 50;
@@ -319,7 +454,7 @@ export async function handleSearchRegex(page, args) {
319
454
  length: match[0].length,
320
455
  };
321
456
  // Extract groups if requested
322
- if (args.extractGroups !== false) {
457
+ if (args.extractGroups !== false && !isSimpleSearch) {
323
458
  if (match.groups) {
324
459
  matchResult.groups = match.groups;
325
460
  }
@@ -327,6 +462,10 @@ export async function handleSearchRegex(page, args) {
327
462
  matchResult.numberedGroups = match.slice(1);
328
463
  }
329
464
  }
465
+ // Build hierarchical group tree if requested
466
+ if (args.showGroupTree && !isSimpleSearch && match.length > 1) {
467
+ matchResult.groupTree = buildGroupTree(args.pattern, match);
468
+ }
330
469
  matches.push(matchResult);
331
470
  if (matches.length >= maxMatches)
332
471
  break;
@@ -341,13 +480,15 @@ export async function handleSearchRegex(page, args) {
341
480
  tracker.setProgress(90, '🔄 Applying replacement...');
342
481
  replaced = content.replace(regex, args.replaceWith);
343
482
  }
344
- tracker.complete(`🎉 Found ${matches.length} matches`);
483
+ const modeText = isSimpleSearch ? 'text matches' : 'regex matches';
484
+ tracker.complete(`🎉 Found ${matches.length} ${modeText}`);
345
485
  return {
346
486
  found: matches.length > 0,
347
487
  matches,
348
488
  count: matches.length,
349
489
  replaced,
350
490
  patternInfo,
491
+ patternExplanation,
351
492
  };
352
493
  }
353
494
  /**
@@ -171,15 +171,18 @@ const TOOL_DEFINITIONS = {
171
171
  // Advanced Tools
172
172
  search_regex: {
173
173
  name: 'search_regex',
174
- description: '🔥 ULTRA-POWERFUL Regex Engine (like regex101.com) - Full regex support with flags, capture groups, replace mode, timeout protection',
174
+ description: '🔥 ULTRA-POWERFUL Regex Engine (like regex101.com) - Full regex support with flags, capture groups, replace mode, timeout protection. NEW: Simple search mode for non-regex text search, pattern explanation, and group tree.',
175
175
  category: 'Advanced',
176
176
  parameters: [
177
- { name: 'pattern', type: 'string', description: 'Regex pattern to search', required: true },
177
+ { name: 'pattern', type: 'string', description: 'Regex pattern to search (or literal text in simpleSearch mode)', required: true },
178
+ { name: 'simpleSearch', type: 'boolean', description: 'Treat pattern as literal text, not regex. Perfect for Ctrl+F style searches.', required: false, default: false },
178
179
  { name: 'flags', type: 'object', description: 'Regex flags: global, ignoreCase, multiline, dotAll, unicode, sticky', required: false },
179
180
  { name: 'replaceWith', type: 'string', description: 'Replace matches with this string (supports $1, $2, $&)', required: false },
180
181
  { name: 'sourceType', type: 'string', description: 'Where to search: text, html, scripts, styles, attributes, all', required: false, default: 'text' },
181
182
  { name: 'extractGroups', type: 'boolean', description: 'Extract capture groups', required: false, default: true },
182
183
  { name: 'highlightMatches', type: 'boolean', description: 'Highlight matches in context', required: false, default: false },
184
+ { name: 'showExplanation', type: 'boolean', description: 'Generate explanation for each regex component', required: false, default: false },
185
+ { name: 'showGroupTree', type: 'boolean', description: 'Show hierarchical structure of capture groups', required: false, default: false },
183
186
  ],
184
187
  },
185
188
  // web_search REMOVED - redundant with search_regex
@@ -476,14 +476,16 @@ export const TOOLS = [
476
476
  },
477
477
  {
478
478
  name: 'search_regex',
479
- description: `🔥 ULTRA-POWERFUL Regex Engine (like regex101.com) - Test, match, replace with full regex support. Features: Named capture groups, all regex flags (g/i/m/s/u/y), match highlighting, detailed match info with indices, replace mode, pattern explanation, timeout protection against catastrophic backtracking. Search in HTML, text, scripts, styles, or attributes.`,
479
+ description: `🔥 ULTRA-POWERFUL Regex Engine (like regex101.com) - Test, match, replace with full regex support. Features: Named capture groups, all regex flags (g/i/m/s/u/y), match highlighting, detailed match info with indices, replace mode, pattern explanation, timeout protection against catastrophic backtracking. Search in HTML, text, scripts, styles, or attributes. NEW: Simple search mode for non-regex text search.`,
480
480
  inputSchema: {
481
481
  type: 'object',
482
482
  additionalProperties: false,
483
483
  properties: {
484
484
  // Core regex
485
- pattern: { type: 'string', description: 'Regex pattern to search (supports full JavaScript regex syntax including lookahead/lookbehind)' },
485
+ pattern: { type: 'string', description: 'Regex pattern to search (supports full JavaScript regex syntax including lookahead/lookbehind). In simpleSearch mode, treated as literal text.' },
486
486
  testString: { type: 'string', description: 'Optional custom string to test against. If not provided, uses page content' },
487
+ // NEW: Simple Search Mode
488
+ simpleSearch: { type: 'boolean', description: 'Treat pattern as literal text, not regex. Perfect for Ctrl+F style searches. Automatically case-insensitive.', default: false },
487
489
  // Regex flags (individually controllable like regex101.com)
488
490
  flags: {
489
491
  type: 'object',
@@ -515,6 +517,10 @@ export const TOOLS = [
515
517
  extractGroups: { type: 'boolean', description: 'Extract and return named/numbered capture groups', default: true },
516
518
  highlightMatches: { type: 'boolean', description: 'Return matches with <<MATCH>> highlighting', default: false },
517
519
  showMatchInfo: { type: 'boolean', description: 'Show detailed match info: index, length, groups', default: true },
520
+ // NEW: Pattern Explanation
521
+ showExplanation: { type: 'boolean', description: 'Generate explanation for each regex component (like regex101.com explanation sidebar)', default: false },
522
+ // NEW: Hierarchical Group Tree
523
+ showGroupTree: { type: 'boolean', description: 'Show hierarchical structure of capture groups with positions and nesting info', default: false },
518
524
  // Safety
519
525
  timeout: { type: 'number', description: 'Regex execution timeout in ms (prevents catastrophic backtracking)', default: 5000 },
520
526
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-browser-mcp-server",
3
- "version": "2.27.26",
3
+ "version": "2.27.28",
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.26",
53
+ "brave-real-browser": "^2.8.28",
54
54
  "puppeteer-core": "^24.35.0",
55
55
  "turndown": "latest",
56
56
  "vscode-languageserver": "^9.0.1",