@mcp-ts/sdk 1.5.3 → 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.
Files changed (46) hide show
  1. package/dist/adapters/agui-adapter.d.mts +1 -1
  2. package/dist/adapters/agui-adapter.d.ts +1 -1
  3. package/dist/adapters/agui-adapter.js +69 -18
  4. package/dist/adapters/agui-adapter.js.map +1 -1
  5. package/dist/adapters/agui-adapter.mjs +69 -18
  6. package/dist/adapters/agui-adapter.mjs.map +1 -1
  7. package/dist/adapters/agui-middleware.d.mts +1 -1
  8. package/dist/adapters/agui-middleware.d.ts +1 -1
  9. package/dist/adapters/ai-adapter.d.mts +1 -1
  10. package/dist/adapters/ai-adapter.d.ts +1 -1
  11. package/dist/adapters/ai-adapter.js +69 -18
  12. package/dist/adapters/ai-adapter.js.map +1 -1
  13. package/dist/adapters/ai-adapter.mjs +69 -18
  14. package/dist/adapters/ai-adapter.mjs.map +1 -1
  15. package/dist/adapters/langchain-adapter.d.mts +1 -1
  16. package/dist/adapters/langchain-adapter.d.ts +1 -1
  17. package/dist/adapters/langchain-adapter.js +69 -18
  18. package/dist/adapters/langchain-adapter.js.map +1 -1
  19. package/dist/adapters/langchain-adapter.mjs +69 -18
  20. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  21. package/dist/client/react.js.map +1 -1
  22. package/dist/client/react.mjs.map +1 -1
  23. package/dist/index.d.mts +2 -2
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.js +200 -42
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +200 -43
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/server/index.js +1 -3
  30. package/dist/server/index.js.map +1 -1
  31. package/dist/server/index.mjs +1 -3
  32. package/dist/server/index.mjs.map +1 -1
  33. package/dist/shared/index.d.mts +15 -8
  34. package/dist/shared/index.d.ts +15 -8
  35. package/dist/shared/index.js +199 -39
  36. package/dist/shared/index.js.map +1 -1
  37. package/dist/shared/index.mjs +199 -40
  38. package/dist/shared/index.mjs.map +1 -1
  39. package/dist/{tool-router-DsKhRmJm.d.ts → tool-router-Bn9R0KWr.d.ts} +56 -7
  40. package/dist/{tool-router-DK0RJblO.d.mts → tool-router-_O2tIwf7.d.mts} +56 -7
  41. package/package.json +3 -1
  42. package/src/server/mcp/oauth-client.ts +4 -4
  43. package/src/shared/index.ts +4 -0
  44. package/src/shared/meta-tools.ts +163 -37
  45. package/src/shared/tool-index.ts +123 -7
  46. package/src/shared/tool-router.ts +40 -7
@@ -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, it tries to match sessionId or serverName.
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
- return list.filter((t) => t.sessionId === namespace || t.serverId === namespace);
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 { ToolIndex, type IndexedTool, type ToolSummary, type EmbedFn } from './tool-index.js';
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 (mcp_search_tool_bm25, mcp_get_tool_schema, mcp_execute_tool)
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(query: string, topK?: number): Promise<ToolSummary[]> {
208
+ async searchTools(
209
+ query: string,
210
+ topK?: number,
211
+ options: ToolSearchOptions = {}
212
+ ): Promise<ToolSummary[]> {
199
213
  await this.ensureInitialized();
200
- return this.index.search(query, topK ?? this.maxTools);
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(toolName: string, namespace?: string): IndexedTool | undefined {
217
- const matches = this.index.getTool(toolName, namespace);
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 mcp_search_tool_bm25 or mcp_search_tool_regex to discover available tools.`
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
  }