@librechat/agents 3.0.66 → 3.0.68

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.
Files changed (39) hide show
  1. package/dist/cjs/common/enum.cjs +3 -1
  2. package/dist/cjs/common/enum.cjs.map +1 -1
  3. package/dist/cjs/main.cjs +14 -7
  4. package/dist/cjs/main.cjs.map +1 -1
  5. package/dist/cjs/messages/tools.cjs +2 -2
  6. package/dist/cjs/messages/tools.cjs.map +1 -1
  7. package/dist/cjs/tools/ToolNode.cjs +1 -1
  8. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  9. package/dist/cjs/tools/{ToolSearchRegex.cjs → ToolSearch.cjs} +318 -63
  10. package/dist/cjs/tools/ToolSearch.cjs.map +1 -0
  11. package/dist/esm/common/enum.mjs +3 -1
  12. package/dist/esm/common/enum.mjs.map +1 -1
  13. package/dist/esm/main.mjs +1 -1
  14. package/dist/esm/messages/tools.mjs +2 -2
  15. package/dist/esm/messages/tools.mjs.map +1 -1
  16. package/dist/esm/tools/ToolNode.mjs +1 -1
  17. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  18. package/dist/esm/tools/{ToolSearchRegex.mjs → ToolSearch.mjs} +311 -63
  19. package/dist/esm/tools/ToolSearch.mjs.map +1 -0
  20. package/dist/types/common/enum.d.ts +4 -2
  21. package/dist/types/index.d.ts +1 -1
  22. package/dist/types/tools/ToolSearch.d.ts +133 -0
  23. package/dist/types/types/tools.d.ts +8 -2
  24. package/package.json +2 -2
  25. package/src/common/enum.ts +3 -1
  26. package/src/index.ts +1 -1
  27. package/src/messages/__tests__/tools.test.ts +21 -21
  28. package/src/messages/tools.ts +2 -2
  29. package/src/scripts/programmatic_exec_agent.ts +4 -4
  30. package/src/scripts/{tool_search_regex.ts → tool_search.ts} +5 -5
  31. package/src/tools/ToolNode.ts +1 -1
  32. package/src/tools/{ToolSearchRegex.ts → ToolSearch.ts} +390 -69
  33. package/src/tools/__tests__/{ToolSearchRegex.integration.test.ts → ToolSearch.integration.test.ts} +6 -6
  34. package/src/tools/__tests__/ToolSearch.test.ts +734 -0
  35. package/src/types/tools.ts +9 -2
  36. package/dist/cjs/tools/ToolSearchRegex.cjs.map +0 -1
  37. package/dist/esm/tools/ToolSearchRegex.mjs.map +0 -1
  38. package/dist/types/tools/ToolSearchRegex.d.ts +0 -80
  39. package/src/tools/__tests__/ToolSearchRegex.test.ts +0 -232
@@ -1,4 +1,4 @@
1
- // src/tools/ToolSearchRegex.ts
1
+ // src/tools/ToolSearch.ts
2
2
  import { z } from 'zod';
3
3
  import { config } from 'dotenv';
4
4
  import fetch, { RequestInit } from 'node-fetch';
@@ -20,28 +20,111 @@ const MAX_REGEX_COMPLEXITY = 5;
20
20
  /** Default search timeout in milliseconds */
21
21
  const SEARCH_TIMEOUT = 5000;
22
22
 
