@librechat/agents 3.0.67 → 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.
- package/dist/cjs/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/main.cjs +6 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +175 -8
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/tools/ToolSearch.mjs +170 -9
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +3 -1
- package/dist/types/tools/ToolSearch.d.ts +45 -2
- package/dist/types/types/tools.d.ts +3 -1
- package/package.json +1 -1
- package/src/common/enum.ts +2 -0
- package/src/tools/ToolSearch.ts +209 -9
- package/src/tools/__tests__/ToolSearch.test.ts +294 -1
- package/src/types/tools.ts +3 -1
package/src/tools/ToolSearch.ts
CHANGED
|
@@ -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.
|
|
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
|
|
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,71 @@ 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
|
+
|
|
61
129
|
/**
|
|
62
130
|
* Escapes special regex characters in a string to use as a literal pattern.
|
|
63
131
|
* @param pattern - The string to escape
|
|
@@ -395,6 +463,78 @@ function formatSearchResults(searchResponse: t.ToolSearchResponse): string {
|
|
|
395
463
|
return response;
|
|
396
464
|
}
|
|
397
465
|
|
|
466
|
+
/**
|
|
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
|
+
|
|
398
538
|
/**
|
|
399
539
|
* Creates a Tool Search tool for discovering tools from a large registry.
|
|
400
540
|
*
|
|
@@ -447,6 +587,14 @@ function createToolSearch(
|
|
|
447
587
|
const baseEndpoint = initParams.baseUrl ?? getCodeBaseURL();
|
|
448
588
|
const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
|
|
449
589
|
|
|
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
|
+
|
|
450
598
|
const description =
|
|
451
599
|
mode === 'local'
|
|
452
600
|
? `
|
|
@@ -458,6 +606,7 @@ Usage:
|
|
|
458
606
|
- Use this when you need to discover tools for a specific task.
|
|
459
607
|
- Results include tool names, match quality scores, and snippets showing where the match occurred.
|
|
460
608
|
- Higher scores (0.95+) indicate name matches, medium scores (0.70+) indicate description matches.
|
|
609
|
+
${mcpInstructions}
|
|
461
610
|
`.trim()
|
|
462
611
|
: `
|
|
463
612
|
Searches through available tools to find ones matching your query pattern.
|
|
@@ -467,6 +616,7 @@ Usage:
|
|
|
467
616
|
- Use this when you need to discover tools for a specific task.
|
|
468
617
|
- Results include tool names, match quality scores, and snippets showing where the match occurred.
|
|
469
618
|
- Higher scores (0.9+) indicate name matches, medium scores (0.7+) indicate description matches.
|
|
619
|
+
${mcpInstructions}
|
|
470
620
|
`.trim();
|
|
471
621
|
|
|
472
622
|
return tool<typeof schema>(
|
|
@@ -475,11 +625,13 @@ Usage:
|
|
|
475
625
|
query,
|
|
476
626
|
fields = ['name', 'description'],
|
|
477
627
|
max_results = 10,
|
|
628
|
+
mcp_server,
|
|
478
629
|
} = params;
|
|
479
630
|
|
|
480
631
|
const {
|
|
481
632
|
toolRegistry: paramToolRegistry,
|
|
482
633
|
onlyDeferred: paramOnlyDeferred,
|
|
634
|
+
mcpServer: paramMcpServer,
|
|
483
635
|
} = config.toolCall ?? {};
|
|
484
636
|
|
|
485
637
|
const toolRegistry = paramToolRegistry ?? initParams.toolRegistry;
|
|
@@ -487,6 +639,10 @@ Usage:
|
|
|
487
639
|
paramOnlyDeferred !== undefined
|
|
488
640
|
? paramOnlyDeferred
|
|
489
641
|
: defaultOnlyDeferred;
|
|
642
|
+
const rawServerFilter =
|
|
643
|
+
mcp_server ?? paramMcpServer ?? initParams.mcpServer;
|
|
644
|
+
const serverFilters = normalizeServerFilter(rawServerFilter);
|
|
645
|
+
const hasServerFilter = serverFilters.length > 0;
|
|
490
646
|
|
|
491
647
|
if (toolRegistry == null) {
|
|
492
648
|
return [
|
|
@@ -504,9 +660,18 @@ Usage:
|
|
|
504
660
|
|
|
505
661
|
const toolsArray: t.LCTool[] = Array.from(toolRegistry.values());
|
|
506
662
|
const deferredTools: t.ToolMetadata[] = toolsArray
|
|
507
|
-
.filter((lcTool) =>
|
|
508
|
-
onlyDeferred === true
|
|
509
|
-
|
|
663
|
+
.filter((lcTool) => {
|
|
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;
|
|
672
|
+
}
|
|
673
|
+
return true;
|
|
674
|
+
})
|
|
510
675
|
.map((lcTool) => ({
|
|
511
676
|
name: lcTool.name,
|
|
512
677
|
description: lcTool.description ?? '',
|
|
@@ -514,11 +679,39 @@ Usage:
|
|
|
514
679
|
}));
|
|
515
680
|
|
|
516
681
|
if (deferredTools.length === 0) {
|
|
682
|
+
const serverMsg = hasServerFilter
|
|
683
|
+
? ` from MCP server(s): ${serverFilters.join(', ')}`
|
|
684
|
+
: '';
|
|
517
685
|
return [
|
|
518
|
-
|
|
686
|
+
`No tools available to search${serverMsg}. The tool registry is empty or no matching deferred tools are registered.`,
|
|
519
687
|
{
|
|
520
688
|
tool_references: [],
|
|
521
|
-
metadata: {
|
|
689
|
+
metadata: {
|
|
690
|
+
total_searched: 0,
|
|
691
|
+
pattern: query,
|
|
692
|
+
mcp_server: serverFilters,
|
|
693
|
+
},
|
|
694
|
+
},
|
|
695
|
+
];
|
|
696
|
+
}
|
|
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
|
+
},
|
|
522
715
|
},
|
|
523
716
|
];
|
|
524
717
|
}
|
|
@@ -539,6 +732,7 @@ Usage:
|
|
|
539
732
|
metadata: {
|
|
540
733
|
total_searched: searchResponse.total_tools_searched,
|
|
541
734
|
pattern: searchResponse.pattern_used,
|
|
735
|
+
mcp_server: serverFilters.length > 0 ? serverFilters : undefined,
|
|
542
736
|
},
|
|
543
737
|
},
|
|
544
738
|
];
|
|
@@ -648,6 +842,12 @@ Usage:
|
|
|
648
842
|
export {
|
|
649
843
|
createToolSearch,
|
|
650
844
|
performLocalSearch,
|
|
845
|
+
extractMcpServerName,
|
|
846
|
+
isFromMcpServer,
|
|
847
|
+
isFromAnyMcpServer,
|
|
848
|
+
normalizeServerFilter,
|
|
849
|
+
getBaseToolName,
|
|
850
|
+
formatServerListing,
|
|
651
851
|
sanitizeRegex,
|
|
652
852
|
escapeRegexSpecialChars,
|
|
653
853
|
isDangerousPattern,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/tools/__tests__/ToolSearch.test.ts
|
|
2
2
|
/**
|
|
3
|
-
* Unit tests for Tool Search
|
|
3
|
+
* Unit tests for Tool Search.
|
|
4
4
|
* Tests helper functions and sanitization logic without hitting the API.
|
|
5
5
|
*/
|
|
6
6
|
import { describe, it, expect } from '@jest/globals';
|
|
@@ -11,6 +11,12 @@ import {
|
|
|
11
11
|
countNestedGroups,
|
|
12
12
|
hasNestedQuantifiers,
|
|
13
13
|
performLocalSearch,
|
|
14
|
+
extractMcpServerName,
|
|
15
|
+
isFromMcpServer,
|
|
16
|
+
isFromAnyMcpServer,
|
|
17
|
+
normalizeServerFilter,
|
|
18
|
+
getBaseToolName,
|
|
19
|
+
formatServerListing,
|
|
14
20
|
} from '../ToolSearch';
|
|
15
21
|
import type { ToolMetadata } from '@/types';
|
|
16
22
|
|
|
@@ -438,4 +444,291 @@ describe('ToolSearch', () => {
|
|
|
438
444
|
expect(result.tool_references[0].snippet.length).toBeGreaterThan(0);
|
|
439
445
|
});
|
|
440
446
|
});
|
|
447
|
+
|
|
448
|
+
describe('extractMcpServerName', () => {
|
|
449
|
+
it('extracts server name from MCP tool name', () => {
|
|
450
|
+
expect(extractMcpServerName('get_weather_mcp_weather-server')).toBe(
|
|
451
|
+
'weather-server'
|
|
452
|
+
);
|
|
453
|
+
expect(extractMcpServerName('send_email_mcp_gmail')).toBe('gmail');
|
|
454
|
+
expect(extractMcpServerName('query_database_mcp_postgres-mcp')).toBe(
|
|
455
|
+
'postgres-mcp'
|
|
456
|
+
);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('returns undefined for non-MCP tools', () => {
|
|
460
|
+
expect(extractMcpServerName('get_weather')).toBeUndefined();
|
|
461
|
+
expect(extractMcpServerName('send_email')).toBeUndefined();
|
|
462
|
+
expect(extractMcpServerName('regular_tool_name')).toBeUndefined();
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('handles edge cases', () => {
|
|
466
|
+
expect(extractMcpServerName('_mcp_server')).toBe('server');
|
|
467
|
+
expect(extractMcpServerName('tool_mcp_')).toBe('');
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
describe('getBaseToolName', () => {
|
|
472
|
+
it('extracts base name from MCP tool name', () => {
|
|
473
|
+
expect(getBaseToolName('get_weather_mcp_weather-server')).toBe(
|
|
474
|
+
'get_weather'
|
|
475
|
+
);
|
|
476
|
+
expect(getBaseToolName('send_email_mcp_gmail')).toBe('send_email');
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('returns full name for non-MCP tools', () => {
|
|
480
|
+
expect(getBaseToolName('get_weather')).toBe('get_weather');
|
|
481
|
+
expect(getBaseToolName('regular_tool')).toBe('regular_tool');
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
describe('isFromMcpServer', () => {
|
|
486
|
+
it('returns true for matching MCP server', () => {
|
|
487
|
+
expect(
|
|
488
|
+
isFromMcpServer('get_weather_mcp_weather-server', 'weather-server')
|
|
489
|
+
).toBe(true);
|
|
490
|
+
expect(isFromMcpServer('send_email_mcp_gmail', 'gmail')).toBe(true);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it('returns false for non-matching MCP server', () => {
|
|
494
|
+
expect(
|
|
495
|
+
isFromMcpServer('get_weather_mcp_weather-server', 'other-server')
|
|
496
|
+
).toBe(false);
|
|
497
|
+
expect(isFromMcpServer('send_email_mcp_gmail', 'outlook')).toBe(false);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('returns false for non-MCP tools', () => {
|
|
501
|
+
expect(isFromMcpServer('get_weather', 'weather-server')).toBe(false);
|
|
502
|
+
expect(isFromMcpServer('regular_tool', 'any-server')).toBe(false);
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
describe('isFromAnyMcpServer', () => {
|
|
507
|
+
it('returns true if tool is from any of the specified servers', () => {
|
|
508
|
+
expect(
|
|
509
|
+
isFromAnyMcpServer('get_weather_mcp_weather-api', [
|
|
510
|
+
'weather-api',
|
|
511
|
+
'gmail',
|
|
512
|
+
])
|
|
513
|
+
).toBe(true);
|
|
514
|
+
expect(
|
|
515
|
+
isFromAnyMcpServer('send_email_mcp_gmail', ['weather-api', 'gmail'])
|
|
516
|
+
).toBe(true);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('returns false if tool is not from any specified server', () => {
|
|
520
|
+
expect(
|
|
521
|
+
isFromAnyMcpServer('get_weather_mcp_weather-api', ['gmail', 'slack'])
|
|
522
|
+
).toBe(false);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('returns false for non-MCP tools', () => {
|
|
526
|
+
expect(isFromAnyMcpServer('regular_tool', ['weather-api', 'gmail'])).toBe(
|
|
527
|
+
false
|
|
528
|
+
);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('returns false for empty server list', () => {
|
|
532
|
+
expect(isFromAnyMcpServer('get_weather_mcp_weather-api', [])).toBe(false);
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
describe('normalizeServerFilter', () => {
|
|
537
|
+
it('converts string to single-element array', () => {
|
|
538
|
+
expect(normalizeServerFilter('gmail')).toEqual(['gmail']);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('passes through arrays unchanged', () => {
|
|
542
|
+
expect(normalizeServerFilter(['gmail', 'slack'])).toEqual([
|
|
543
|
+
'gmail',
|
|
544
|
+
'slack',
|
|
545
|
+
]);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it('returns empty array for undefined', () => {
|
|
549
|
+
expect(normalizeServerFilter(undefined)).toEqual([]);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it('returns empty array for empty string', () => {
|
|
553
|
+
expect(normalizeServerFilter('')).toEqual([]);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it('filters out empty strings from arrays', () => {
|
|
557
|
+
expect(normalizeServerFilter(['gmail', '', 'slack'])).toEqual([
|
|
558
|
+
'gmail',
|
|
559
|
+
'slack',
|
|
560
|
+
]);
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
describe('performLocalSearch with MCP tools', () => {
|
|
565
|
+
const mcpTools: ToolMetadata[] = [
|
|
566
|
+
{
|
|
567
|
+
name: 'get_weather_mcp_weather-server',
|
|
568
|
+
description: 'Get weather from MCP server',
|
|
569
|
+
parameters: undefined,
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
name: 'get_forecast_mcp_weather-server',
|
|
573
|
+
description: 'Get forecast from MCP server',
|
|
574
|
+
parameters: undefined,
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
name: 'send_email_mcp_gmail',
|
|
578
|
+
description: 'Send email via Gmail MCP',
|
|
579
|
+
parameters: undefined,
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
name: 'read_inbox_mcp_gmail',
|
|
583
|
+
description: 'Read inbox via Gmail MCP',
|
|
584
|
+
parameters: undefined,
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
name: 'get_weather',
|
|
588
|
+
description: 'Regular weather tool (not MCP)',
|
|
589
|
+
parameters: undefined,
|
|
590
|
+
},
|
|
591
|
+
];
|
|
592
|
+
|
|
593
|
+
it('searches across all tools including MCP tools', () => {
|
|
594
|
+
const result = performLocalSearch(
|
|
595
|
+
mcpTools,
|
|
596
|
+
'weather',
|
|
597
|
+
['name', 'description'],
|
|
598
|
+
10
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
expect(result.tool_references.length).toBe(3);
|
|
602
|
+
expect(result.tool_references.map((r) => r.tool_name)).toContain(
|
|
603
|
+
'get_weather_mcp_weather-server'
|
|
604
|
+
);
|
|
605
|
+
expect(result.tool_references.map((r) => r.tool_name)).toContain(
|
|
606
|
+
'get_weather'
|
|
607
|
+
);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it('finds MCP tools by searching the full name including server suffix', () => {
|
|
611
|
+
const result = performLocalSearch(mcpTools, 'gmail', ['name'], 10);
|
|
612
|
+
|
|
613
|
+
expect(result.tool_references.length).toBe(2);
|
|
614
|
+
expect(result.tool_references.map((r) => r.tool_name)).toContain(
|
|
615
|
+
'send_email_mcp_gmail'
|
|
616
|
+
);
|
|
617
|
+
expect(result.tool_references.map((r) => r.tool_name)).toContain(
|
|
618
|
+
'read_inbox_mcp_gmail'
|
|
619
|
+
);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
it('can search for tools by MCP delimiter', () => {
|
|
623
|
+
const result = performLocalSearch(mcpTools, '_mcp_', ['name'], 10);
|
|
624
|
+
|
|
625
|
+
expect(result.tool_references.length).toBe(4);
|
|
626
|
+
expect(result.tool_references.map((r) => r.tool_name)).not.toContain(
|
|
627
|
+
'get_weather'
|
|
628
|
+
);
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
describe('formatServerListing', () => {
|
|
633
|
+
const serverTools: ToolMetadata[] = [
|
|
634
|
+
{
|
|
635
|
+
name: 'get_weather_mcp_weather-api',
|
|
636
|
+
description: 'Get current weather conditions for a location',
|
|
637
|
+
parameters: undefined,
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
name: 'get_forecast_mcp_weather-api',
|
|
641
|
+
description: 'Get weather forecast for the next 7 days',
|
|
642
|
+
parameters: undefined,
|
|
643
|
+
},
|
|
644
|
+
];
|
|
645
|
+
|
|
646
|
+
it('formats server listing with tool names and descriptions', () => {
|
|
647
|
+
const result = formatServerListing(serverTools, 'weather-api');
|
|
648
|
+
|
|
649
|
+
expect(result).toContain('Tools from MCP server: weather-api');
|
|
650
|
+
expect(result).toContain('2 tool(s)');
|
|
651
|
+
expect(result).toContain('get_weather');
|
|
652
|
+
expect(result).toContain('get_forecast');
|
|
653
|
+
expect(result).toContain('preview only');
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
it('includes hint to search for specific tool to load it', () => {
|
|
657
|
+
const result = formatServerListing(serverTools, 'weather-api');
|
|
658
|
+
|
|
659
|
+
expect(result).toContain('To use a tool, search for it by name');
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('uses base tool name (without MCP suffix) in display', () => {
|
|
663
|
+
const result = formatServerListing(serverTools, 'weather-api');
|
|
664
|
+
|
|
665
|
+
expect(result).toContain('**get_weather**');
|
|
666
|
+
expect(result).not.toContain('**get_weather_mcp_weather-api**');
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it('handles empty tools array', () => {
|
|
670
|
+
const result = formatServerListing([], 'empty-server');
|
|
671
|
+
|
|
672
|
+
expect(result).toContain('No tools found');
|
|
673
|
+
expect(result).toContain('empty-server');
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it('truncates long descriptions', () => {
|
|
677
|
+
const toolsWithLongDesc: ToolMetadata[] = [
|
|
678
|
+
{
|
|
679
|
+
name: 'long_tool_mcp_server',
|
|
680
|
+
description:
|
|
681
|
+
'This is a very long description that exceeds 80 characters and should be truncated to keep the listing compact and readable.',
|
|
682
|
+
parameters: undefined,
|
|
683
|
+
},
|
|
684
|
+
];
|
|
685
|
+
|
|
686
|
+
const result = formatServerListing(toolsWithLongDesc, 'server');
|
|
687
|
+
|
|
688
|
+
expect(result).toContain('...');
|
|
689
|
+
expect(result.length).toBeLessThan(
|
|
690
|
+
toolsWithLongDesc[0].description.length + 200
|
|
691
|
+
);
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
it('handles multiple servers with grouped output', () => {
|
|
695
|
+
const multiServerTools: ToolMetadata[] = [
|
|
696
|
+
{
|
|
697
|
+
name: 'get_weather_mcp_weather-api',
|
|
698
|
+
description: 'Get weather',
|
|
699
|
+
parameters: undefined,
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
name: 'send_email_mcp_gmail',
|
|
703
|
+
description: 'Send email',
|
|
704
|
+
parameters: undefined,
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
name: 'read_inbox_mcp_gmail',
|
|
708
|
+
description: 'Read inbox',
|
|
709
|
+
parameters: undefined,
|
|
710
|
+
},
|
|
711
|
+
];
|
|
712
|
+
|
|
713
|
+
const result = formatServerListing(multiServerTools, [
|
|
714
|
+
'weather-api',
|
|
715
|
+
'gmail',
|
|
716
|
+
]);
|
|
717
|
+
|
|
718
|
+
expect(result).toContain('Tools from MCP servers: weather-api, gmail');
|
|
719
|
+
expect(result).toContain('3 tool(s)');
|
|
720
|
+
expect(result).toContain('### weather-api');
|
|
721
|
+
expect(result).toContain('### gmail');
|
|
722
|
+
expect(result).toContain('get_weather');
|
|
723
|
+
expect(result).toContain('send_email');
|
|
724
|
+
expect(result).toContain('read_inbox');
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
it('accepts single server as array', () => {
|
|
728
|
+
const result = formatServerListing(serverTools, ['weather-api']);
|
|
729
|
+
|
|
730
|
+
expect(result).toContain('Tools from MCP server: weather-api');
|
|
731
|
+
expect(result).not.toContain('###');
|
|
732
|
+
});
|
|
733
|
+
});
|
|
441
734
|
});
|
package/src/types/tools.ts
CHANGED
|
@@ -131,7 +131,7 @@ export type ProgrammaticCache = { toolMap: ToolMap; toolDefs: LCTool[] };
|
|
|
131
131
|
/** Search mode: code_interpreter uses external sandbox, local uses safe substring matching */
|
|
132
132
|
export type ToolSearchMode = 'code_interpreter' | 'local';
|
|
133
133
|
|
|
134
|
-
/** Parameters for creating a Tool Search
|
|
134
|
+
/** Parameters for creating a Tool Search tool */
|
|
135
135
|
export type ToolSearchParams = {
|
|
136
136
|
apiKey?: string;
|
|
137
137
|
toolRegistry?: LCToolRegistry;
|
|
@@ -139,6 +139,8 @@ export type ToolSearchParams = {
|
|
|
139
139
|
baseUrl?: string;
|
|
140
140
|
/** Search mode: 'code_interpreter' (default) uses sandbox for regex, 'local' uses safe substring matching */
|
|
141
141
|
mode?: ToolSearchMode;
|
|
142
|
+
/** Filter tools to only those from specific MCP server(s). Can be a single name or array of names. */
|
|
143
|
+
mcpServer?: string | string[];
|
|
142
144
|
[key: string]: unknown;
|
|
143
145
|
};
|
|
144
146
|
|