@librechat/agents 3.0.67 → 3.0.69

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.
@@ -22,11 +22,12 @@ const SEARCH_TIMEOUT = 5000;
22
22
 
23
23
  /** Zod schema type for tool search parameters */
24
24
  type ToolSearchSchema = z.ZodObject<{
25
- query: z.ZodString;
25
+ query: z.ZodDefault<z.ZodOptional<z.ZodString>>;
26
26
  fields: z.ZodDefault<
27
27
  z.ZodOptional<z.ZodArray<z.ZodEnum<['name', 'description', 'parameters']>>>
28
28
  >;
29
29
  max_results: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
30
+ mcp_server: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString>]>>;
30
31
  }>;
31
32
 
32
33
  /**
@@ -37,11 +38,16 @@ type ToolSearchSchema = z.ZodObject<{
37
38
  function createToolSearchSchema(mode: t.ToolSearchMode): ToolSearchSchema {
38
39
  const queryDescription =
39
40
  mode === 'local'
40
- ? 'Search term to find in tool names and descriptions. Case-insensitive substring matching.'
41
- : 'Regex pattern to search tool names and descriptions. Special regex characters will be sanitized for safety.';
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.';
42
43
 
43
44
  return z.object({
44
- query: z.string().min(1).max(MAX_PATTERN_LENGTH).describe(queryDescription),
45
+ query: z
46
+ .string()
47
+ .max(MAX_PATTERN_LENGTH)
48
+ .optional()
49
+ .default('')
50
+ .describe(queryDescription),
45
51
  fields: z
46
52
  .array(z.enum(['name', 'description', 'parameters']))
47
53
  .optional()
@@ -55,9 +61,99 @@ function createToolSearchSchema(mode: t.ToolSearchMode): ToolSearchSchema {
55
61
  .optional()
56
62
  .default(10)
57
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
+ ),
58
70
  });
59
71
  }
60
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
+ }
128
+
129
+ /**
130
+ * Extracts all unique MCP server names from a tool registry.
131
+ * @param toolRegistry - The tool registry to scan
132
+ * @param onlyDeferred - If true, only considers deferred tools
133
+ * @returns Array of unique server names, sorted alphabetically
134
+ */
135
+ function getAvailableMcpServers(
136
+ toolRegistry: t.LCToolRegistry | undefined,
137
+ onlyDeferred: boolean = true
138
+ ): string[] {
139
+ if (!toolRegistry) {
140
+ return [];
141
+ }
142
+
143
+ const servers = new Set<string>();
144
+ for (const [, toolDef] of toolRegistry) {
145
+ if (onlyDeferred && toolDef.defer_loading !== true) {
146
+ continue;
147
+ }
148
+ const server = extractMcpServerName(toolDef.name);
149
+ if (server !== undefined && server !== '') {
150
+ servers.add(server);
151
+ }
152
+ }
153
+
154
+ return Array.from(servers).sort();
155
+ }
156
+
61
157
  /**
62
158
  * Escapes special regex characters in a string to use as a literal pattern.
63
159
  * @param pattern - The string to escape
@@ -395,6 +491,78 @@ function formatSearchResults(searchResponse: t.ToolSearchResponse): string {
395
491
  return response;
396
492
  }
397
493
 
494
+ /**
495
+ * Extracts the base tool name (without MCP server suffix) from a full tool name.
496
+ * @param toolName - The full tool name
497
+ * @returns The base tool name without server suffix
498
+ */
499
+ function getBaseToolName(toolName: string): string {
500
+ const delimiterIndex = toolName.indexOf(Constants.MCP_DELIMITER);
501
+ if (delimiterIndex === -1) {
502
+ return toolName;
503
+ }
504
+ return toolName.substring(0, delimiterIndex);
505
+ }
506
+
507
+ /**
508
+ * Formats a server listing response when listing all tools from MCP server(s).
509
+ * Provides a cohesive view of all tools grouped by server.
510
+ * NOTE: This is a PREVIEW only - tools are NOT discovered/loaded.
511
+ * @param tools - Array of tool metadata from the server(s)
512
+ * @param serverNames - The MCP server name(s)
513
+ * @returns Formatted string showing all tools from the server(s)
514
+ */
515
+ function formatServerListing(
516
+ tools: t.ToolMetadata[],
517
+ serverNames: string | string[]
518
+ ): string {
519
+ const servers = Array.isArray(serverNames) ? serverNames : [serverNames];
520
+
521
+ if (tools.length === 0) {
522
+ return `No tools found from MCP server(s): ${servers.join(', ')}.`;
523
+ }
524
+
525
+ const toolsByServer = new Map<string, t.ToolMetadata[]>();
526
+ for (const tool of tools) {
527
+ const server = extractMcpServerName(tool.name) ?? 'unknown';
528
+ const existing = toolsByServer.get(server) ?? [];
529
+ existing.push(tool);
530
+ toolsByServer.set(server, existing);
531
+ }
532
+
533
+ let response =
534
+ servers.length === 1
535
+ ? `## Tools from MCP server: ${servers[0]}\n\n`
536
+ : `## Tools from MCP servers: ${servers.join(', ')}\n\n`;
537
+
538
+ response += `Found ${tools.length} tool(s) (preview only - not yet loaded):\n\n`;
539
+
540
+ for (const [server, serverTools] of toolsByServer) {
541
+ if (servers.length > 1) {
542
+ response += `### ${server}\n\n`;
543
+ }
544
+ for (const tool of serverTools) {
545
+ const baseName = getBaseToolName(tool.name);
546
+ response += `- **${baseName}**`;
547
+ if (tool.description) {
548
+ const shortDesc =
549
+ tool.description.length > 80
550
+ ? tool.description.substring(0, 77) + '...'
551
+ : tool.description;
552
+ response += `: ${shortDesc}`;
553
+ }
554
+ response += '\n';
555
+ }
556
+ if (servers.length > 1) {
557
+ response += '\n';
558
+ }
559
+ }
560
+
561
+ response += `\n_To use a tool, search for it by name (e.g., query: "${getBaseToolName(tools[0]?.name ?? 'tool_name')}") to load it._`;
562
+
563
+ return response;
564
+ }
565
+
398
566
  /**
399
567
  * Creates a Tool Search tool for discovering tools from a large registry.
400
568
  *
@@ -447,6 +615,24 @@ function createToolSearch(
447
615
  const baseEndpoint = initParams.baseUrl ?? getCodeBaseURL();
448
616
  const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
449
617
 
618
+ const availableServers = getAvailableMcpServers(
619
+ initParams.toolRegistry,
620
+ defaultOnlyDeferred
621
+ );
622
+
623
+ const serverListText =
624
+ availableServers.length > 0
625
+ ? `\n- Available MCP servers: ${availableServers.join(', ')}`
626
+ : '';
627
+
628
+ const mcpInstructions = `
629
+
630
+ MCP Server Tools:
631
+ - Tools from MCP servers follow the naming convention: toolName${Constants.MCP_DELIMITER}serverName
632
+ - Example: "get_weather${Constants.MCP_DELIMITER}weather-api" is the "get_weather" tool from the "weather-api" server
633
+ - Use mcp_server parameter to filter by server (e.g., mcp_server: "weather-api")
634
+ - If mcp_server is provided without a query, lists ALL tools from that server${serverListText}`;
635
+
450
636
  const description =
451
637
  mode === 'local'
452
638
  ? `
@@ -458,6 +644,7 @@ Usage:
458
644
  - Use this when you need to discover tools for a specific task.
459
645
  - Results include tool names, match quality scores, and snippets showing where the match occurred.
460
646
  - Higher scores (0.95+) indicate name matches, medium scores (0.70+) indicate description matches.
647
+ ${mcpInstructions}
461
648
  `.trim()
462
649
  : `
463
650
  Searches through available tools to find ones matching your query pattern.
@@ -467,6 +654,7 @@ Usage:
467
654
  - Use this when you need to discover tools for a specific task.
468
655
  - Results include tool names, match quality scores, and snippets showing where the match occurred.
469
656
  - Higher scores (0.9+) indicate name matches, medium scores (0.7+) indicate description matches.
657
+ ${mcpInstructions}
470
658
  `.trim();
471
659
 
472
660
  return tool<typeof schema>(
@@ -475,11 +663,13 @@ Usage:
475
663
  query,
476
664
  fields = ['name', 'description'],
477
665
  max_results = 10,
666
+ mcp_server,
478
667
  } = params;
479
668
 
480
669
  const {
481
670
  toolRegistry: paramToolRegistry,
482
671
  onlyDeferred: paramOnlyDeferred,
672
+ mcpServer: paramMcpServer,
483
673
  } = config.toolCall ?? {};
484
674
 
485
675
  const toolRegistry = paramToolRegistry ?? initParams.toolRegistry;
@@ -487,6 +677,10 @@ Usage:
487
677
  paramOnlyDeferred !== undefined
488
678
  ? paramOnlyDeferred
489
679
  : defaultOnlyDeferred;
680
+ const rawServerFilter =
681
+ mcp_server ?? paramMcpServer ?? initParams.mcpServer;
682
+ const serverFilters = normalizeServerFilter(rawServerFilter);
683
+ const hasServerFilter = serverFilters.length > 0;
490
684
 
491
685
  if (toolRegistry == null) {
492
686
  return [
@@ -504,9 +698,18 @@ Usage:
504
698
 
505
699
  const toolsArray: t.LCTool[] = Array.from(toolRegistry.values());
506
700
  const deferredTools: t.ToolMetadata[] = toolsArray
507
- .filter((lcTool) =>
508
- onlyDeferred === true ? lcTool.defer_loading === true : true
509
- )
701
+ .filter((lcTool) => {
702
+ if (onlyDeferred === true && lcTool.defer_loading !== true) {
703
+ return false;
704
+ }
705
+ if (
706
+ hasServerFilter &&
707
+ !isFromAnyMcpServer(lcTool.name, serverFilters)
708
+ ) {
709
+ return false;
710
+ }
711
+ return true;
712
+ })
510
713
  .map((lcTool) => ({
511
714
  name: lcTool.name,
512
715
  description: lcTool.description ?? '',
@@ -514,11 +717,39 @@ Usage:
514
717
  }));
515
718
 
516
719
  if (deferredTools.length === 0) {
720
+ const serverMsg = hasServerFilter
721
+ ? ` from MCP server(s): ${serverFilters.join(', ')}`
722
+ : '';
517
723
  return [
518
- 'No tools available to search. The tool registry is empty or no deferred tools are registered.',
724
+ `No tools available to search${serverMsg}. The tool registry is empty or no matching deferred tools are registered.`,
519
725
  {
520
726
  tool_references: [],
521
- metadata: { total_searched: 0, pattern: query },
727
+ metadata: {
728
+ total_searched: 0,
729
+ pattern: query,
730
+ mcp_server: serverFilters,
731
+ },
732
+ },
733
+ ];
734
+ }
735
+
736
+ const isServerListing = hasServerFilter && query === '';
737
+
738
+ if (isServerListing) {
739
+ const formattedOutput = formatServerListing(
740
+ deferredTools,
741
+ serverFilters
742
+ );
743
+
744
+ return [
745
+ formattedOutput,
746
+ {
747
+ tool_references: [],
748
+ metadata: {
749
+ total_available: deferredTools.length,
750
+ mcp_server: serverFilters,
751
+ listing_mode: true,
752
+ },
522
753
  },
523
754
  ];
524
755
  }
@@ -539,6 +770,7 @@ Usage:
539
770
  metadata: {
540
771
  total_searched: searchResponse.total_tools_searched,
541
772
  pattern: searchResponse.pattern_used,
773
+ mcp_server: serverFilters.length > 0 ? serverFilters : undefined,
542
774
  },
543
775
  },
544
776
  ];
@@ -648,6 +880,13 @@ Usage:
648
880
  export {
649
881
  createToolSearch,
650
882
  performLocalSearch,
883
+ extractMcpServerName,
884
+ isFromMcpServer,
885
+ isFromAnyMcpServer,
886
+ normalizeServerFilter,
887
+ getAvailableMcpServers,
888
+ getBaseToolName,
889
+ formatServerListing,
651
890
  sanitizeRegex,
652
891
  escapeRegexSpecialChars,
653
892
  isDangerousPattern,