23
- const ToolSearchRegexSchema = z.object({
24
- query: z
25
- .string()
26
- .min(1)
27
- .max(MAX_PATTERN_LENGTH)
28
- .describe(
29
- 'Regex pattern to search tool names and descriptions. Special regex characters will be sanitized for safety.'
30
- ),
31
- fields: z
32
- .array(z.enum(['name', 'description', 'parameters']))
33
- .optional()
34
- .default(['name', 'description'])
35
- .describe('Which fields to search. Default: name and description'),
36
- max_results: z
37
- .number()
38
- .int()
39
- .min(1)
40
- .max(50)
41
- .optional()
42
- .default(10)
43
- .describe('Maximum number of matching tools to return'),
44
- });
23
+ /** Zod schema type for tool search parameters */
24
+ type ToolSearchSchema = z.ZodObject<{
25
+ query: z.ZodDefault<z.ZodOptional<z.ZodString>>;
26
+ fields: z.ZodDefault<
27
+ z.ZodOptional<z.ZodArray<z.ZodEnum<['name', 'description', 'parameters']>>>
28
+ >;
29
+ max_results: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
30
+ mcp_server: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString>]>>;
31
+ }>;
32
+
33
+ /**
34
+ * Creates the Zod schema with dynamic query description based on mode.
35
+ * @param mode - The search mode determining query interpretation
36
+ * @returns Zod schema for tool search parameters
37
+ */
38
+ function createToolSearchSchema(mode: t.ToolSearchMode): ToolSearchSchema {
39
+ const queryDescription =
40
+ mode === 'local'
41
+ ? 'Search term to find in tool names and descriptions. Case-insensitive substring matching. Optional if mcp_server is provided.'
42
+ : 'Regex pattern to search tool names and descriptions. Optional if mcp_server is provided.';
43
+
44
+ return z.object({
45
+ query: z
46
+ .string()
47
+ .max(MAX_PATTERN_LENGTH)
48
+ .optional()
49
+ .default('')
50
+ .describe(queryDescription),
51
+ fields: z
52
+ .array(z.enum(['name', 'description', 'parameters']))
53
+ .optional()
54
+ .default(['name', 'description'])
55
+ .describe('Which fields to search. Default: name and description'),
56
+ max_results: z
57
+ .number()
58
+ .int()
59
+ .min(1)
60
+ .max(50)
61
+ .optional()
62
+ .default(10)
63
+ .describe('Maximum number of matching tools to return'),
64
+ mcp_server: z
65
+ .union([z.string(), z.array(z.string())])
66
+ .optional()
67
+ .describe(
68
+ 'Filter to tools from specific MCP server(s). Can be a single server name or array of names. If provided without a query, lists all tools from those servers.'
69
+ ),
70
+ });
71
+ }
72
+
73
+ /**
74
+ * Extracts the MCP server name from a tool name.
75
+ * MCP tools follow the pattern: toolName_mcp_serverName
76
+ * @param toolName - The full tool name
77
+ * @returns The server name if it's an MCP tool, undefined otherwise
78
+ */
79
+ function extractMcpServerName(toolName: string): string | undefined {
80
+ const delimiterIndex = toolName.indexOf(Constants.MCP_DELIMITER);
81
+ if (delimiterIndex === -1) {
82
+ return undefined;
83
+ }
84
+ return toolName.substring(delimiterIndex + Constants.MCP_DELIMITER.length);
85
+ }
86
+
87
+ /**
88
+ * Checks if a tool belongs to a specific MCP server.
89
+ * @param toolName - The full tool name
90
+ * @param serverName - The server name to match
91
+ * @returns True if the tool belongs to the specified server
92
+ */
93
+ function isFromMcpServer(toolName: string, serverName: string): boolean {
94
+ const toolServer = extractMcpServerName(toolName);
95
+ return toolServer === serverName;
96
+ }
97
+
98
+ /**
99
+ * Checks if a tool belongs to any of the specified MCP servers.
100
+ * @param toolName - The full tool name
101
+ * @param serverNames - Array of server names to match
102
+ * @returns True if the tool belongs to any of the specified servers
103
+ */
104
+ function isFromAnyMcpServer(toolName: string, serverNames: string[]): boolean {
105
+ const toolServer = extractMcpServerName(toolName);
106
+ if (toolServer === undefined) {
107
+ return false;
108
+ }
109
+ return serverNames.includes(toolServer);
110
+ }
111
+
112
+ /**
113
+ * Normalizes server filter input to always be an array.
114
+ * @param serverFilter - String, array of strings, or undefined
115
+ * @returns Array of server names (empty if none specified)
116
+ */
117
+ function normalizeServerFilter(
118
+ serverFilter: string | string[] | undefined
119
+ ): string[] {
120
+ if (serverFilter === undefined) {
121
+ return [];
122
+ }
123
+ if (typeof serverFilter === 'string') {
124
+ return serverFilter === '' ? [] : [serverFilter];
125
+ }
126
+ return serverFilter.filter((s) => s !== '');
127
+ }
45
128
 
