@mcp-ts/sdk 1.5.2 → 1.6.0
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/README.md +89 -27
- package/dist/adapters/agui-adapter.d.mts +1 -1
- package/dist/adapters/agui-adapter.d.ts +1 -1
- package/dist/adapters/agui-adapter.js +76 -19
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +76 -19
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +5 -1
- package/dist/adapters/agui-middleware.d.ts +5 -1
- package/dist/adapters/agui-middleware.js +116 -49
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs +117 -50
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/adapters/ai-adapter.d.mts +1 -1
- package/dist/adapters/ai-adapter.d.ts +1 -1
- package/dist/adapters/ai-adapter.js +76 -19
- package/dist/adapters/ai-adapter.js.map +1 -1
- package/dist/adapters/ai-adapter.mjs +76 -19
- package/dist/adapters/ai-adapter.mjs.map +1 -1
- package/dist/adapters/langchain-adapter.d.mts +1 -1
- package/dist/adapters/langchain-adapter.d.ts +1 -1
- package/dist/adapters/langchain-adapter.js +76 -19
- package/dist/adapters/langchain-adapter.js.map +1 -1
- package/dist/adapters/langchain-adapter.mjs +76 -19
- package/dist/adapters/langchain-adapter.mjs.map +1 -1
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +207 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +207 -44
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.js +1 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1 -3
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +15 -8
- package/dist/shared/index.d.ts +15 -8
- package/dist/shared/index.js +206 -40
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +206 -41
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-DsKhRmJm.d.ts → tool-router-Bn9R0KWr.d.ts} +56 -7
- package/dist/{tool-router-DK0RJblO.d.mts → tool-router-_O2tIwf7.d.mts} +56 -7
- package/package.json +5 -3
- package/src/adapters/agui-middleware.ts +163 -59
- package/src/server/mcp/oauth-client.ts +4 -4
- package/src/shared/index.ts +4 -0
- package/src/shared/meta-tools.ts +172 -37
- package/src/shared/tool-index.ts +123 -7
- package/src/shared/tool-router.ts +40 -7
|
@@ -1126,9 +1126,10 @@ export class MCPClient {
|
|
|
1126
1126
|
* @returns Server name or undefined
|
|
1127
1127
|
*/
|
|
1128
1128
|
getServerName(): string | undefined {
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
return info?.title ?? info?.name ?? this.serverName;
|
|
1129
|
+
// Temporarily avoid deriving serverName from serverVersion metadata.
|
|
1130
|
+
// const info = (this.client as any)?.getServerVersion();
|
|
1131
|
+
// return info?.title ?? info?.name ?? this.serverName;
|
|
1132
|
+
return this.serverName;
|
|
1132
1133
|
}
|
|
1133
1134
|
|
|
1134
1135
|
/**
|
|
@@ -1228,4 +1229,3 @@ export class MCPClient {
|
|
|
1228
1229
|
}
|
|
1229
1230
|
|
|
1230
1231
|
}
|
|
1231
|
-
|
package/src/shared/index.ts
CHANGED
|
@@ -87,6 +87,9 @@ export {
|
|
|
87
87
|
export {
|
|
88
88
|
ToolIndex,
|
|
89
89
|
type ToolSummary,
|
|
90
|
+
type ToolServerSummary,
|
|
91
|
+
type ToolSearchOptions,
|
|
92
|
+
type ToolListResult,
|
|
90
93
|
type IndexedTool,
|
|
91
94
|
type ToolIndexOptions,
|
|
92
95
|
type EmbedFn,
|
|
@@ -100,6 +103,7 @@ export {
|
|
|
100
103
|
|
|
101
104
|
export {
|
|
102
105
|
createSearchToolDefinition,
|
|
106
|
+
createListServersToolDefinition,
|
|
103
107
|
createRegexSearchToolDefinition,
|
|
104
108
|
createGetSchemaToolDefinition,
|
|
105
109
|
createExecuteToolDefinition,
|
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,13 +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, ToolLookupOptions } from './tool-index.js';
|
|
20
21
|
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
22
23
|
// Tool Definitions
|
|
23
24
|
// ---------------------------------------------------------------------------
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
|
-
* Creates the `
|
|
27
|
+
* Creates the `mcp_search_tools` tool definition.
|
|
27
28
|
*
|
|
28
29
|
* This tool lets the LLM search the full catalog of available MCP tools
|
|
29
30
|
* using a BM25 natural-language query. Returns tool names and descriptions
|
|
@@ -31,7 +32,7 @@ import type { ToolRouter } from './tool-router.js';
|
|
|
31
32
|
*/
|
|
32
33
|
export function createSearchToolDefinition(): Tool {
|
|
33
34
|
return {
|
|
34
|
-
name: '
|
|
35
|
+
name: 'mcp_search_tools',
|
|
35
36
|
description:
|
|
36
37
|
'Search the catalog of available tools. Returns tool names, descriptions, and server info. ' +
|
|
37
38
|
'Use this FIRST to find relevant tools before calling them.\n\n' +
|
|
@@ -46,9 +47,28 @@ export function createSearchToolDefinition(): Tool {
|
|
|
46
47
|
type: 'string',
|
|
47
48
|
description: 'Query to find tools. Use "select:<tool_name>" for direct selection, or keywords to search. Prefix keywords with + to require them.',
|
|
48
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
|
+
},
|
|
49
65
|
limit: {
|
|
50
66
|
type: 'number',
|
|
51
|
-
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".',
|
|
52
72
|
},
|
|
53
73
|
},
|
|
54
74
|
required: ['query'],
|
|
@@ -56,6 +76,31 @@ export function createSearchToolDefinition(): Tool {
|
|
|
56
76
|
};
|
|
57
77
|
}
|
|
58
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
|
+
|
|
59
104
|
/**
|
|
60
105
|
* Creates the `mcp_search_tool_regex` tool definition.
|
|
61
106
|
*
|
|
@@ -88,7 +133,7 @@ export function createRegexSearchToolDefinition(): Tool {
|
|
|
88
133
|
/**
|
|
89
134
|
* Creates the `mcp_get_tool_schema` tool definition.
|
|
90
135
|
*
|
|
91
|
-
* After discovering tools via `
|
|
136
|
+
* After discovering tools via `mcp_search_tools` or
|
|
92
137
|
* `mcp_search_tool_regex`, the LLM calls this to load the full
|
|
93
138
|
* inputSchema for a specific tool so it can construct the correct
|
|
94
139
|
* arguments.
|
|
@@ -98,19 +143,20 @@ export function createGetSchemaToolDefinition(): Tool {
|
|
|
98
143
|
name: 'mcp_get_tool_schema',
|
|
99
144
|
description:
|
|
100
145
|
'Get the full input schema (parameters) for a specific tool. ' +
|
|
101
|
-
'Call this after
|
|
102
|
-
'needed to call a tool correctly.'
|
|
146
|
+
'Call this after mcp_search_tools to get the parameter details ' +
|
|
147
|
+
'needed to call a tool correctly. ' +
|
|
148
|
+
'Do NOT call the discovered tool directly; after reading the schema, call mcp_execute_tool.',
|
|
103
149
|
inputSchema: {
|
|
104
150
|
type: 'object' as const,
|
|
105
151
|
properties: {
|
|
106
152
|
toolName: {
|
|
107
153
|
type: 'string',
|
|
108
|
-
description: 'The exact tool name returned by
|
|
154
|
+
description: 'The exact tool name returned by mcp_search_tools.',
|
|
109
155
|
},
|
|
110
156
|
serverId: {
|
|
111
157
|
type: 'string',
|
|
112
158
|
description:
|
|
113
|
-
'Optional: The server ID provided in
|
|
159
|
+
'Optional: The server ID provided in mcp_search_tools. Required if multiple tools have the same name.',
|
|
114
160
|
},
|
|
115
161
|
},
|
|
116
162
|
required: ['toolName'],
|
|
@@ -122,7 +168,7 @@ export function createGetSchemaToolDefinition(): Tool {
|
|
|
122
168
|
* Creates the `mcp_execute_tool` tool definition.
|
|
123
169
|
*
|
|
124
170
|
* This is the execution meta-tool — the LLM calls this to execute any
|
|
125
|
-
* tool discovered via `
|
|
171
|
+
* tool discovered via `mcp_search_tools` or `mcp_search_tool_regex`.
|
|
126
172
|
* The LLM should first call `mcp_get_tool_schema` to know the correct
|
|
127
173
|
* arguments.
|
|
128
174
|
*
|
|
@@ -133,7 +179,7 @@ export function createExecuteToolDefinition(): Tool {
|
|
|
133
179
|
return {
|
|
134
180
|
name: 'mcp_execute_tool',
|
|
135
181
|
description:
|
|
136
|
-
'Execute a tool that was discovered via
|
|
182
|
+
'Execute a tool that was discovered via mcp_search_tools. ' +
|
|
137
183
|
'You MUST call mcp_get_tool_schema first to know the correct parameters. ' +
|
|
138
184
|
'Pass the exact tool name and its arguments.',
|
|
139
185
|
inputSchema: {
|
|
@@ -141,12 +187,12 @@ export function createExecuteToolDefinition(): Tool {
|
|
|
141
187
|
properties: {
|
|
142
188
|
toolName: {
|
|
143
189
|
type: 'string',
|
|
144
|
-
description: 'The exact tool name from
|
|
190
|
+
description: 'The exact tool name from mcp_search_tools results.',
|
|
145
191
|
},
|
|
146
192
|
serverId: {
|
|
147
193
|
type: 'string',
|
|
148
194
|
description:
|
|
149
|
-
'Optional: The server ID provided in
|
|
195
|
+
'Optional: The server ID provided in mcp_search_tools. Required if multiple tools have the same name.',
|
|
150
196
|
},
|
|
151
197
|
args: {
|
|
152
198
|
type: 'object',
|
|
@@ -177,7 +223,7 @@ export type CallToolFn = (
|
|
|
177
223
|
/**
|
|
178
224
|
* Execute a meta-tool call and return the result in MCP CallToolResult format.
|
|
179
225
|
*
|
|
180
|
-
* @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.)
|
|
181
227
|
* @param args - The arguments from the LLM's tool call
|
|
182
228
|
* @param router - The ToolRouter to query
|
|
183
229
|
* @param callToolFn - Optional callback for executing real tools (required for mcp_execute_tool)
|
|
@@ -189,9 +235,13 @@ export async function executeMetaTool(
|
|
|
189
235
|
router: ToolRouter,
|
|
190
236
|
callToolFn?: CallToolFn
|
|
191
237
|
): Promise<CallToolResult | null> {
|
|
192
|
-
const resolveToolSchema = (
|
|
238
|
+
const resolveToolSchema = (
|
|
239
|
+
name: string,
|
|
240
|
+
namespace?: string,
|
|
241
|
+
options?: ToolLookupOptions
|
|
242
|
+
): { tool?: IndexedTool; error?: CallToolResult } => {
|
|
193
243
|
try {
|
|
194
|
-
return { tool: router.getToolSchema(name, namespace) };
|
|
244
|
+
return { tool: router.getToolSchema(name, namespace, options) };
|
|
195
245
|
} catch (err) {
|
|
196
246
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
197
247
|
return {
|
|
@@ -204,13 +254,61 @@ export async function executeMetaTool(
|
|
|
204
254
|
};
|
|
205
255
|
|
|
206
256
|
switch (toolName) {
|
|
207
|
-
case '
|
|
257
|
+
case 'mcp_search_tools': {
|
|
208
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
|
+
|
|
209
304
|
const limit = Math.min(Number(args.limit) || 5, 20);
|
|
305
|
+
const searchOptions = { serverId, serverName };
|
|
210
306
|
|
|
211
307
|
// Fast path: Check for select: prefix
|
|
212
308
|
const selectMatch = query.match(/^select:(.+)$/i);
|
|
213
309
|
if (selectMatch) {
|
|
310
|
+
await router.listTools({ serverId, serverName, limit: 1 });
|
|
311
|
+
|
|
214
312
|
const requested = selectMatch[1]!
|
|
215
313
|
.split(',')
|
|
216
314
|
.map((s) => s.trim())
|
|
@@ -219,15 +317,19 @@ export async function executeMetaTool(
|
|
|
219
317
|
const found: any[] = [];
|
|
220
318
|
const errors: string[] = [];
|
|
221
319
|
|
|
320
|
+
const namespace = serverId ?? serverName;
|
|
321
|
+
|
|
222
322
|
for (const requestedToolName of requested) {
|
|
223
|
-
const { tool, error } = resolveToolSchema(requestedToolName
|
|
323
|
+
const { tool, error } = resolveToolSchema(requestedToolName, namespace, {
|
|
324
|
+
allowServerNameFragment: Boolean(serverName && !serverId),
|
|
325
|
+
});
|
|
224
326
|
if (error) {
|
|
225
327
|
const errorMsg = error.content[0]?.type === 'text' ? error.content[0].text : 'Unknown error';
|
|
226
328
|
errors.push(`- **${requestedToolName}**: ${errorMsg}`);
|
|
227
329
|
} else if (tool) {
|
|
228
330
|
found.push(tool);
|
|
229
331
|
} else {
|
|
230
|
-
errors.push(`- **${requestedToolName}**: Tool not found. Try searching with
|
|
332
|
+
errors.push(`- **${requestedToolName}**: Tool not found. Try searching with mcp_search_tools.`);
|
|
231
333
|
}
|
|
232
334
|
}
|
|
233
335
|
|
|
@@ -255,16 +357,31 @@ export async function executeMetaTool(
|
|
|
255
357
|
};
|
|
256
358
|
}
|
|
257
359
|
|
|
258
|
-
const results = await router.searchTools(query, limit);
|
|
360
|
+
const results = await router.searchTools(query, limit, searchOptions);
|
|
259
361
|
|
|
260
362
|
const text = results.length === 0
|
|
261
|
-
? 'No tools found matching your query.
|
|
262
|
-
: 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
|
|
263
381
|
.map(
|
|
264
|
-
(
|
|
265
|
-
`${i + 1}. **${
|
|
266
|
-
` ${
|
|
267
|
-
` Estimated tokens: ${t.estimatedTokens}`
|
|
382
|
+
(server, i) =>
|
|
383
|
+
`${i + 1}. **${server.serverName}** (serverId: ${server.serverId}, sessionId: ${server.sessionId})\n` +
|
|
384
|
+
` Tool count: ${server.toolCount}`
|
|
268
385
|
)
|
|
269
386
|
.join('\n');
|
|
270
387
|
|
|
@@ -282,14 +399,7 @@ export async function executeMetaTool(
|
|
|
282
399
|
|
|
283
400
|
const text = results.length === 0
|
|
284
401
|
? 'No tools matched your regex pattern. Try a broader pattern.'
|
|
285
|
-
: results
|
|
286
|
-
.map(
|
|
287
|
-
(t, i) =>
|
|
288
|
-
`${i + 1}. **${t.name}** (server: ${t.serverName}, serverId: ${t.serverId})\n` +
|
|
289
|
-
` ${t.description}\n` +
|
|
290
|
-
` Estimated tokens: ${t.estimatedTokens}`
|
|
291
|
-
)
|
|
292
|
-
.join('\n');
|
|
402
|
+
: formatToolSummaries(results).join('\n');
|
|
293
403
|
|
|
294
404
|
return {
|
|
295
405
|
content: [{ type: 'text', text }],
|
|
@@ -311,7 +421,7 @@ export async function executeMetaTool(
|
|
|
311
421
|
content: [
|
|
312
422
|
{
|
|
313
423
|
type: 'text',
|
|
314
|
-
text: `Tool "${name}" not found. Use
|
|
424
|
+
text: `Tool "${name}" not found. Use mcp_search_tools to find available tools first.`,
|
|
315
425
|
},
|
|
316
426
|
],
|
|
317
427
|
isError: true,
|
|
@@ -322,6 +432,13 @@ export async function executeMetaTool(
|
|
|
322
432
|
name: tool.name,
|
|
323
433
|
description: tool.description,
|
|
324
434
|
inputSchema: tool.inputSchema,
|
|
435
|
+
executionInstructions: {
|
|
436
|
+
nextTool: 'mcp_execute_tool',
|
|
437
|
+
toolName: tool.name,
|
|
438
|
+
serverId: tool.serverId,
|
|
439
|
+
note:
|
|
440
|
+
'Do not call this discovered tool directly unless it was explicitly registered as a runtime tool. Execute it via mcp_execute_tool and pass these parameters inside args.',
|
|
441
|
+
},
|
|
325
442
|
};
|
|
326
443
|
|
|
327
444
|
return {
|
|
@@ -353,7 +470,7 @@ export async function executeMetaTool(
|
|
|
353
470
|
content: [
|
|
354
471
|
{
|
|
355
472
|
type: 'text',
|
|
356
|
-
text: `Tool "${targetToolName}" not found. Use
|
|
473
|
+
text: `Tool "${targetToolName}" not found. Use mcp_search_tools to discover available tools first.`,
|
|
357
474
|
},
|
|
358
475
|
],
|
|
359
476
|
isError: true,
|
|
@@ -395,10 +512,28 @@ export async function executeMetaTool(
|
|
|
395
512
|
}
|
|
396
513
|
}
|
|
397
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
|
+
|
|
398
532
|
/** Check if a tool name is one of the meta-tools. */
|
|
399
533
|
export function isMetaTool(toolName: string): boolean {
|
|
400
534
|
return (
|
|
401
|
-
toolName === '
|
|
535
|
+
toolName === 'mcp_search_tools' ||
|
|
536
|
+
toolName === 'mcp_list_servers' ||
|
|
402
537
|
toolName === 'mcp_search_tool_regex' ||
|
|
403
538
|
toolName === 'mcp_get_tool_schema' ||
|
|
404
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
|