@mcp-ts/sdk 1.5.3 → 1.6.1
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/adapters/agui-adapter.d.mts +2 -2
- package/dist/adapters/agui-adapter.d.ts +2 -2
- package/dist/adapters/agui-adapter.js +69 -18
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +69 -18
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +2 -2
- package/dist/adapters/agui-middleware.d.ts +2 -2
- package/dist/adapters/ai-adapter.d.mts +2 -2
- package/dist/adapters/ai-adapter.d.ts +2 -2
- package/dist/adapters/ai-adapter.js +69 -18
- package/dist/adapters/ai-adapter.js.map +1 -1
- package/dist/adapters/ai-adapter.mjs +69 -18
- package/dist/adapters/ai-adapter.mjs.map +1 -1
- package/dist/adapters/langchain-adapter.d.mts +2 -2
- package/dist/adapters/langchain-adapter.d.ts +2 -2
- package/dist/adapters/langchain-adapter.js +69 -18
- package/dist/adapters/langchain-adapter.js.map +1 -1
- package/dist/adapters/langchain-adapter.mjs +69 -18
- package/dist/adapters/langchain-adapter.mjs.map +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/react.d.mts +4 -4
- package/dist/client/react.d.ts +4 -4
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +4 -4
- package/dist/client/vue.d.ts +4 -4
- package/dist/{index-GfC_eNEv.d.ts → index-DhA-OEAe.d.ts} +1 -1
- package/dist/{index-DcYfpY3H.d.mts → index-bFL4ZF2N.d.mts} +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +212 -44
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +212 -45
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +13 -5
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +13 -5
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +17 -10
- package/dist/shared/index.d.ts +17 -10
- package/dist/shared/index.js +199 -39
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +199 -40
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-DsKhRmJm.d.ts → tool-router-BVaV1udm.d.mts} +57 -8
- package/dist/{tool-router-DK0RJblO.d.mts → tool-router-Dh2804tM.d.ts} +57 -8
- package/dist/{types-CfCoIsWI.d.mts → types-rIuN1CQi.d.mts} +1 -0
- package/dist/{types-CfCoIsWI.d.ts → types-rIuN1CQi.d.ts} +1 -0
- package/package.json +3 -1
- package/src/server/handlers/sse-handler.ts +12 -0
- package/src/server/mcp/oauth-client.ts +10 -6
- package/src/shared/index.ts +4 -0
- package/src/shared/meta-tools.ts +163 -37
- package/src/shared/tool-index.ts +123 -7
- package/src/shared/tool-router.ts +40 -7
- package/src/shared/types.ts +1 -0
package/src/shared/meta-tools.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* only the tools it actually needs.
|
|
8
8
|
*
|
|
9
9
|
* Meta-tools:
|
|
10
|
-
* • `
|
|
10
|
+
* • `mcp_search_tools` — Search/list available tools
|
|
11
11
|
* • `mcp_search_tool_regex` — Regex pattern search
|
|
12
12
|
* • `mcp_get_tool_schema` — Get full inputSchema for a discovered tool
|
|
13
13
|
* • `mcp_execute_tool` — Execute a discovered tool
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
|
|
18
18
|
import type { Tool, CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
19
19
|
import type { ToolRouter } from './tool-router.js';
|
|
20
|
-
import type { IndexedTool } from './tool-index.js';
|
|
20
|
+
import type { IndexedTool, ToolLookupOptions } from './tool-index.js';
|
|
21
21
|
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
23
23
|
// Tool Definitions
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* Creates the `
|
|
27
|
+
* Creates the `mcp_search_tools` tool definition.
|
|
28
28
|
*
|
|
29
29
|
* This tool lets the LLM search the full catalog of available MCP tools
|
|
30
30
|
* using a BM25 natural-language query. Returns tool names and descriptions
|
|
@@ -32,7 +32,7 @@ import type { IndexedTool } from './tool-index.js';
|
|
|
32
32
|
*/
|
|
33
33
|
export function createSearchToolDefinition(): Tool {
|
|
34
34
|
return {
|
|
35
|
-
name: '
|
|
35
|
+
name: 'mcp_search_tools',
|
|
36
36
|
description:
|
|
37
37
|
'Search the catalog of available tools. Returns tool names, descriptions, and server info. ' +
|
|
38
38
|
'Use this FIRST to find relevant tools before calling them.\n\n' +
|
|
@@ -47,9 +47,28 @@ export function createSearchToolDefinition(): Tool {
|
|
|
47
47
|
type: 'string',
|
|
48
48
|
description: 'Query to find tools. Use "select:<tool_name>" for direct selection, or keywords to search. Prefix keywords with + to require them.',
|
|
49
49
|
},
|
|
50
|
+
operation: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
enum: ['search', 'list'],
|
|
53
|
+
description:
|
|
54
|
+
'Operation to perform. Use "search" to find relevant tools by capability. Use "list" with serverId or serverName when the user asks for every tool from a connected MCP server.',
|
|
55
|
+
},
|
|
56
|
+
serverId: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
description: 'Optional server ID to restrict search/list results to one MCP server.',
|
|
59
|
+
},
|
|
60
|
+
serverName: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
description:
|
|
63
|
+
'Optional server name fragment to restrict search/list results to matching MCP servers, e.g. "supabase".',
|
|
64
|
+
},
|
|
50
65
|
limit: {
|
|
51
66
|
type: 'number',
|
|
52
|
-
description: 'Maximum number of results to return (default: 5, max: 20).',
|
|
67
|
+
description: 'Maximum number of results to return (default: 5 for search, 20 for list; max: 20 for search, 100 for list).',
|
|
68
|
+
},
|
|
69
|
+
cursor: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'Optional pagination cursor returned by operation "list".',
|
|
53
72
|
},
|
|
54
73
|
},
|
|
55
74
|
required: ['query'],
|
|
@@ -57,6 +76,31 @@ export function createSearchToolDefinition(): Tool {
|
|
|
57
76
|
};
|
|
58
77
|
}
|
|
59
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Creates the `mcp_list_servers` tool definition.
|
|
81
|
+
*
|
|
82
|
+
* This tool lets the LLM inspect connected MCP servers before doing
|
|
83
|
+
* server-scoped tool discovery.
|
|
84
|
+
*/
|
|
85
|
+
export function createListServersToolDefinition(): Tool {
|
|
86
|
+
return {
|
|
87
|
+
name: 'mcp_list_servers',
|
|
88
|
+
description:
|
|
89
|
+
'List connected MCP servers and their tool counts. ' +
|
|
90
|
+
'Use this when mcp_search_tools returns no matches, then retry mcp_search_tools with serverId or serverName.',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: 'object' as const,
|
|
93
|
+
properties: {
|
|
94
|
+
query: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description:
|
|
97
|
+
'Optional server filter text. Matches server name or serverId, e.g. "web" or "supabase".',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
60
104
|
/**
|
|
61
105
|
* Creates the `mcp_search_tool_regex` tool definition.
|
|
62
106
|
*
|
|
@@ -89,7 +133,7 @@ export function createRegexSearchToolDefinition(): Tool {
|
|
|
89
133
|
/**
|
|
90
134
|
* Creates the `mcp_get_tool_schema` tool definition.
|
|
91
135
|
*
|
|
92
|
-
* After discovering tools via `
|
|
136
|
+
* After discovering tools via `mcp_search_tools` or
|
|
93
137
|
* `mcp_search_tool_regex`, the LLM calls this to load the full
|
|
94
138
|
* inputSchema for a specific tool so it can construct the correct
|
|
95
139
|
* arguments.
|
|
@@ -99,7 +143,7 @@ export function createGetSchemaToolDefinition(): Tool {
|
|
|
99
143
|
name: 'mcp_get_tool_schema',
|
|
100
144
|
description:
|
|
101
145
|
'Get the full input schema (parameters) for a specific tool. ' +
|
|
102
|
-
'Call this after
|
|
146
|
+
'Call this after mcp_search_tools to get the parameter details ' +
|
|
103
147
|
'needed to call a tool correctly. ' +
|
|
104
148
|
'Do NOT call the discovered tool directly; after reading the schema, call mcp_execute_tool.',
|
|
105
149
|
inputSchema: {
|
|
@@ -107,12 +151,12 @@ export function createGetSchemaToolDefinition(): Tool {
|
|
|
107
151
|
properties: {
|
|
108
152
|
toolName: {
|
|
109
153
|
type: 'string',
|
|
110
|
-
description: 'The exact tool name returned by
|
|
154
|
+
description: 'The exact tool name returned by mcp_search_tools.',
|
|
111
155
|
},
|
|
112
156
|
serverId: {
|
|
113
157
|
type: 'string',
|
|
114
158
|
description:
|
|
115
|
-
'Optional: The server ID provided in
|
|
159
|
+
'Optional: The server ID provided in mcp_search_tools. Required if multiple tools have the same name.',
|
|
116
160
|
},
|
|
117
161
|
},
|
|
118
162
|
required: ['toolName'],
|
|
@@ -124,7 +168,7 @@ export function createGetSchemaToolDefinition(): Tool {
|
|
|
124
168
|
* Creates the `mcp_execute_tool` tool definition.
|
|
125
169
|
*
|
|
126
170
|
* This is the execution meta-tool — the LLM calls this to execute any
|
|
127
|
-
* tool discovered via `
|
|
171
|
+
* tool discovered via `mcp_search_tools` or `mcp_search_tool_regex`.
|
|
128
172
|
* The LLM should first call `mcp_get_tool_schema` to know the correct
|
|
129
173
|
* arguments.
|
|
130
174
|
*
|
|
@@ -135,7 +179,7 @@ export function createExecuteToolDefinition(): Tool {
|
|
|
135
179
|
return {
|
|
136
180
|
name: 'mcp_execute_tool',
|
|
137
181
|
description:
|
|
138
|
-
'Execute a tool that was discovered via
|
|
182
|
+
'Execute a tool that was discovered via mcp_search_tools. ' +
|
|
139
183
|
'You MUST call mcp_get_tool_schema first to know the correct parameters. ' +
|
|
140
184
|
'Pass the exact tool name and its arguments.',
|
|
141
185
|
inputSchema: {
|
|
@@ -143,12 +187,12 @@ export function createExecuteToolDefinition(): Tool {
|
|
|
143
187
|
properties: {
|
|
144
188
|
toolName: {
|
|
145
189
|
type: 'string',
|
|
146
|
-
description: 'The exact tool name from
|
|
190
|
+
description: 'The exact tool name from mcp_search_tools results.',
|
|
147
191
|
},
|
|
148
192
|
serverId: {
|
|
149
193
|
type: 'string',
|
|
150
194
|
description:
|
|
151
|
-
'Optional: The server ID provided in
|
|
195
|
+
'Optional: The server ID provided in mcp_search_tools. Required if multiple tools have the same name.',
|
|
152
196
|
},
|
|
153
197
|
args: {
|
|
154
198
|
type: 'object',
|
|
@@ -179,7 +223,7 @@ export type CallToolFn = (
|
|
|
179
223
|
/**
|
|
180
224
|
* Execute a meta-tool call and return the result in MCP CallToolResult format.
|
|
181
225
|
*
|
|
182
|
-
* @param toolName - One of the meta-tool names (
|
|
226
|
+
* @param toolName - One of the meta-tool names (mcp_search_tools, mcp_list_servers, mcp_search_tool_regex, etc.)
|
|
183
227
|
* @param args - The arguments from the LLM's tool call
|
|
184
228
|
* @param router - The ToolRouter to query
|
|
185
229
|
* @param callToolFn - Optional callback for executing real tools (required for mcp_execute_tool)
|
|
@@ -191,9 +235,13 @@ export async function executeMetaTool(
|
|
|
191
235
|
router: ToolRouter,
|
|
192
236
|
callToolFn?: CallToolFn
|
|
193
237
|
): Promise<CallToolResult | null> {
|
|
194
|
-
const resolveToolSchema = (
|
|
238
|
+
const resolveToolSchema = (
|
|
239
|
+
name: string,
|
|
240
|
+
namespace?: string,
|
|
241
|
+
options?: ToolLookupOptions
|
|
242
|
+
): { tool?: IndexedTool; error?: CallToolResult } => {
|
|
195
243
|
try {
|
|
196
|
-
return { tool: router.getToolSchema(name, namespace) };
|
|
244
|
+
return { tool: router.getToolSchema(name, namespace, options) };
|
|
197
245
|
} catch (err) {
|
|
198
246
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
199
247
|
return {
|
|
@@ -206,13 +254,61 @@ export async function executeMetaTool(
|
|
|
206
254
|
};
|
|
207
255
|
|
|
208
256
|
switch (toolName) {
|
|
209
|
-
case '
|
|
257
|
+
case 'mcp_search_tools': {
|
|
210
258
|
const query = String(args.query ?? '');
|
|
259
|
+
const operation = String(args.operation ?? 'search');
|
|
260
|
+
const serverId = String(args.serverId ?? '') || undefined;
|
|
261
|
+
const serverName = String(args.serverName ?? '') || undefined;
|
|
262
|
+
|
|
263
|
+
if (operation === 'list') {
|
|
264
|
+
const limit = Math.min(Number(args.limit) || 20, 100);
|
|
265
|
+
const cursor = String(args.cursor ?? '') || undefined;
|
|
266
|
+
const result = await router.listTools({
|
|
267
|
+
serverId,
|
|
268
|
+
serverName: serverName ?? (!serverId && query ? query : undefined),
|
|
269
|
+
limit,
|
|
270
|
+
cursor,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const serverText = result.servers.length > 0
|
|
274
|
+
? result.servers
|
|
275
|
+
.map((server) => `${server.serverName} (serverId: ${server.serverId}, tools: ${server.toolCount})`)
|
|
276
|
+
.join(', ')
|
|
277
|
+
: 'none';
|
|
278
|
+
|
|
279
|
+
const lines: string[] = [
|
|
280
|
+
'operation: list',
|
|
281
|
+
`servers: ${serverText}`,
|
|
282
|
+
`totalCount: ${result.totalCount}`,
|
|
283
|
+
`returnedCount: ${result.returnedCount}`,
|
|
284
|
+
`nextCursor: ${result.nextCursor ?? 'null'}`,
|
|
285
|
+
'',
|
|
286
|
+
];
|
|
287
|
+
|
|
288
|
+
if (result.tools.length > 0) {
|
|
289
|
+
lines.push(...formatToolSummaries(result.tools));
|
|
290
|
+
} else {
|
|
291
|
+
lines.push(
|
|
292
|
+
serverId || serverName
|
|
293
|
+
? 'No tools found for the requested server scope.'
|
|
294
|
+
: 'No tools found. Try operation "search" or provide serverId/serverName.'
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
300
|
+
isError: false,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
211
304
|
const limit = Math.min(Number(args.limit) || 5, 20);
|
|
305
|
+
const searchOptions = { serverId, serverName };
|
|
212
306
|
|
|
213
307
|
// Fast path: Check for select: prefix
|
|
214
308
|
const selectMatch = query.match(/^select:(.+)$/i);
|
|
215
309
|
if (selectMatch) {
|
|
310
|
+
await router.listTools({ serverId, serverName, limit: 1 });
|
|
311
|
+
|
|
216
312
|
const requested = selectMatch[1]!
|
|
217
313
|
.split(',')
|
|
218
314
|
.map((s) => s.trim())
|
|
@@ -221,15 +317,19 @@ export async function executeMetaTool(
|
|
|
221
317
|
const found: any[] = [];
|
|
222
318
|
const errors: string[] = [];
|
|
223
319
|
|
|
320
|
+
const namespace = serverId ?? serverName;
|
|
321
|
+
|
|
224
322
|
for (const requestedToolName of requested) {
|
|
225
|
-
const { tool, error } = resolveToolSchema(requestedToolName
|
|
323
|
+
const { tool, error } = resolveToolSchema(requestedToolName, namespace, {
|
|
324
|
+
allowServerNameFragment: Boolean(serverName && !serverId),
|
|
325
|
+
});
|
|
226
326
|
if (error) {
|
|
227
327
|
const errorMsg = error.content[0]?.type === 'text' ? error.content[0].text : 'Unknown error';
|
|
228
328
|
errors.push(`- **${requestedToolName}**: ${errorMsg}`);
|
|
229
329
|
} else if (tool) {
|
|
230
330
|
found.push(tool);
|
|
231
331
|
} else {
|
|
232
|
-
errors.push(`- **${requestedToolName}**: Tool not found. Try searching with
|
|
332
|
+
errors.push(`- **${requestedToolName}**: Tool not found. Try searching with mcp_search_tools.`);
|
|
233
333
|
}
|
|
234
334
|
}
|
|
235
335
|
|
|
@@ -257,16 +357,31 @@ export async function executeMetaTool(
|
|
|
257
357
|
};
|
|
258
358
|
}
|
|
259
359
|
|
|
260
|
-
const results = await router.searchTools(query, limit);
|
|
360
|
+
const results = await router.searchTools(query, limit, searchOptions);
|
|
261
361
|
|
|
262
362
|
const text = results.length === 0
|
|
263
|
-
? 'No tools found matching your query.
|
|
264
|
-
: results
|
|
363
|
+
? 'No tools found matching your query. Call mcp_list_servers to inspect connected servers, then retry mcp_search_tools with serverId or serverName.'
|
|
364
|
+
: formatToolSummaries(results).join('\n');
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
content: [{ type: 'text', text }],
|
|
368
|
+
isError: false,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
case 'mcp_list_servers': {
|
|
373
|
+
const query = String(args.query ?? '').trim();
|
|
374
|
+
const servers = await router.listServers({
|
|
375
|
+
serverName: query || undefined,
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const text = servers.length === 0
|
|
379
|
+
? 'No connected servers found.'
|
|
380
|
+
: servers
|
|
265
381
|
.map(
|
|
266
|
-
(
|
|
267
|
-
`${i + 1}. **${
|
|
268
|
-
` ${
|
|
269
|
-
` Estimated tokens: ${t.estimatedTokens}`
|
|
382
|
+
(server, i) =>
|
|
383
|
+
`${i + 1}. **${server.serverName}** (serverId: ${server.serverId}, sessionId: ${server.sessionId})\n` +
|
|
384
|
+
` Tool count: ${server.toolCount}`
|
|
270
385
|
)
|
|
271
386
|
.join('\n');
|
|
272
387
|
|
|
@@ -284,14 +399,7 @@ export async function executeMetaTool(
|
|
|
284
399
|
|
|
285
400
|
const text = results.length === 0
|
|
286
401
|
? 'No tools matched your regex pattern. Try a broader pattern.'
|
|
287
|
-
: results
|
|
288
|
-
.map(
|
|
289
|
-
(t, i) =>
|
|
290
|
-
`${i + 1}. **${t.name}** (server: ${t.serverName}, serverId: ${t.serverId})\n` +
|
|
291
|
-
` ${t.description}\n` +
|
|
292
|
-
` Estimated tokens: ${t.estimatedTokens}`
|
|
293
|
-
)
|
|
294
|
-
.join('\n');
|
|
402
|
+
: formatToolSummaries(results).join('\n');
|
|
295
403
|
|
|
296
404
|
return {
|
|
297
405
|
content: [{ type: 'text', text }],
|
|
@@ -313,7 +421,7 @@ export async function executeMetaTool(
|
|
|
313
421
|
content: [
|
|
314
422
|
{
|
|
315
423
|
type: 'text',
|
|
316
|
-
text: `Tool "${name}" not found. Use
|
|
424
|
+
text: `Tool "${name}" not found. Use mcp_search_tools to find available tools first.`,
|
|
317
425
|
},
|
|
318
426
|
],
|
|
319
427
|
isError: true,
|
|
@@ -362,7 +470,7 @@ export async function executeMetaTool(
|
|
|
362
470
|
content: [
|
|
363
471
|
{
|
|
364
472
|
type: 'text',
|
|
365
|
-
text: `Tool "${targetToolName}" not found. Use
|
|
473
|
+
text: `Tool "${targetToolName}" not found. Use mcp_search_tools to discover available tools first.`,
|
|
366
474
|
},
|
|
367
475
|
],
|
|
368
476
|
isError: true,
|
|
@@ -404,10 +512,28 @@ export async function executeMetaTool(
|
|
|
404
512
|
}
|
|
405
513
|
}
|
|
406
514
|
|
|
515
|
+
function formatToolSummaries(
|
|
516
|
+
tools: Array<{
|
|
517
|
+
name: string;
|
|
518
|
+
description: string;
|
|
519
|
+
serverName: string;
|
|
520
|
+
serverId: string;
|
|
521
|
+
estimatedTokens: number;
|
|
522
|
+
}>
|
|
523
|
+
): string[] {
|
|
524
|
+
return tools.map(
|
|
525
|
+
(t, i) =>
|
|
526
|
+
`${i + 1}. **${t.name}** (server: ${t.serverName}, serverId: ${t.serverId})\n` +
|
|
527
|
+
` ${t.description}\n` +
|
|
528
|
+
` Estimated tokens: ${t.estimatedTokens}`
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
|
|
407
532
|
/** Check if a tool name is one of the meta-tools. */
|
|
408
533
|
export function isMetaTool(toolName: string): boolean {
|
|
409
534
|
return (
|
|
410
|
-
toolName === '
|
|
535
|
+
toolName === 'mcp_search_tools' ||
|
|
536
|
+
toolName === 'mcp_list_servers' ||
|
|
411
537
|
toolName === 'mcp_search_tool_regex' ||
|
|
412
538
|
toolName === 'mcp_get_tool_schema' ||
|
|
413
539
|
toolName === 'mcp_execute_tool'
|
package/src/shared/tool-index.ts
CHANGED
|
@@ -32,6 +32,43 @@ export interface ToolSummary {
|
|
|
32
32
|
estimatedTokens: number;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/** Server-level summary derived from indexed tools. */
|
|
36
|
+
export interface ToolServerSummary {
|
|
37
|
+
/** Human-readable server name */
|
|
38
|
+
serverName: string;
|
|
39
|
+
/** Stable server identifier */
|
|
40
|
+
serverId: string;
|
|
41
|
+
/** Session the server belongs to */
|
|
42
|
+
sessionId: string;
|
|
43
|
+
/** Number of indexed tools for this server */
|
|
44
|
+
toolCount: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Optional filters for search and listing. */
|
|
48
|
+
export interface ToolSearchOptions {
|
|
49
|
+
/** Restrict results to this server ID. */
|
|
50
|
+
serverId?: string;
|
|
51
|
+
/** Restrict results to servers whose name or ID matches this value. */
|
|
52
|
+
serverName?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Paginated tool listing result. */
|
|
56
|
+
export interface ToolListResult {
|
|
57
|
+
tools: ToolSummary[];
|
|
58
|
+
totalCount: number;
|
|
59
|
+
returnedCount: number;
|
|
60
|
+
nextCursor?: string;
|
|
61
|
+
servers: ToolServerSummary[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ToolLookupOptions {
|
|
65
|
+
/**
|
|
66
|
+
* Allow namespace to match a fragment of serverName after exact
|
|
67
|
+
* sessionId/serverId matching fails.
|
|
68
|
+
*/
|
|
69
|
+
allowServerNameFragment?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
35
72
|
/** A tool with routing metadata attached during indexing. */
|
|
36
73
|
export interface IndexedTool extends Tool {
|
|
37
74
|
sessionId: string;
|
|
@@ -259,14 +296,14 @@ export class ToolIndex {
|
|
|
259
296
|
*
|
|
260
297
|
* `score = keywordWeight × keyword_score + (1 - keywordWeight) × cosine_score`
|
|
261
298
|
*/
|
|
262
|
-
async search(query: string, topK = 5): Promise<ToolSummary[]> {
|
|
299
|
+
async search(query: string, topK = 5, options: ToolSearchOptions = {}): Promise<ToolSummary[]> {
|
|
263
300
|
if (this.tools.size === 0) return [];
|
|
264
301
|
|
|
265
302
|
const queryLower = query.toLowerCase().trim();
|
|
266
303
|
|
|
267
304
|
// Fast path: Exact tool name match (supports duplicate names across servers)
|
|
268
305
|
const exactMatches = [...this.toolSummaries.values()].filter(
|
|
269
|
-
(summary) => summary.name.toLowerCase() === queryLower
|
|
306
|
+
(summary) => summary.name.toLowerCase() === queryLower && this.matchesServer(summary, options)
|
|
270
307
|
);
|
|
271
308
|
if (exactMatches.length > 0) {
|
|
272
309
|
return exactMatches.slice(0, topK);
|
|
@@ -275,7 +312,7 @@ export class ToolIndex {
|
|
|
275
312
|
// Fast path: MCP prefix match (e.g. "mcp__github")
|
|
276
313
|
if (queryLower.startsWith('mcp__') && queryLower.length > 5) {
|
|
277
314
|
const prefixMatches = [...this.toolSummaries.values()]
|
|
278
|
-
.filter((t) => t.name.toLowerCase().startsWith(queryLower))
|
|
315
|
+
.filter((t) => t.name.toLowerCase().startsWith(queryLower) && this.matchesServer(t, options))
|
|
279
316
|
.slice(0, topK);
|
|
280
317
|
if (prefixMatches.length > 0) return prefixMatches;
|
|
281
318
|
}
|
|
@@ -300,9 +337,11 @@ export class ToolIndex {
|
|
|
300
337
|
// Pre-filter: only keep documents that contain ALL required terms
|
|
301
338
|
const candidateKeys = new Set<string>();
|
|
302
339
|
for (const docKey of this.toolSummaries.keys()) {
|
|
340
|
+
const summary = this.toolSummaries.get(docKey)!;
|
|
341
|
+
if (!this.matchesServer(summary, options)) continue;
|
|
342
|
+
|
|
303
343
|
if (requiredTerms.length > 0) {
|
|
304
344
|
const text = this.searchTexts.get(docKey) || '';
|
|
305
|
-
const summary = this.toolSummaries.get(docKey)!;
|
|
306
345
|
const nameLower = summary.name.toLowerCase();
|
|
307
346
|
const matchesAll = requiredTerms.every(
|
|
308
347
|
(term) => text.includes(term) || nameLower.includes(term)
|
|
@@ -456,13 +495,22 @@ export class ToolIndex {
|
|
|
456
495
|
|
|
457
496
|
/**
|
|
458
497
|
* Get tool definition(s) by name.
|
|
459
|
-
* If namespace is provided,
|
|
498
|
+
* If namespace is provided, exact sessionId/serverId matches take precedence.
|
|
499
|
+
* Falls back to serverName fragment matching only when explicitly allowed.
|
|
460
500
|
*/
|
|
461
|
-
getTool(name: string, namespace?: string): IndexedTool[] {
|
|
501
|
+
getTool(name: string, namespace?: string, options: ToolLookupOptions = {}): IndexedTool[] {
|
|
462
502
|
const list = this.tools.get(name) ?? [];
|
|
463
503
|
if (!namespace) return list;
|
|
464
504
|
|
|
465
|
-
|
|
505
|
+
const exactMatches = list.filter(
|
|
506
|
+
(t) => t.sessionId === namespace || t.serverId === namespace
|
|
507
|
+
);
|
|
508
|
+
if (exactMatches.length > 0) return exactMatches;
|
|
509
|
+
|
|
510
|
+
if (!options.allowServerNameFragment) return [];
|
|
511
|
+
|
|
512
|
+
const namespaceLower = namespace.toLowerCase();
|
|
513
|
+
return list.filter((t) => t.serverName.toLowerCase().includes(namespaceLower));
|
|
466
514
|
}
|
|
467
515
|
|
|
468
516
|
/** All indexed tool names. */
|
|
@@ -470,6 +518,57 @@ export class ToolIndex {
|
|
|
470
518
|
return [...this.tools.keys()];
|
|
471
519
|
}
|
|
472
520
|
|
|
521
|
+
/** List indexed servers with tool counts. */
|
|
522
|
+
listServers(options: ToolSearchOptions = {}): ToolServerSummary[] {
|
|
523
|
+
const servers = new Map<string, ToolServerSummary>();
|
|
524
|
+
|
|
525
|
+
for (const summary of this.toolSummaries.values()) {
|
|
526
|
+
if (!this.matchesServer(summary, options)) continue;
|
|
527
|
+
|
|
528
|
+
const key = `${summary.sessionId}::${summary.serverId}`;
|
|
529
|
+
const existing = servers.get(key);
|
|
530
|
+
if (existing) {
|
|
531
|
+
existing.toolCount += 1;
|
|
532
|
+
} else {
|
|
533
|
+
servers.set(key, {
|
|
534
|
+
serverName: summary.serverName,
|
|
535
|
+
serverId: summary.serverId,
|
|
536
|
+
sessionId: summary.sessionId,
|
|
537
|
+
toolCount: 1,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return [...servers.values()].sort((a, b) => {
|
|
543
|
+
const byName = a.serverName.localeCompare(b.serverName);
|
|
544
|
+
return byName !== 0 ? byName : a.serverId.localeCompare(b.serverId);
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/** List tools deterministically, optionally scoped to a server. */
|
|
549
|
+
listTools(options: ToolSearchOptions & { limit?: number; cursor?: string } = {}): ToolListResult {
|
|
550
|
+
const offset = Math.max(Number(options.cursor) || 0, 0);
|
|
551
|
+
const limit = Math.max(Number(options.limit) || 20, 1);
|
|
552
|
+
const tools = [...this.toolSummaries.values()]
|
|
553
|
+
.filter((summary) => this.matchesServer(summary, options))
|
|
554
|
+
.sort((a, b) => {
|
|
555
|
+
const byServer = a.serverName.localeCompare(b.serverName);
|
|
556
|
+
if (byServer !== 0) return byServer;
|
|
557
|
+
return a.name.localeCompare(b.name);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
const page = tools.slice(offset, offset + limit);
|
|
561
|
+
const nextOffset = offset + page.length;
|
|
562
|
+
|
|
563
|
+
return {
|
|
564
|
+
tools: page,
|
|
565
|
+
totalCount: tools.length,
|
|
566
|
+
returnedCount: page.length,
|
|
567
|
+
nextCursor: nextOffset < tools.length ? String(nextOffset) : undefined,
|
|
568
|
+
servers: this.listServers(options),
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
473
572
|
/** Number of indexed tools (including duplicates). */
|
|
474
573
|
get size(): number {
|
|
475
574
|
let count = 0;
|
|
@@ -539,6 +638,23 @@ export class ToolIndex {
|
|
|
539
638
|
return `${tool.sessionId}::${tool.serverId}::${tool.name}`;
|
|
540
639
|
}
|
|
541
640
|
|
|
641
|
+
private matchesServer(summary: ToolSummary, options: ToolSearchOptions): boolean {
|
|
642
|
+
if (options.serverId && summary.serverId !== options.serverId) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (options.serverName) {
|
|
647
|
+
const serverNameQuery = options.serverName.toLowerCase();
|
|
648
|
+
const serverName = summary.serverName.toLowerCase();
|
|
649
|
+
const serverId = summary.serverId.toLowerCase();
|
|
650
|
+
if (!serverName.includes(serverNameQuery) && !serverId.includes(serverNameQuery)) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
|
|
542
658
|
/** Simple whitespace + camelCase + snake_case tokenizer. */
|
|
543
659
|
private tokenize(text: string): string[] {
|
|
544
660
|
return text
|
|
@@ -28,10 +28,20 @@
|
|
|
28
28
|
|
|
29
29
|
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
30
30
|
import type { ToolClient, ToolClientProvider } from './types.js';
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
ToolIndex,
|
|
33
|
+
type IndexedTool,
|
|
34
|
+
type ToolLookupOptions,
|
|
35
|
+
type ToolListResult,
|
|
36
|
+
type ToolSearchOptions,
|
|
37
|
+
type ToolServerSummary,
|
|
38
|
+
type ToolSummary,
|
|
39
|
+
type EmbedFn,
|
|
40
|
+
} from './tool-index.js';
|
|
32
41
|
import { SchemaCompressor, type CompactTool } from './schema-compressor.js';
|
|
33
42
|
import {
|
|
34
43
|
createSearchToolDefinition,
|
|
44
|
+
createListServersToolDefinition,
|
|
35
45
|
createRegexSearchToolDefinition,
|
|
36
46
|
createGetSchemaToolDefinition,
|
|
37
47
|
createExecuteToolDefinition,
|
|
@@ -159,7 +169,7 @@ export class ToolRouter {
|
|
|
159
169
|
* This is the main method adapters should call.
|
|
160
170
|
*
|
|
161
171
|
* - `all` → returns all tools (unchanged behavior)
|
|
162
|
-
* - `search` → returns only meta-tools (
|
|
172
|
+
* - `search` → returns only meta-tools (mcp_search_tools, mcp_get_tool_schema, mcp_execute_tool)
|
|
163
173
|
* - `groups` → returns tools from active groups only
|
|
164
174
|
*/
|
|
165
175
|
async getFilteredTools(): Promise<Tool[]> {
|
|
@@ -195,9 +205,14 @@ export class ToolRouter {
|
|
|
195
205
|
* Search tools by natural-language query.
|
|
196
206
|
* Works regardless of strategy.
|
|
197
207
|
*/
|
|
198
|
-
async searchTools(
|
|
208
|
+
async searchTools(
|
|
209
|
+
query: string,
|
|
210
|
+
topK?: number,
|
|
211
|
+
options: ToolSearchOptions = {}
|
|
212
|
+
): Promise<ToolSummary[]> {
|
|
199
213
|
await this.ensureInitialized();
|
|
200
|
-
|
|
214
|
+
const limit = topK ?? this.maxTools;
|
|
215
|
+
return this.index.search(query, limit, options);
|
|
201
216
|
}
|
|
202
217
|
|
|
203
218
|
/**
|
|
@@ -209,12 +224,28 @@ export class ToolRouter {
|
|
|
209
224
|
return this.index.searchRegex(pattern, topK ?? this.maxTools);
|
|
210
225
|
}
|
|
211
226
|
|
|
227
|
+
/** List connected MCP servers with indexed tool counts. */
|
|
228
|
+
async listServers(options: ToolSearchOptions = {}): Promise<ToolServerSummary[]> {
|
|
229
|
+
await this.ensureInitialized();
|
|
230
|
+
return this.index.listServers(options);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** List tools deterministically, optionally scoped to a server. */
|
|
234
|
+
async listTools(options: ToolSearchOptions & { limit?: number; cursor?: string } = {}): Promise<ToolListResult> {
|
|
235
|
+
await this.ensureInitialized();
|
|
236
|
+
return this.index.listTools(options);
|
|
237
|
+
}
|
|
238
|
+
|
|
212
239
|
/**
|
|
213
240
|
* Get the full tool definition by name.
|
|
214
241
|
* If tool name is ambiguous, use namespace to specify the server.
|
|
215
242
|
*/
|
|
216
|
-
getToolSchema(
|
|
217
|
-
|
|
243
|
+
getToolSchema(
|
|
244
|
+
toolName: string,
|
|
245
|
+
namespace?: string,
|
|
246
|
+
options: ToolLookupOptions = {}
|
|
247
|
+
): IndexedTool | undefined {
|
|
248
|
+
const matches = this.index.getTool(toolName, namespace, options);
|
|
218
249
|
|
|
219
250
|
if (matches.length === 0) return undefined;
|
|
220
251
|
|
|
@@ -318,7 +349,7 @@ export class ToolRouter {
|
|
|
318
349
|
throw new Error(
|
|
319
350
|
`Tool "${toolName}" not found${
|
|
320
351
|
namespace ? ` on server "${namespace}"` : ''
|
|
321
|
-
}. Use
|
|
352
|
+
}. Use mcp_search_tools or mcp_search_tool_regex to discover available tools.`
|
|
322
353
|
);
|
|
323
354
|
}
|
|
324
355
|
|
|
@@ -462,9 +493,11 @@ export class ToolRouter {
|
|
|
462
493
|
private getMetaToolDefinitions(): Tool[] {
|
|
463
494
|
return [
|
|
464
495
|
createSearchToolDefinition(),
|
|
496
|
+
createListServersToolDefinition(),
|
|
465
497
|
createRegexSearchToolDefinition(),
|
|
466
498
|
createGetSchemaToolDefinition(),
|
|
467
499
|
createExecuteToolDefinition(),
|
|
468
500
|
];
|
|
469
501
|
}
|
|
502
|
+
|
|
470
503
|
}
|