46
129
  /**
47
130
  * Escapes special regex characters in a string to use as a literal pattern.
@@ -170,6 +253,80 @@ function simplifyParametersForSearch(
170
253
  return { type: parameters.type };
171
254
  }
172
255
 
256
+ /**
257
+ * Performs safe local substring search without regex.
258
+ * Uses case-insensitive String.includes() for complete safety against ReDoS.
259
+ * @param tools - Array of tool metadata to search
260
+ * @param query - The search term (treated as literal substring)
261
+ * @param fields - Which fields to search
262
+ * @param maxResults - Maximum results to return
263
+ * @returns Search response with matching tools
264
+ */
265
+ function performLocalSearch(
266
+ tools: t.ToolMetadata[],
267
+ query: string,
268
+ fields: string[],
269
+ maxResults: number
270
+ ): t.ToolSearchResponse {
271
+ const lowerQuery = query.toLowerCase();
272
+ const results: t.ToolSearchResult[] = [];
273
+
274
+ for (const tool of tools) {
275
+ let bestScore = 0;
276
+ let matchedField = '';
277
+ let snippet = '';
278
+
279
+ if (fields.includes('name')) {
280
+ const lowerName = tool.name.toLowerCase();
281
+ if (lowerName.includes(lowerQuery)) {
282
+ const isExactMatch = lowerName === lowerQuery;
283
+ const startsWithQuery = lowerName.startsWith(lowerQuery);
284
+ bestScore = isExactMatch ? 1.0 : startsWithQuery ? 0.95 : 0.85;
285
+ matchedField = 'name';
286
+ snippet = tool.name;
287
+ }
288
+ }
289
+
290
+ if (fields.includes('description') && tool.description) {
291
+ const lowerDesc = tool.description.toLowerCase();
292
+ if (lowerDesc.includes(lowerQuery) && bestScore === 0) {
293
+ bestScore = 0.7;
294
+ matchedField = 'description';
295
+ snippet = tool.description.substring(0, 100);
296
+ }
297
+ }
298
+
299
+ if (fields.includes('parameters') && tool.parameters?.properties) {
300
+ const paramNames = Object.keys(tool.parameters.properties)
301
+ .join(' ')
302
+ .toLowerCase();
303
+ if (paramNames.includes(lowerQuery) && bestScore === 0) {
304
+ bestScore = 0.55;
305
+ matchedField = 'parameters';
306
+ snippet = Object.keys(tool.parameters.properties).join(' ');
307
+ }
308
+ }
309
+
310
+ if (bestScore > 0) {
311
+ results.push({
312
+ tool_name: tool.name,
313
+ match_score: bestScore,
314
+ matched_field: matchedField,
315
+ snippet,
316
+ });
317
+ }
318
+ }
319
+
320
+ results.sort((a, b) => b.match_score - a.match_score);
321
+ const topResults = results.slice(0, maxResults);
322
+
323
+ return {
324
+ tool_references: topResults,
325
+ total_tools_searched: tools.length,
326
+ pattern_used: query,
327
+ };
328
+ }
329
+
173
330
  /**
174
331
  * Generates the JavaScript search script to be executed in the sandbox.
175
332
  * Uses plain JavaScript for maximum compatibility with the Code API.
@@ -307,11 +464,87 @@ function formatSearchResults(searchResponse: t.ToolSearchResponse): string {
307
464
  }
308
465
 
309
466
  /**
310
- * Creates a Tool Search Regex tool for discovering tools from a large registry.
467
+ * Extracts the base tool name (without MCP server suffix) from a full tool name.
468
+ * @param toolName - The full tool name
469
+ * @returns The base tool name without server suffix
470
+ */
471
+ function getBaseToolName(toolName: string): string {
472
+ const delimiterIndex = toolName.indexOf(Constants.MCP_DELIMITER);
473
+ if (delimiterIndex === -1) {
474
+ return toolName;
475
+ }
476
+ return toolName.substring(0, delimiterIndex);
477
+ }
478
+
479
+ /**
480
+ * Formats a server listing response when listing all tools from MCP server(s).
481
+ * Provides a cohesive view of all tools grouped by server.
482
+ * NOTE: This is a PREVIEW only - tools are NOT discovered/loaded.
483
+ * @param tools - Array of tool metadata from the server(s)
484
+ * @param serverNames - The MCP server name(s)
485
+ * @returns Formatted string showing all tools from the server(s)
486
+ */
487
+ function formatServerListing(
488
+ tools: t.ToolMetadata[],
489
+ serverNames: string | string[]
490
+ ): string {
491
+ const servers = Array.isArray(serverNames) ? serverNames : [serverNames];
492
+
493
+ if (tools.length === 0) {
494
+ return `No tools found from MCP server(s): ${servers.join(', ')}.`;
495
+ }
496
+
497
+ const toolsByServer = new Map<string, t.ToolMetadata[]>();
498
+ for (const tool of tools) {
499
+ const server = extractMcpServerName(tool.name) ?? 'unknown';
500
+ const existing = toolsByServer.get(server) ?? [];
501
+ existing.push(tool);
502
+ toolsByServer.set(server, existing);
503
+ }
504
+
505
+ let response =
506
+ servers.length === 1
507
+ ? `## Tools from MCP server: ${servers[0]}\n\n`
508
+ : `## Tools from MCP servers: ${servers.join(', ')}\n\n`;
509
+
510
+ response += `Found ${tools.length} tool(s) (preview only - not yet loaded):\n\n`;
511
+
512
+ for (const [server, serverTools] of toolsByServer) {
513
+ if (servers.length > 1) {
514
+ response += `### ${server}\n\n`;
515
+ }
516
+ for (const tool of serverTools) {
517
+ const baseName = getBaseToolName(tool.name);
518
+ response += `- **${baseName}**`;
519
+ if (tool.description) {
520
+ const shortDesc =
521
+ tool.description.length > 80
522
+ ? tool.description.substring(0, 77) + '...'
523
+ : tool.description;
524
+ response += `: ${shortDesc}`;
525
+ }
526
+ response += '\n';
527
+ }
528
+ if (servers.length > 1) {
529
+ response += '\n';
530
+ }
531
+ }
532
+
533
+ response += `\n_To use a tool, search for it by name (e.g., query: "${getBaseToolName(tools[0]?.name ?? 'tool_name')}") to load it._`;
534
+
535
+ return response;
536
+ }
537
+
538
+ /**
539
+ * Creates a Tool Search tool for discovering tools from a large registry.
311
540
  *
312
541
  * This tool enables AI agents to dynamically discover tools from a large library
313
542
  * without loading all tool definitions into the LLM context window. The agent
314
- * can search for relevant tools on-demand using regex patterns.
543
+ * can search for relevant tools on-demand.
544
+ *
545
+ * **Modes:**
546
+ * - `code_interpreter` (default): Uses external sandbox for regex search. Safer for complex patterns.
547
+ * - `local`: Uses safe substring matching locally. No network call, faster, completely safe from ReDoS.
315
548
  *
316
549
  * The tool registry can be provided either:
317
550
  * 1. At initialization time via params.toolRegistry
@@ -321,36 +554,61 @@ function formatSearchResults(searchResponse: t.ToolSearchResponse): string {
321
554
  * @returns A LangChain DynamicStructuredTool for tool searching
322
555
  *
323
556
  * @example
324
- * // Option 1: Registry at initialization
325
- * const tool = createToolSearchRegexTool({ apiKey, toolRegistry });
326
- * await tool.invoke({ query: 'expense' });
557
+ * // Option 1: Code interpreter mode (regex via sandbox)
558
+ * const tool = createToolSearch({ apiKey, toolRegistry });
559
+ * await tool.invoke({ query: 'expense.*report' });
327
560
  *
328
561
  * @example
329
- * // Option 2: Registry at runtime
330
- * const tool = createToolSearchRegexTool({ apiKey });
331
- * await tool.invoke(
332
- * { query: 'expense' },
333
- * { configurable: { toolRegistry, onlyDeferred: true } }
334
- * );
562
+ * // Option 2: Local mode (safe substring search, no API key needed)
563
+ * const tool = createToolSearch({ mode: 'local', toolRegistry });
564
+ * await tool.invoke({ query: 'expense' });
335
565
  */
