@mcp-ts/sdk 1.5.1 → 1.5.2

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 (57) 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 +43 -9
  4. package/dist/adapters/agui-adapter.js.map +1 -1
  5. package/dist/adapters/agui-adapter.mjs +43 -9
  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/agui-middleware.js.map +1 -1
  10. package/dist/adapters/agui-middleware.mjs.map +1 -1
  11. package/dist/adapters/ai-adapter.d.mts +1 -1
  12. package/dist/adapters/ai-adapter.d.ts +1 -1
  13. package/dist/adapters/ai-adapter.js +42 -8
  14. package/dist/adapters/ai-adapter.js.map +1 -1
  15. package/dist/adapters/ai-adapter.mjs +42 -8
  16. package/dist/adapters/ai-adapter.mjs.map +1 -1
  17. package/dist/adapters/langchain-adapter.d.mts +1 -1
  18. package/dist/adapters/langchain-adapter.d.ts +1 -1
  19. package/dist/adapters/langchain-adapter.js +42 -8
  20. package/dist/adapters/langchain-adapter.js.map +1 -1
  21. package/dist/adapters/langchain-adapter.mjs +42 -8
  22. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  23. package/dist/client/react.d.mts +10 -0
  24. package/dist/client/react.d.ts +10 -0
  25. package/dist/client/react.js +23 -0
  26. package/dist/client/react.js.map +1 -1
  27. package/dist/client/react.mjs +23 -0
  28. package/dist/client/react.mjs.map +1 -1
  29. package/dist/client/vue.d.mts +10 -0
  30. package/dist/client/vue.d.ts +10 -0
  31. package/dist/client/vue.js +17 -0
  32. package/dist/client/vue.js.map +1 -1
  33. package/dist/client/vue.mjs +17 -0
  34. package/dist/client/vue.mjs.map +1 -1
  35. package/dist/index.d.mts +1 -1
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.js +115 -26
  38. package/dist/index.js.map +1 -1
  39. package/dist/index.mjs +115 -26
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/shared/index.d.mts +2 -2
  42. package/dist/shared/index.d.ts +2 -2
  43. package/dist/shared/index.js +115 -26
  44. package/dist/shared/index.js.map +1 -1
  45. package/dist/shared/index.mjs +115 -26
  46. package/dist/shared/index.mjs.map +1 -1
  47. package/dist/{tool-router-XnWVxPzv.d.mts → tool-router-DK0RJblO.d.mts} +3 -0
  48. package/dist/{tool-router-Bo8qZbsD.d.ts → tool-router-DsKhRmJm.d.ts} +3 -0
  49. package/package.json +1 -1
  50. package/src/adapters/agui-adapter.ts +7 -7
  51. package/src/adapters/ai-adapter.ts +5 -5
  52. package/src/adapters/langchain-adapter.ts +5 -5
  53. package/src/client/react/use-mcp.ts +48 -0
  54. package/src/client/vue/use-mcp.ts +42 -0
  55. package/src/shared/meta-tools.ts +62 -13
  56. package/src/shared/tool-index.ts +85 -12
  57. package/src/shared/tool-router.ts +8 -7
@@ -137,11 +137,11 @@ export class AIAdapter {
137
137
  // @ts-ignore: ToolSet type inference can be tricky with dynamic imports
138
138
  return Object.fromEntries(
139
139
  filteredTools.map((tool) => {
140
- const routedTool = tool as typeof tool & { sessionId?: string; serverName?: string };
141
- const namespace = routedTool.serverName ?? routedTool.sessionId;
140
+ const routedTool = tool as typeof tool & { sessionId?: string; serverId?: string; serverName?: string };
141
+ const namespace = routedTool.serverId ?? routedTool.sessionId;
142
142
  const toolKey = isMetaTool(tool.name)
143
143
  ? tool.name
144
- : this.getRouterToolKey(tool.name, routedTool.sessionId, routedTool.serverName);
144
+ : this.getRouterToolKey(tool.name, routedTool.sessionId, routedTool.serverId);
145
145
 
146
146
  return [
147
147
  toolKey,
@@ -172,8 +172,8 @@ export class AIAdapter {
172
172
  );
173
173
  }
174
174
 
175
- private getRouterToolKey(toolName: string, sessionId?: string, serverName?: string): string {
176
- const namespace = sessionId ?? serverName ?? 'mcp';
175
+ private getRouterToolKey(toolName: string, sessionId?: string, serverId?: string): string {
176
+ const namespace = sessionId ?? serverId ?? 'mcp';
177
177
  const normalized = namespace
178
178
  .toLowerCase()
179
179
  .replace(/[^a-z0-9]+/g, '_')
@@ -144,14 +144,14 @@ export class LangChainAdapter {
144
144
  const filteredTools = await router.getFilteredTools();
145
145
 
146
146
  return filteredTools.map((tool) => {
147
- const routedTool = tool as typeof tool & { sessionId?: string; serverName?: string };
148
- const namespace = routedTool.serverName ?? routedTool.sessionId;
147
+ const routedTool = tool as typeof tool & { sessionId?: string; serverId?: string; serverName?: string };
148
+ const namespace = routedTool.serverId ?? routedTool.sessionId;
149
149
  const schema = this.jsonSchemaToZod(tool.inputSchema);
150
150
 
151
151
  return new this.DynamicStructuredTool!({
152
152
  name: isMetaTool(tool.name)
153
153
  ? tool.name
154
- : this.getRouterToolKey(tool.name, routedTool.sessionId, routedTool.serverName),
154
+ : this.getRouterToolKey(tool.name, routedTool.sessionId, routedTool.serverId),
155
155
  description: tool.description || `Tool ${tool.name}`,
156
156
  schema: schema,
157
157
  func: async (args: any) => {
@@ -183,8 +183,8 @@ export class LangChainAdapter {
183
183
  });
184
184
  }
185
185
 
186
- private getRouterToolKey(toolName: string, sessionId?: string, serverName?: string): string {
187
- const namespace = sessionId ?? serverName ?? 'mcp';
186
+ private getRouterToolKey(toolName: string, sessionId?: string, serverId?: string): string {
187
+ const namespace = sessionId ?? serverId ?? 'mcp';
188
188
  const normalized = namespace
189
189
  .toLowerCase()
190
190
  .replace(/[^a-z0-9]+/g, '_')
@@ -118,6 +118,17 @@ export interface McpClient {
118
118
  */
119
119
  disconnect: (sessionId: string) => Promise<void>;
120
120
 
121
+ /**
122
+ * Reconnect to an MCP server (disconnects existing session first)
123
+ */
124
+ reconnect: (params: {
125
+ serverId: string;
126
+ serverName: string;
127
+ serverUrl: string;
128
+ callbackUrl: string;
129
+ transportType?: 'sse' | 'streamable_http';
130
+ }) => Promise<string>;
131
+
121
132
  /**
122
133
  * Get connection by session ID
123
134
  */
@@ -499,6 +510,41 @@ export function useMcp(options: UseMcpOptions): McpClient {
499
510
  []
500
511
  );
501
512
 
513
+ /**
514
+ * Reconnect to an MCP server (tears down existing session, then connects fresh)
515
+ */
516
+ const reconnect = useCallback(
517
+ async (params: {
518
+ serverId: string;
519
+ serverName: string;
520
+ serverUrl: string;
521
+ callbackUrl: string;
522
+ transportType?: 'sse' | 'streamable_http';
523
+ }): Promise<string> => {
524
+ if (!clientRef.current) {
525
+ throw new Error('SSE client not initialized');
526
+ }
527
+
528
+ // Find and disconnect existing session for the same server
529
+ const existing = connections.find(
530
+ (c: McpConnection) => c.serverId === params.serverId || c.serverUrl === params.serverUrl
531
+ );
532
+ if (existing) {
533
+ await clientRef.current.disconnectFromServer(existing.sessionId);
534
+ if (isMountedRef.current) {
535
+ setConnections((prev: McpConnection[]) =>
536
+ prev.filter((c: McpConnection) => c.sessionId !== existing.sessionId)
537
+ );
538
+ }
539
+ }
540
+
541
+ // Connect fresh
542
+ const result = await clientRef.current.connectToServer(params);
543
+ return result.sessionId;
544
+ },
545
+ [connections]
546
+ );
547
+
502
548
  /**
503
549
  * Disconnect from an MCP server
504
550
  */
@@ -668,6 +714,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
668
714
  status,
669
715
  isInitializing,
670
716
  connect,
717
+ reconnect,
671
718
  disconnect,
672
719
  getConnection,
673
720
  getConnectionByServerId,
@@ -691,6 +738,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
691
738
  status,
692
739
  isInitializing,
693
740
  connect,
741
+ reconnect,
694
742
  disconnect,
695
743
  getConnection,
696
744
  getConnectionByServerId,
@@ -117,6 +117,17 @@ export interface McpClient {
117
117
  */
118
118
  disconnect: (sessionId: string) => Promise<void>;
119
119
 
120
+ /**
121
+ * Reconnect to an MCP server (disconnects existing session first)
122
+ */
123
+ reconnect: (params: {
124
+ serverId: string;
125
+ serverName: string;
126
+ serverUrl: string;
127
+ callbackUrl: string;
128
+ transportType?: 'sse' | 'streamable_http';
129
+ }) => Promise<string>;
130
+
120
131
  /**
121
132
  * Get connection by session ID
122
133
  */
@@ -492,6 +503,36 @@ export function useMcp(options: UseMcpOptions): McpClient {
492
503
  return result.sessionId;
493
504
  };
494
505
 
506
+ /**
507
+ * Reconnect to an MCP server (tears down existing session, then connects fresh)
508
+ */
509
+ const reconnect = async (params: {
510
+ serverId: string;
511
+ serverName: string;
512
+ serverUrl: string;
513
+ callbackUrl: string;
514
+ transportType?: 'sse' | 'streamable_http';
515
+ }): Promise<string> => {
516
+ if (!clientRef.value) {
517
+ throw new Error('SSE client not initialized');
518
+ }
519
+
520
+ // Find and disconnect existing session for the same server
521
+ const existing = connections.value.find(
522
+ (c) => c.serverId === params.serverId || c.serverUrl === params.serverUrl
523
+ );
524
+ if (existing) {
525
+ await clientRef.value.disconnectFromServer(existing.sessionId);
526
+ if (isMountedRef.value) {
527
+ connections.value = connections.value.filter((c) => c.sessionId !== existing.sessionId);
528
+ }
529
+ }
530
+
531
+ // Connect fresh
532
+ const result = await clientRef.value.connectToServer(params);
533
+ return result.sessionId;
534
+ };
535
+
495
536
  /**
496
537
  * Disconnect from an MCP server
497
538
  */
@@ -642,6 +683,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
642
683
  status: status as unknown as { value: 'connecting' | 'connected' | 'disconnected' | 'error' },
643
684
  isInitializing: isInitializing as unknown as { value: boolean },
644
685
  connect,
686
+ reconnect,
645
687
  disconnect,
646
688
  getConnection,
647
689
  getConnectionByServerId,
@@ -33,16 +33,18 @@ export function createSearchToolDefinition(): Tool {
33
33
  return {
34
34
  name: 'mcp_search_tool_bm25',
35
35
  description:
36
- 'Search the catalog of available tools using BM25 natural language ranking. ' +
37
- 'Returns tool names, descriptions, and server info. ' +
38
- 'Use this FIRST to find relevant tools before calling them. ' +
39
- 'Example queries: "database query", "send email", "github pull request".',
36
+ 'Search the catalog of available tools. Returns tool names, descriptions, and server info. ' +
37
+ 'Use this FIRST to find relevant tools before calling them.\n\n' +
38
+ 'Query forms:\n' +
39
+ '- "select:Read,Edit,Grep" fetch these exact tools by name\n' +
40
+ '- "notebook jupyter" — keyword search, up to limit best matches\n' +
41
+ '- "+slack send" — require "slack" in the name, rank by remaining terms',
40
42
  inputSchema: {
41
43
  type: 'object' as const,
42
44
  properties: {
43
45
  query: {
44
46
  type: 'string',
45
- description: 'Natural language description of the capability you need.',
47
+ description: 'Query to find tools. Use "select:<tool_name>" for direct selection, or keywords to search. Prefix keywords with + to require them.',
46
48
  },
47
49
  limit: {
48
50
  type: 'number',
@@ -105,10 +107,10 @@ export function createGetSchemaToolDefinition(): Tool {
105
107
  type: 'string',
106
108
  description: 'The exact tool name returned by mcp_search_tool_bm25.',
107
109
  },
108
- serverName: {
110
+ serverId: {
109
111
  type: 'string',
110
112
  description:
111
- 'Optional: The server name provided in mcp_search_tool_bm25. Required if multiple tools have the same name.',
113
+ 'Optional: The server ID provided in mcp_search_tool_bm25. Required if multiple tools have the same name.',
112
114
  },
113
115
  },
114
116
  required: ['toolName'],
@@ -141,10 +143,10 @@ export function createExecuteToolDefinition(): Tool {
141
143
  type: 'string',
142
144
  description: 'The exact tool name from mcp_search_tool_bm25 results.',
143
145
  },
144
- serverName: {
146
+ serverId: {
145
147
  type: 'string',
146
148
  description:
147
- 'Optional: The server name provided in mcp_search_tool_bm25. Required if multiple tools have the same name.',
149
+ 'Optional: The server ID provided in mcp_search_tool_bm25. Required if multiple tools have the same name.',
148
150
  },
149
151
  args: {
150
152
  type: 'object',
@@ -206,6 +208,53 @@ export async function executeMetaTool(
206
208
  const query = String(args.query ?? '');
207
209
  const limit = Math.min(Number(args.limit) || 5, 20);
208
210
 
211
+ // Fast path: Check for select: prefix
212
+ const selectMatch = query.match(/^select:(.+)$/i);
213
+ if (selectMatch) {
214
+ const requested = selectMatch[1]!
215
+ .split(',')
216
+ .map((s) => s.trim())
217
+ .filter(Boolean);
218
+
219
+ const found: any[] = [];
220
+ const errors: string[] = [];
221
+
222
+ for (const requestedToolName of requested) {
223
+ const { tool, error } = resolveToolSchema(requestedToolName);
224
+ if (error) {
225
+ const errorMsg = error.content[0]?.type === 'text' ? error.content[0].text : 'Unknown error';
226
+ errors.push(`- **${requestedToolName}**: ${errorMsg}`);
227
+ } else if (tool) {
228
+ found.push(tool);
229
+ } else {
230
+ errors.push(`- **${requestedToolName}**: Tool not found. Try searching with mcp_search_tool_bm25.`);
231
+ }
232
+ }
233
+
234
+ const lines: string[] = [];
235
+
236
+ if (found.length > 0) {
237
+ lines.push(...found.map((t, i) =>
238
+ `${i + 1}. **${t.name}** (server: ${t.serverName}, serverId: ${t.serverId})\n ${t.description}`
239
+ ));
240
+ }
241
+
242
+ if (errors.length > 0) {
243
+ if (lines.length > 0) lines.push(""); // Add empty line spacing
244
+ lines.push("Errors resolving some tools:");
245
+ lines.push(...errors);
246
+ }
247
+
248
+ const text = lines.length > 0
249
+ ? lines.join('\n')
250
+ : `No tools found matching select query: ${requested.join(', ')}`;
251
+
252
+ return {
253
+ content: [{ type: 'text', text }],
254
+ isError: found.length === 0,
255
+ };
256
+ }
257
+
209
258
  const results = await router.searchTools(query, limit);
210
259
 
211
260
  const text = results.length === 0
@@ -213,7 +262,7 @@ export async function executeMetaTool(
213
262
  : results
214
263
  .map(
215
264
  (t, i) =>
216
- `${i + 1}. **${t.name}** (server: ${t.serverName})\n` +
265
+ `${i + 1}. **${t.name}** (server: ${t.serverName}, serverId: ${t.serverId})\n` +
217
266
  ` ${t.description}\n` +
218
267
  ` Estimated tokens: ${t.estimatedTokens}`
219
268
  )
@@ -236,7 +285,7 @@ export async function executeMetaTool(
236
285
  : results
237
286
  .map(
238
287
  (t, i) =>
239
- `${i + 1}. **${t.name}** (server: ${t.serverName})\n` +
288
+ `${i + 1}. **${t.name}** (server: ${t.serverName}, serverId: ${t.serverId})\n` +
240
289
  ` ${t.description}\n` +
241
290
  ` Estimated tokens: ${t.estimatedTokens}`
242
291
  )
@@ -250,7 +299,7 @@ export async function executeMetaTool(
250
299
 
251
300
  case 'mcp_get_tool_schema': {
252
301
  const name = String(args.toolName ?? '');
253
- const namespace = String(args.serverName ?? '') || undefined;
302
+ const namespace = String(args.serverId ?? '') || undefined;
254
303
  const { tool, error } = resolveToolSchema(name, namespace);
255
304
 
256
305
  if (error) {
@@ -283,7 +332,7 @@ export async function executeMetaTool(
283
332
 
284
333
  case 'mcp_execute_tool': {
285
334
  const targetToolName = String(args.toolName ?? '');
286
- const namespace = String(args.serverName ?? '') || undefined;
335
+ const namespace = String(args.serverId ?? '') || undefined;
287
336
  const toolArgs = (args.args as Record<string, unknown>) ?? {};
288
337
 
289
338
  if (!targetToolName) {
@@ -24,6 +24,8 @@ export interface ToolSummary {
24
24
  description: string;
25
25
  /** Server that owns this tool */
26
26
  serverName: string;
27
+ /** Unique ID of the server */
28
+ serverId: string;
27
29
  /** Session the tool belongs to */
28
30
  sessionId: string;
29
31
  /** Estimated token cost of the full inputSchema */
@@ -33,6 +35,7 @@ export interface ToolSummary {
33
35
  /** A tool with routing metadata attached during indexing. */
34
36
  export interface IndexedTool extends Tool {
35
37
  sessionId: string;
38
+ serverId: string;
36
39
  serverName: string;
37
40
  }
38
41
 
@@ -177,6 +180,7 @@ export class ToolIndex {
177
180
  name: tool.name,
178
181
  description: tool.description ?? '',
179
182
  serverName: tool.serverName,
183
+ serverId: tool.serverId,
180
184
  sessionId: tool.sessionId,
181
185
  estimatedTokens,
182
186
  });
@@ -258,8 +262,55 @@ export class ToolIndex {
258
262
  async search(query: string, topK = 5): Promise<ToolSummary[]> {
259
263
  if (this.tools.size === 0) return [];
260
264
 
261
- const queryLower = query.toLowerCase();
262
- const queryTokens = this.tokenize(queryLower);
265
+ const queryLower = query.toLowerCase().trim();
266
+
267
+ // Fast path: Exact tool name match (supports duplicate names across servers)
268
+ const exactMatches = [...this.toolSummaries.values()].filter(
269
+ (summary) => summary.name.toLowerCase() === queryLower
270
+ );
271
+ if (exactMatches.length > 0) {
272
+ return exactMatches.slice(0, topK);
273
+ }
274
+
275
+ // Fast path: MCP prefix match (e.g. "mcp__github")
276
+ if (queryLower.startsWith('mcp__') && queryLower.length > 5) {
277
+ const prefixMatches = [...this.toolSummaries.values()]
278
+ .filter((t) => t.name.toLowerCase().startsWith(queryLower))
279
+ .slice(0, topK);
280
+ if (prefixMatches.length > 0) return prefixMatches;
281
+ }
282
+
283
+ const queryTermsRaw = queryLower.split(/\s+/).filter((t) => t.length > 0);
284
+ const requiredTerms: string[] = [];
285
+ const optionalTerms: string[] = [];
286
+
287
+ for (const term of queryTermsRaw) {
288
+ if (term.startsWith('+') && term.length > 1) {
289
+ requiredTerms.push(term.slice(1));
290
+ } else {
291
+ optionalTerms.push(term);
292
+ }
293
+ }
294
+
295
+ const allScoringTerms =
296
+ requiredTerms.length > 0 ? [...requiredTerms, ...optionalTerms] : queryTermsRaw;
297
+ const normalizedQueryText = allScoringTerms.join(' ').trim();
298
+ const queryTokens = this.tokenize(allScoringTerms.join(' '));
299
+
300
+ // Pre-filter: only keep documents that contain ALL required terms
301
+ const candidateKeys = new Set<string>();
302
+ for (const docKey of this.toolSummaries.keys()) {
303
+ if (requiredTerms.length > 0) {
304
+ const text = this.searchTexts.get(docKey) || '';
305
+ const summary = this.toolSummaries.get(docKey)!;
306
+ const nameLower = summary.name.toLowerCase();
307
+ const matchesAll = requiredTerms.every(
308
+ (term) => text.includes(term) || nameLower.includes(term)
309
+ );
310
+ if (!matchesAll) continue;
311
+ }
312
+ candidateKeys.add(docKey);
313
+ }
263
314
 
264
315
  // 1. Keyword scores (BM25)
265
316
  const keywordScores = new Map<string, number>();
@@ -267,7 +318,12 @@ export class ToolIndex {
267
318
  const k1 = 1.2;
268
319
  const b = 0.75;
269
320
 
270
- for (const [docKey, docTf] of this.tfVectors) {
321
+ for (const docKey of candidateKeys) {
322
+ const docTf = this.tfVectors.get(docKey);
323
+ if (!docTf) continue;
324
+
325
+ const summary = this.toolSummaries.get(docKey)!;
326
+
271
327
  let score = 0;
272
328
  const docLen = this.docLengths.get(docKey) ?? 0;
273
329
 
@@ -276,16 +332,30 @@ export class ToolIndex {
276
332
  if (tfVal === 0) continue;
277
333
 
278
334
  const idf = this.idf.get(tok) ?? 0;
279
-
280
335
  // BM25 formula:
281
336
  // score = idf * (tf * (k1 + 1)) / (tf + k1 * (1 - b + b * (docLen / avgDocLength)))
282
337
  const numerator = tfVal * (k1 + 1);
283
338
  const denominator = tfVal + k1 * (1 - b + b * (docLen / this.avgDocLength));
284
-
339
+
285
340
  score += idf * (numerator / denominator);
286
341
  }
287
342
 
288
- keywordScores.set(docKey, score);
343
+ // Name heuristics: give massive boosts for exact server/tool name matches
344
+ const serverLower = (summary.serverName || summary.serverId || '').toLowerCase();
345
+ const toolLower = summary.name.toLowerCase();
346
+
347
+ for (const term of allScoringTerms) {
348
+ if (serverLower.includes(term)) {
349
+ score += 10;
350
+ }
351
+ if (toolLower.includes(term)) {
352
+ score += 5;
353
+ }
354
+ }
355
+
356
+ if (score > 0) {
357
+ keywordScores.set(docKey, score);
358
+ }
289
359
  }
290
360
 
291
361
  // 2. Embedding scores (optional)
@@ -293,11 +363,14 @@ export class ToolIndex {
293
363
 
294
364
  if (this.options.embedFn && this.embeddings.size > 0) {
295
365
  try {
296
- const [queryEmbedding] = await this.options.embedFn([queryLower]);
366
+ const [queryEmbedding] = await this.options.embedFn([normalizedQueryText]);
297
367
  if (queryEmbedding) {
298
368
  embeddingScores = new Map();
299
- for (const [docKey, vec] of this.embeddings) {
300
- embeddingScores.set(docKey, this.cosineSimilarity(queryEmbedding, vec));
369
+ for (const docKey of candidateKeys) {
370
+ const vec = this.embeddings.get(docKey);
371
+ if (vec) {
372
+ embeddingScores.set(docKey, this.cosineSimilarity(queryEmbedding, vec));
373
+ }
301
374
  }
302
375
  }
303
376
  } catch {
@@ -309,7 +382,7 @@ export class ToolIndex {
309
382
  const kw = this.options.keywordWeight;
310
383
  const finalScores: Array<{ docKey: string; score: number }> = [];
311
384
 
312
- for (const docKey of this.toolSummaries.keys()) {
385
+ for (const docKey of candidateKeys) {
313
386
  const kwScore = keywordScores.get(docKey) ?? 0;
314
387
  const embScore = embeddingScores?.get(docKey) ?? 0;
315
388
 
@@ -389,7 +462,7 @@ export class ToolIndex {
389
462
  const list = this.tools.get(name) ?? [];
390
463
  if (!namespace) return list;
391
464
 
392
- return list.filter((t) => t.sessionId === namespace || t.serverName === namespace);
465
+ return list.filter((t) => t.sessionId === namespace || t.serverId === namespace);
393
466
  }
394
467
 
395
468
  /** All indexed tool names. */
@@ -463,7 +536,7 @@ export class ToolIndex {
463
536
  }
464
537
 
465
538
  private getDocumentKey(tool: IndexedTool): string {
466
- return `${tool.sessionId}::${tool.serverName}::${tool.name}`;
539
+ return `${tool.sessionId}::${tool.serverId}::${tool.name}`;
467
540
  }
468
541
 
469
542
  /** Simple whitespace + camelCase + snake_case tokenizer. */
@@ -219,10 +219,10 @@ export class ToolRouter {
219
219
  if (matches.length === 0) return undefined;
220
220
 
221
221
  if (matches.length > 1) {
222
- const servers = matches.map((m) => m.serverName).join(', ');
222
+ const servers = matches.map((m) => m.serverId).join(', ');
223
223
  throw new Error(
224
224
  `Tool "${toolName}" is provided by multiple servers: [${servers}]. ` +
225
- `Please specify the desired "serverName" as a namespace.`
225
+ `Please specify the desired "serverId" as a namespace.`
226
226
  );
227
227
  }
228
228
 
@@ -372,6 +372,7 @@ export class ToolRouter {
372
372
  for (const tool of tools) {
373
373
  result.push({
374
374
  ...tool,
375
+ serverId,
375
376
  serverName: serverName,
376
377
  sessionId,
377
378
  });
@@ -409,20 +410,20 @@ export class ToolRouter {
409
410
  });
410
411
  }
411
412
  } else {
412
- // Auto-group by server name
413
+ // Auto-group by server ID
413
414
  const serverTools = new Map<string, string[]>();
414
415
  for (const tool of this.allTools) {
415
- const group = tool.serverName;
416
+ const group = tool.serverId;
416
417
  if (!serverTools.has(group)) {
417
418
  serverTools.set(group, []);
418
419
  }
419
420
  serverTools.get(group)!.push(tool.name);
420
421
  }
421
422
 
422
- for (const [serverName, tools] of serverTools) {
423
- this.groupsMap.set(serverName, {
423
+ for (const [serverId, tools] of serverTools) {
424
+ this.groupsMap.set(serverId, {
424
425
  tools,
425
- active: this.activeGroups.size === 0 || this.activeGroups.has(serverName),
426
+ active: this.activeGroups.size === 0 || this.activeGroups.has(serverId),
426
427
  });
427
428
  }
428
429
  }