336
- function createToolSearchRegexTool(
337
- initParams: t.ToolSearchRegexParams = {}
338
- ): DynamicStructuredTool<typeof ToolSearchRegexSchema> {
339
- const apiKey: string =
340
- (initParams[EnvVar.CODE_API_KEY] as string | undefined) ??
341
- initParams.apiKey ??
342
- getEnvironmentVariable(EnvVar.CODE_API_KEY) ??
343
- '';
566
+ function createToolSearch(
567
+ initParams: t.ToolSearchParams = {}
568
+ ): DynamicStructuredTool<ReturnType<typeof createToolSearchSchema>> {
569
+ const mode: t.ToolSearchMode = initParams.mode ?? 'code_interpreter';
570
+ const defaultOnlyDeferred = initParams.onlyDeferred ?? true;
571
+ const schema = createToolSearchSchema(mode);
344
572
 
345
- if (!apiKey) {
346
- throw new Error('No API key provided for tool search regex tool.');
573
+ const apiKey: string =
574
+ mode === 'code_interpreter'
575
+ ? ((initParams[EnvVar.CODE_API_KEY] as string | undefined) ??
576
+ initParams.apiKey ??
577
+ getEnvironmentVariable(EnvVar.CODE_API_KEY) ??
578
+ '')
579
+ : '';
580
+
581
+ if (mode === 'code_interpreter' && !apiKey) {
582
+ throw new Error(
583
+ 'No API key provided for tool search in code_interpreter mode. Use mode: "local" to search without an API key.'
584
+ );
347
585
  }
348
586
 
349
587
  const baseEndpoint = initParams.baseUrl ?? getCodeBaseURL();
350
588
  const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
351
- const defaultOnlyDeferred = initParams.onlyDeferred ?? true;
352
589
 
353
- const description = `
590
+ const mcpInstructions = `
591
+
592
+ MCP Server Tools:
593
+ - Tools from MCP servers follow the naming convention: toolName${Constants.MCP_DELIMITER}serverName
594
+ - Example: "get_weather${Constants.MCP_DELIMITER}weather-api" is the "get_weather" tool from the "weather-api" server
595
+ - Use mcp_server parameter to filter by server (e.g., mcp_server: "weather-api")
596
+ - If mcp_server is provided without a query, lists ALL tools from that server`;
597
+
598
+ const description =
599
+ mode === 'local'
600
+ ? `
601
+ Searches through available tools to find ones matching your search term.
602
+
603
+ Usage:
604
+ - Provide a search term to find in tool names and descriptions.
605
+ - Uses case-insensitive substring matching (fast and safe).
606
+ - Use this when you need to discover tools for a specific task.
607
+ - Results include tool names, match quality scores, and snippets showing where the match occurred.
608
+ - Higher scores (0.95+) indicate name matches, medium scores (0.70+) indicate description matches.
609
+ ${mcpInstructions}
610
+ `.trim()
611
+ : `
354
612
  Searches through available tools to find ones matching your query pattern.
355
613
 
356
614
  Usage:
@@ -358,45 +616,42 @@ Usage:
358
616
  - Use this when you need to discover tools for a specific task.
359
617
  - Results include tool names, match quality scores, and snippets showing where the match occurred.
360
618
  - Higher scores (0.9+) indicate name matches, medium scores (0.7+) indicate description matches.
619
+ ${mcpInstructions}
361
620
  `.trim();
362
621
 
363
- return tool<typeof ToolSearchRegexSchema>(
622
+ return tool<typeof schema>(
364
623
  async (params, config) => {
365
624
  const {
366
625
  query,
367
626
  fields = ['name', 'description'],
368
627
  max_results = 10,
628
+ mcp_server,
369
629
  } = params;
370
630
 
371
- // Extra params injected by ToolNode (follows web_search pattern)
372
631
  const {
373
632
  toolRegistry: paramToolRegistry,
374
633
  onlyDeferred: paramOnlyDeferred,
634
+ mcpServer: paramMcpServer,
375
635
  } = config.toolCall ?? {};
376
636
 
377
- const { safe: sanitizedPattern, wasEscaped } = sanitizeRegex(query);
378
-
379
- let warningMessage = '';
380
- if (wasEscaped) {
381
- warningMessage =
382
- 'Note: The provided pattern was converted to a literal search for safety.\n\n';
383
- }
384
-
385
- // Priority: ToolNode injection (via config.toolCall) > initialization params
386
637
  const toolRegistry = paramToolRegistry ?? initParams.toolRegistry;
387
638
  const onlyDeferred =
388
639
  paramOnlyDeferred !== undefined
389
640
  ? paramOnlyDeferred
390
641
  : defaultOnlyDeferred;
642
+ const rawServerFilter =
643
+ mcp_server ?? paramMcpServer ?? initParams.mcpServer;
644
+ const serverFilters = normalizeServerFilter(rawServerFilter);
645
+ const hasServerFilter = serverFilters.length > 0;
391
646
 
392
647
  if (toolRegistry == null) {
393
648
  return [
394
- `${warningMessage}Error: No tool registry provided. Configure toolRegistry at agent level or initialization.`,
649
+ 'Error: No tool registry provided. Configure toolRegistry at agent level or initialization.',
395
650
  {
396
651
  tool_references: [],
397
652
  metadata: {
398
653
  total_searched: 0,
399
- pattern: sanitizedPattern,
654
+ pattern: query,
400
655
  error: 'No tool registry provided',
401
656
  },
402
657
  },
@@ -404,11 +659,16 @@ Usage:
404
659
  }
405
660
 
406
661
  const toolsArray: t.LCTool[] = Array.from(toolRegistry.values());
407
-
408
662
  const deferredTools: t.ToolMetadata[] = toolsArray
409
663
  .filter((lcTool) => {
410
- if (onlyDeferred === true) {
411
- return lcTool.defer_loading === true;
664
+ if (onlyDeferred === true && lcTool.defer_loading !== true) {
665
+ return false;
666
+ }
667
+ if (
668
+ hasServerFilter &&
669
+ !isFromAnyMcpServer(lcTool.name, serverFilters)
670
+ ) {
671
+ return false;
412
672
  }
413
673
  return true;
414
674
  })
@@ -419,18 +679,72 @@ Usage:
419
679
  }));
420
680
 
421
681
  if (deferredTools.length === 0) {
682
+ const serverMsg = hasServerFilter
683
+ ? ` from MCP server(s): ${serverFilters.join(', ')}`
684
+ : '';
422
685
  return [
423
- `${warningMessage}No tools available to search. The tool registry is empty or no deferred tools are registered.`,
686
+ `No tools available to search${serverMsg}. The tool registry is empty or no matching deferred tools are registered.`,
424
687
  {
425
688
  tool_references: [],
426
689
  metadata: {
427
690
  total_searched: 0,
428
- pattern: sanitizedPattern,
691
+ pattern: query,
692
+ mcp_server: serverFilters,
429
693
  },
430
694
  },
431
695
  ];
432
696
  }
433
697
 
698
+ const isServerListing = hasServerFilter && query === '';
699
+
700
+ if (isServerListing) {
701
+ const formattedOutput = formatServerListing(
702
+ deferredTools,
703
+ serverFilters
704
+ );
705
+
706
+ return [
707
+ formattedOutput,
708
+ {
709
+ tool_references: [],
710
+ metadata: {
711
+ total_available: deferredTools.length,
712
+ mcp_server: serverFilters,
713
+ listing_mode: true,
714
+ },
715
+ },
716
+ ];
717
+ }
718
+
719
+ if (mode === 'local') {
720
+ const searchResponse = performLocalSearch(
721
+ deferredTools,
722
+ query,
723
+ fields,
724
+ max_results
725
+ );
726
+ const formattedOutput = formatSearchResults(searchResponse);
727
+
728
+ return [
729
+ formattedOutput,
730
+ {
731
+ tool_references: searchResponse.tool_references,
732
+ metadata: {
733
+ total_searched: searchResponse.total_tools_searched,
734
+ pattern: searchResponse.pattern_used,
735
+ mcp_server: serverFilters.length > 0 ? serverFilters : undefined,
736
+ },
737
+ },
738
+ ];
739
+ }
740
+
741
+ const { safe: sanitizedPattern, wasEscaped } = sanitizeRegex(query);
742
+ let warningMessage = '';
743
+ if (wasEscaped) {
744
+ warningMessage =
745
+ 'Note: The provided pattern was converted to a literal search for safety.\n\n';
746
+ }
747
+
434
748
  const searchScript = generateSearchScript(
435
749
  deferredTools,
436
750
  fields,
@@ -468,7 +782,7 @@ Usage:
468
782
 
469
783
  if (result.stderr && result.stderr.trim()) {
470
784
  // eslint-disable-next-line no-console
471
- console.warn('[ToolSearchRegex] stderr:', result.stderr);
785
+ console.warn('[ToolSearch] stderr:', result.stderr);
472
786
  }
473
787
 
474
788
  if (!result.stdout || !result.stdout.trim()) {
@@ -499,7 +813,7 @@ Usage:
499
813
  ];
500
814
  } catch (error) {
501
815
  // eslint-disable-next-line no-console
502
- console.error('[ToolSearchRegex] Error:', error);
816
+ console.error('[ToolSearch] Error:', error);
503
817
 
504
818
  const errorMessage =
505
819
  error instanceof Error ? error.message : String(error);
@@ -517,16 +831,23 @@ Usage:
517
831
  }
518
832
  },
519
833
  {
520
- name: Constants.TOOL_SEARCH_REGEX,
834
+ name: Constants.TOOL_SEARCH,
521
835
  description,
522
- schema: ToolSearchRegexSchema,
836
+ schema,
523
837
  responseFormat: Constants.CONTENT_AND_ARTIFACT,
524
838
  }
525
839
  );
526
840
  }
527
841
 
528
842
  export {
529
- createToolSearchRegexTool,
843
+ createToolSearch,
844
+ performLocalSearch,
845
+ extractMcpServerName,
846
+ isFromMcpServer,
847
+ isFromAnyMcpServer,
848
+ normalizeServerFilter,
849
+ getBaseToolName,
850
+ formatServerListing,
530
851
  sanitizeRegex,
531
852
  escapeRegexSpecialChars,
532
853
  isDangerousPattern,
@@ -1,19 +1,19 @@
1
- // src/tools/__tests__/ToolSearchRegex.integration.test.ts
1
+ // src/tools/__tests__/ToolSearch.integration.test.ts
2
2
  /**
3
3
  * Integration tests for Tool Search Regex.
4
4
  * These tests hit the LIVE Code API and verify end-to-end search functionality.
5
5
  *
6
- * Run with: npm test -- ToolSearchRegex.integration.test.ts
6
+ * Run with: npm test -- ToolSearch.integration.test.ts
7
7
  */
8
8
  import { config as dotenvConfig } from 'dotenv';
9
9
  dotenvConfig();
10
10
 
11
11
  import { describe, it, expect, beforeAll } from '@jest/globals';
12
- import { createToolSearchRegexTool } from '../ToolSearchRegex';
12
+ import { createToolSearch } from '../ToolSearch';
13
13
  import { createToolSearchToolRegistry } from '@/test/mockTools';
14
14
 
15
- describe('ToolSearchRegex - Live API Integration', () => {
16
- let searchTool: ReturnType<typeof createToolSearchRegexTool>;
15
+ describe('ToolSearch - Live API Integration', () => {
16
+ let searchTool: ReturnType<typeof createToolSearch>;
17
17
  const toolRegistry = createToolSearchToolRegistry();
18
18
 
19
19
  beforeAll(() => {
@@ -24,7 +24,7 @@ describe('ToolSearchRegex - Live API Integration', () => {
24
24
  );
25
25
  }
26
26
 
27
- searchTool = createToolSearchRegexTool({ apiKey, toolRegistry });
27
+ searchTool = createToolSearch({ apiKey, toolRegistry });
28
28
  });
29
29
 
30
30
  it('searches for expense-related tools', async () => {