@juspay/neurolink 7.13.0 → 7.14.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.
Files changed (132) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +89 -25
  3. package/dist/agent/directTools.d.ts +3 -3
  4. package/dist/agent/directTools.js +1 -1
  5. package/dist/cli/commands/mcp.js +67 -207
  6. package/dist/cli/factories/commandFactory.js +7 -1
  7. package/dist/cli/utils/interactiveSetup.js +1 -1
  8. package/dist/config/conversationMemoryConfig.js +2 -1
  9. package/dist/context/ContextManager.js +15 -4
  10. package/dist/context/config.js +5 -1
  11. package/dist/context/utils.js +1 -1
  12. package/dist/core/baseProvider.d.ts +11 -30
  13. package/dist/core/baseProvider.js +268 -42
  14. package/dist/core/conversationMemoryManager.js +3 -2
  15. package/dist/core/dynamicModels.d.ts +14 -14
  16. package/dist/core/dynamicModels.js +1 -1
  17. package/dist/core/evaluation.js +1 -1
  18. package/dist/core/factory.js +1 -1
  19. package/dist/factories/providerFactory.js +5 -11
  20. package/dist/factories/providerRegistry.js +2 -2
  21. package/dist/index.d.ts +5 -4
  22. package/dist/index.js +1 -1
  23. package/dist/lib/agent/directTools.js +1 -1
  24. package/dist/lib/config/conversationMemoryConfig.js +2 -1
  25. package/dist/lib/context/ContextManager.js +15 -4
  26. package/dist/lib/context/config.js +5 -1
  27. package/dist/lib/context/utils.js +1 -1
  28. package/dist/lib/core/baseProvider.d.ts +11 -30
  29. package/dist/lib/core/baseProvider.js +268 -42
  30. package/dist/lib/core/conversationMemoryManager.js +3 -2
  31. package/dist/lib/core/dynamicModels.js +1 -1
  32. package/dist/lib/core/evaluation.js +1 -1
  33. package/dist/lib/core/factory.js +1 -1
  34. package/dist/lib/factories/providerFactory.js +5 -11
  35. package/dist/lib/factories/providerRegistry.js +2 -2
  36. package/dist/lib/index.d.ts +5 -4
  37. package/dist/lib/index.js +1 -1
  38. package/dist/lib/mcp/externalServerManager.d.ts +148 -0
  39. package/dist/lib/mcp/externalServerManager.js +1038 -0
  40. package/dist/lib/mcp/mcpCircuitBreaker.d.ts +184 -0
  41. package/dist/lib/mcp/mcpCircuitBreaker.js +338 -0
  42. package/dist/lib/mcp/mcpClientFactory.d.ts +105 -0
  43. package/dist/lib/mcp/mcpClientFactory.js +421 -0
  44. package/dist/lib/mcp/toolDiscoveryService.d.ts +193 -0
  45. package/dist/lib/mcp/toolDiscoveryService.js +646 -0
  46. package/dist/lib/mcp/toolRegistry.d.ts +15 -11
  47. package/dist/lib/mcp/toolRegistry.js +118 -55
  48. package/dist/lib/models/modelResolver.js +1 -1
  49. package/dist/lib/neurolink.d.ts +139 -43
  50. package/dist/lib/neurolink.js +604 -174
  51. package/dist/lib/providers/googleVertex.d.ts +7 -1
  52. package/dist/lib/providers/googleVertex.js +34 -7
  53. package/dist/lib/providers/huggingFace.js +1 -1
  54. package/dist/lib/providers/mistral.js +3 -3
  55. package/dist/lib/providers/ollama.js +1 -1
  56. package/dist/lib/providers/openAI.d.ts +3 -2
  57. package/dist/lib/providers/openAI.js +2 -2
  58. package/dist/lib/providers/openaiCompatible.d.ts +1 -1
  59. package/dist/lib/providers/openaiCompatible.js +2 -2
  60. package/dist/lib/providers/sagemaker/config.js +1 -1
  61. package/dist/lib/sdk/toolRegistration.d.ts +4 -13
  62. package/dist/lib/sdk/toolRegistration.js +19 -66
  63. package/dist/lib/types/cli.d.ts +0 -1
  64. package/dist/lib/types/cli.js +0 -1
  65. package/dist/lib/types/common.d.ts +1 -2
  66. package/dist/lib/types/common.js +0 -1
  67. package/dist/lib/types/contextTypes.d.ts +1 -1
  68. package/dist/lib/types/contextTypes.js +3 -3
  69. package/dist/lib/types/externalMcp.d.ts +288 -0
  70. package/dist/lib/types/externalMcp.js +7 -0
  71. package/dist/lib/types/generateTypes.d.ts +0 -1
  72. package/dist/lib/types/index.d.ts +2 -2
  73. package/dist/lib/types/index.js +0 -1
  74. package/dist/lib/types/mcpTypes.d.ts +53 -99
  75. package/dist/lib/types/providers.d.ts +0 -1
  76. package/dist/lib/types/providers.js +0 -1
  77. package/dist/lib/types/tools.d.ts +2 -2
  78. package/dist/lib/types/tools.js +2 -2
  79. package/dist/lib/utils/factoryProcessing.js +1 -1
  80. package/dist/lib/utils/mcpDefaults.d.ts +54 -0
  81. package/dist/lib/utils/mcpDefaults.js +125 -0
  82. package/dist/lib/utils/providerConfig.d.ts +1 -1
  83. package/dist/lib/utils/providerConfig.js +2 -2
  84. package/dist/lib/utils/providerHealth.js +6 -6
  85. package/dist/mcp/externalServerManager.d.ts +148 -0
  86. package/dist/mcp/externalServerManager.js +1038 -0
  87. package/dist/mcp/mcpCircuitBreaker.d.ts +184 -0
  88. package/dist/mcp/mcpCircuitBreaker.js +338 -0
  89. package/dist/mcp/mcpClientFactory.d.ts +105 -0
  90. package/dist/mcp/mcpClientFactory.js +421 -0
  91. package/dist/mcp/toolDiscoveryService.d.ts +193 -0
  92. package/dist/mcp/toolDiscoveryService.js +646 -0
  93. package/dist/mcp/toolRegistry.d.ts +15 -11
  94. package/dist/mcp/toolRegistry.js +118 -55
  95. package/dist/models/modelResolver.js +1 -1
  96. package/dist/neurolink.d.ts +139 -43
  97. package/dist/neurolink.js +604 -174
  98. package/dist/providers/googleVertex.d.ts +7 -1
  99. package/dist/providers/googleVertex.js +34 -7
  100. package/dist/providers/huggingFace.js +1 -1
  101. package/dist/providers/mistral.js +3 -3
  102. package/dist/providers/ollama.js +1 -1
  103. package/dist/providers/openAI.d.ts +3 -2
  104. package/dist/providers/openAI.js +2 -2
  105. package/dist/providers/openaiCompatible.d.ts +1 -1
  106. package/dist/providers/openaiCompatible.js +2 -2
  107. package/dist/providers/sagemaker/config.js +1 -1
  108. package/dist/sdk/toolRegistration.d.ts +4 -13
  109. package/dist/sdk/toolRegistration.js +19 -66
  110. package/dist/types/cli.d.ts +0 -1
  111. package/dist/types/cli.js +0 -1
  112. package/dist/types/common.d.ts +1 -2
  113. package/dist/types/common.js +0 -1
  114. package/dist/types/contextTypes.d.ts +1 -1
  115. package/dist/types/contextTypes.js +3 -3
  116. package/dist/types/externalMcp.d.ts +288 -0
  117. package/dist/types/externalMcp.js +7 -0
  118. package/dist/types/generateTypes.d.ts +0 -1
  119. package/dist/types/index.d.ts +2 -2
  120. package/dist/types/index.js +0 -1
  121. package/dist/types/mcpTypes.d.ts +53 -99
  122. package/dist/types/providers.d.ts +0 -1
  123. package/dist/types/providers.js +0 -1
  124. package/dist/types/tools.d.ts +2 -2
  125. package/dist/types/tools.js +2 -2
  126. package/dist/utils/factoryProcessing.js +1 -1
  127. package/dist/utils/mcpDefaults.d.ts +54 -0
  128. package/dist/utils/mcpDefaults.js +125 -0
  129. package/dist/utils/providerConfig.d.ts +1 -1
  130. package/dist/utils/providerConfig.js +2 -2
  131. package/dist/utils/providerHealth.js +6 -6
  132. package/package.json +1 -1
@@ -0,0 +1,646 @@
1
+ /**
2
+ * Tool Discovery Service
3
+ * Automatically discovers and registers tools from external MCP servers
4
+ * Handles tool validation, transformation, and lifecycle management
5
+ */
6
+ import { EventEmitter } from "events";
7
+ import { mcpLogger } from "../utils/logger.js";
8
+ import { globalCircuitBreakerManager, } from "./mcpCircuitBreaker.js";
9
+ /**
10
+ * ToolDiscoveryService
11
+ * Handles automatic tool discovery and registration from external MCP servers
12
+ */
13
+ export class ToolDiscoveryService extends EventEmitter {
14
+ serverToolStorage = new Map();
15
+ toolRegistry = new Map();
16
+ serverTools = new Map();
17
+ discoveryInProgress = new Set();
18
+ constructor() {
19
+ super();
20
+ }
21
+ /**
22
+ * Discover tools from an external MCP server
23
+ */
24
+ async discoverTools(serverId, client, timeout = 10000) {
25
+ const startTime = Date.now();
26
+ try {
27
+ // Prevent concurrent discovery for same server
28
+ if (this.discoveryInProgress.has(serverId)) {
29
+ return {
30
+ success: false,
31
+ error: `Discovery already in progress for server: ${serverId}`,
32
+ toolCount: 0,
33
+ tools: [],
34
+ duration: Date.now() - startTime,
35
+ serverId,
36
+ };
37
+ }
38
+ this.discoveryInProgress.add(serverId);
39
+ mcpLogger.info(`[ToolDiscoveryService] Starting tool discovery for server: ${serverId}`);
40
+ // Create circuit breaker for tool discovery
41
+ const circuitBreaker = globalCircuitBreakerManager.getBreaker(`tool-discovery-${serverId}`, {
42
+ failureThreshold: 2,
43
+ resetTimeout: 60000,
44
+ operationTimeout: timeout,
45
+ });
46
+ // Discover tools with circuit breaker protection
47
+ const tools = await circuitBreaker.execute(async () => {
48
+ return await this.performToolDiscovery(serverId, client, timeout);
49
+ });
50
+ // Register discovered tools
51
+ const registeredTools = await this.registerDiscoveredTools(serverId, tools);
52
+ const result = {
53
+ success: true,
54
+ toolCount: registeredTools.length,
55
+ tools: registeredTools,
56
+ duration: Date.now() - startTime,
57
+ serverId,
58
+ };
59
+ // Emit discovery completed event
60
+ this.emit("discoveryCompleted", {
61
+ serverId,
62
+ toolCount: registeredTools.length,
63
+ duration: result.duration,
64
+ timestamp: new Date(),
65
+ });
66
+ mcpLogger.info(`[ToolDiscoveryService] Discovery completed for ${serverId}: ${registeredTools.length} tools`);
67
+ return result;
68
+ }
69
+ catch (error) {
70
+ const errorMessage = error instanceof Error ? error.message : String(error);
71
+ mcpLogger.error(`[ToolDiscoveryService] Discovery failed for ${serverId}:`, error);
72
+ // Emit discovery failed event
73
+ this.emit("discoveryFailed", {
74
+ serverId,
75
+ error: errorMessage,
76
+ timestamp: new Date(),
77
+ });
78
+ return {
79
+ success: false,
80
+ error: errorMessage,
81
+ toolCount: 0,
82
+ tools: [],
83
+ duration: Date.now() - startTime,
84
+ serverId,
85
+ };
86
+ }
87
+ finally {
88
+ this.discoveryInProgress.delete(serverId);
89
+ }
90
+ }
91
+ /**
92
+ * Perform the actual tool discovery
93
+ */
94
+ async performToolDiscovery(serverId, client, timeout) {
95
+ // List tools from the MCP server
96
+ const listToolsPromise = client.listTools();
97
+ const timeoutPromise = this.createTimeoutPromise(timeout, "Tool discovery timeout");
98
+ const result = await Promise.race([listToolsPromise, timeoutPromise]);
99
+ if (!result || !result.tools) {
100
+ throw new Error("No tools returned from server");
101
+ }
102
+ mcpLogger.debug(`[ToolDiscoveryService] Discovered ${result.tools.length} tools from ${serverId}`);
103
+ return result.tools;
104
+ }
105
+ /**
106
+ * Register discovered tools
107
+ */
108
+ async registerDiscoveredTools(serverId, tools) {
109
+ const registeredTools = [];
110
+ // Clear existing tools for this server
111
+ this.clearServerTools(serverId);
112
+ for (const tool of tools) {
113
+ try {
114
+ const toolInfo = await this.createToolInfo(serverId, tool);
115
+ const validation = this.validateTool(toolInfo);
116
+ if (!validation.isValid) {
117
+ mcpLogger.warn(`[ToolDiscoveryService] Skipping invalid tool ${tool.name} from ${serverId}:`, validation.errors);
118
+ continue;
119
+ }
120
+ // Apply validation metadata
121
+ if (validation.metadata) {
122
+ toolInfo.metadata = {
123
+ ...toolInfo.metadata,
124
+ ...validation.metadata,
125
+ };
126
+ }
127
+ // Register the tool
128
+ const toolKey = this.createToolKey(serverId, tool.name);
129
+ this.toolRegistry.set(toolKey, toolInfo);
130
+ if (!this.serverToolStorage.has(serverId)) {
131
+ this.serverToolStorage.set(serverId, []);
132
+ }
133
+ const serverTools = this.serverToolStorage.get(serverId);
134
+ // Add tool if not already present
135
+ if (!serverTools.find((t) => t.name === tool.name)) {
136
+ serverTools.push({
137
+ name: tool.name,
138
+ description: tool.description || "",
139
+ inputSchema: tool.inputSchema,
140
+ });
141
+ }
142
+ // Track server tools (legacy)
143
+ if (!this.serverTools.has(serverId)) {
144
+ this.serverTools.set(serverId, new Set());
145
+ }
146
+ this.serverTools.get(serverId).add(tool.name);
147
+ registeredTools.push(toolInfo);
148
+ // Emit tool registered event
149
+ this.emit("toolRegistered", {
150
+ serverId,
151
+ toolName: tool.name,
152
+ toolInfo,
153
+ timestamp: new Date(),
154
+ });
155
+ mcpLogger.debug(`[ToolDiscoveryService] Registered tool: ${tool.name} from ${serverId}`);
156
+ }
157
+ catch (error) {
158
+ mcpLogger.error(`[ToolDiscoveryService] Failed to register tool ${tool.name} from ${serverId}:`, error);
159
+ }
160
+ }
161
+ return registeredTools;
162
+ }
163
+ /**
164
+ * Create tool info from MCP tool definition
165
+ */
166
+ async createToolInfo(serverId, tool) {
167
+ return {
168
+ name: tool.name,
169
+ description: tool.description || "No description provided",
170
+ serverId,
171
+ inputSchema: tool.inputSchema,
172
+ isAvailable: true,
173
+ stats: {
174
+ totalCalls: 0,
175
+ successfulCalls: 0,
176
+ failedCalls: 0,
177
+ averageExecutionTime: 0,
178
+ lastExecutionTime: 0,
179
+ },
180
+ metadata: {
181
+ category: this.inferToolCategory(tool),
182
+ version: "1.0.0",
183
+ deprecated: false,
184
+ },
185
+ };
186
+ }
187
+ /**
188
+ * Infer tool category from tool definition
189
+ */
190
+ inferToolCategory(tool) {
191
+ const name = tool.name.toLowerCase();
192
+ const description = (tool.description || "").toLowerCase();
193
+ // Common patterns for categorization
194
+ if (name.includes("git") || description.includes("git")) {
195
+ return "version-control";
196
+ }
197
+ if (name.includes("file") ||
198
+ name.includes("read") ||
199
+ name.includes("write")) {
200
+ return "file-system";
201
+ }
202
+ if (name.includes("api") ||
203
+ name.includes("http") ||
204
+ name.includes("request")) {
205
+ return "api";
206
+ }
207
+ if (name.includes("data") ||
208
+ name.includes("query") ||
209
+ name.includes("search")) {
210
+ return "data";
211
+ }
212
+ if (name.includes("auth") ||
213
+ name.includes("login") ||
214
+ name.includes("token")) {
215
+ return "authentication";
216
+ }
217
+ if (name.includes("deploy") ||
218
+ name.includes("build") ||
219
+ name.includes("ci")) {
220
+ return "deployment";
221
+ }
222
+ return "general";
223
+ }
224
+ /**
225
+ * Validate a tool
226
+ */
227
+ validateTool(toolInfo) {
228
+ const errors = [];
229
+ const warnings = [];
230
+ // Basic validation
231
+ if (!toolInfo.name || toolInfo.name.trim().length === 0) {
232
+ errors.push("Tool name is required");
233
+ }
234
+ if (toolInfo.name && !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(toolInfo.name)) {
235
+ errors.push("Tool name must start with a letter and contain only letters, numbers, underscores, and hyphens");
236
+ }
237
+ if (!toolInfo.description || toolInfo.description.trim().length === 0) {
238
+ warnings.push("Tool description is empty");
239
+ }
240
+ if (!toolInfo.serverId) {
241
+ errors.push("Server ID is required");
242
+ }
243
+ // Schema validation
244
+ if (toolInfo.inputSchema) {
245
+ try {
246
+ JSON.stringify(toolInfo.inputSchema);
247
+ }
248
+ catch {
249
+ errors.push("Input schema is not valid JSON");
250
+ }
251
+ }
252
+ // Infer metadata
253
+ const metadata = {
254
+ category: typeof toolInfo.metadata?.category === "string"
255
+ ? toolInfo.metadata.category
256
+ : "general",
257
+ complexity: this.inferComplexity(toolInfo),
258
+ requiresAuth: this.inferAuthRequirement(toolInfo),
259
+ isDeprecated: typeof toolInfo.metadata?.deprecated === "boolean"
260
+ ? toolInfo.metadata.deprecated
261
+ : false,
262
+ };
263
+ return {
264
+ isValid: errors.length === 0,
265
+ errors,
266
+ warnings,
267
+ metadata,
268
+ };
269
+ }
270
+ /**
271
+ * Infer tool complexity
272
+ */
273
+ inferComplexity(toolInfo) {
274
+ const schema = toolInfo.inputSchema;
275
+ if (!schema || !schema.properties) {
276
+ return "simple";
277
+ }
278
+ const propertyCount = Object.keys(schema.properties).length;
279
+ if (propertyCount <= 2) {
280
+ return "simple";
281
+ }
282
+ else if (propertyCount <= 5) {
283
+ return "moderate";
284
+ }
285
+ else {
286
+ return "complex";
287
+ }
288
+ }
289
+ /**
290
+ * Infer if tool requires authentication
291
+ */
292
+ inferAuthRequirement(toolInfo) {
293
+ const name = toolInfo.name.toLowerCase();
294
+ const description = toolInfo.description.toLowerCase();
295
+ return (name.includes("auth") ||
296
+ name.includes("login") ||
297
+ name.includes("token") ||
298
+ description.includes("authentication") ||
299
+ description.includes("credentials") ||
300
+ description.includes("permission"));
301
+ }
302
+ /**
303
+ * Execute a tool
304
+ */
305
+ async executeTool(toolName, serverId, client, parameters, options = {}) {
306
+ const startTime = Date.now();
307
+ try {
308
+ const toolKey = this.createToolKey(serverId, toolName);
309
+ const toolInfo = this.toolRegistry.get(toolKey);
310
+ if (!toolInfo) {
311
+ throw new Error(`Tool '${toolName}' not found for server '${serverId}'`);
312
+ }
313
+ if (!toolInfo.isAvailable) {
314
+ throw new Error(`Tool '${toolName}' is not available`);
315
+ }
316
+ // Validate input parameters if requested
317
+ if (options.validateInput !== false) {
318
+ this.validateToolParameters(toolInfo, parameters);
319
+ }
320
+ mcpLogger.debug(`[ToolDiscoveryService] Executing tool: ${toolName} on ${serverId}`, {
321
+ parameters,
322
+ });
323
+ // Create circuit breaker for tool execution
324
+ const circuitBreaker = globalCircuitBreakerManager.getBreaker(`tool-execution-${serverId}-${toolName}`, {
325
+ failureThreshold: 3,
326
+ resetTimeout: 30000,
327
+ operationTimeout: options.timeout || 30000,
328
+ });
329
+ // Execute tool with circuit breaker protection
330
+ const result = await circuitBreaker.execute(async () => {
331
+ const timeout = options.timeout || 30000;
332
+ const executePromise = client.callTool({
333
+ name: toolName,
334
+ arguments: parameters,
335
+ });
336
+ const timeoutPromise = this.createTimeoutPromise(timeout, `Tool execution timeout: ${toolName}`);
337
+ return await Promise.race([executePromise, timeoutPromise]);
338
+ });
339
+ const duration = Date.now() - startTime;
340
+ // Update tool statistics
341
+ this.updateToolStats(toolKey, true, duration);
342
+ // Validate output if requested
343
+ if (options.validateOutput !== false && result) {
344
+ this.validateToolOutput(result);
345
+ }
346
+ mcpLogger.debug(`[ToolDiscoveryService] Tool execution completed: ${toolName}`, {
347
+ duration,
348
+ hasContent: !!result.content,
349
+ });
350
+ return {
351
+ success: true,
352
+ data: result,
353
+ duration,
354
+ metadata: {
355
+ toolName,
356
+ serverId,
357
+ timestamp: Date.now(),
358
+ },
359
+ };
360
+ }
361
+ catch (error) {
362
+ const duration = Date.now() - startTime;
363
+ const errorMessage = error instanceof Error ? error.message : String(error);
364
+ // Update tool statistics
365
+ const toolKey = this.createToolKey(serverId, toolName);
366
+ this.updateToolStats(toolKey, false, duration);
367
+ mcpLogger.error(`[ToolDiscoveryService] Tool execution failed: ${toolName}`, error);
368
+ return {
369
+ success: false,
370
+ error: errorMessage,
371
+ duration,
372
+ metadata: {
373
+ toolName,
374
+ serverId,
375
+ timestamp: Date.now(),
376
+ },
377
+ };
378
+ }
379
+ }
380
+ /**
381
+ * Validate tool parameters
382
+ */
383
+ validateToolParameters(toolInfo, parameters) {
384
+ if (!toolInfo.inputSchema) {
385
+ return; // No schema to validate against
386
+ }
387
+ // Basic validation - check required properties
388
+ const schema = toolInfo.inputSchema;
389
+ if (schema.required && Array.isArray(schema.required)) {
390
+ for (const requiredProp of schema.required) {
391
+ if (typeof requiredProp === "string" && !(requiredProp in parameters)) {
392
+ throw new Error(`Missing required parameter: ${requiredProp}`);
393
+ }
394
+ }
395
+ }
396
+ // Type validation for properties
397
+ if (schema.properties) {
398
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
399
+ if (propName in parameters) {
400
+ this.validateParameterType(propName, parameters[propName], propSchema);
401
+ }
402
+ }
403
+ }
404
+ }
405
+ /**
406
+ * Validate parameter type
407
+ */
408
+ validateParameterType(name, value, schema) {
409
+ if (!schema.type) {
410
+ return; // No type constraint
411
+ }
412
+ const expectedType = schema.type;
413
+ const actualType = typeof value;
414
+ switch (expectedType) {
415
+ case "string":
416
+ if (actualType !== "string") {
417
+ throw new Error(`Parameter '${name}' must be a string, got ${actualType}`);
418
+ }
419
+ break;
420
+ case "number":
421
+ if (actualType !== "number") {
422
+ throw new Error(`Parameter '${name}' must be a number, got ${actualType}`);
423
+ }
424
+ break;
425
+ case "boolean":
426
+ if (actualType !== "boolean") {
427
+ throw new Error(`Parameter '${name}' must be a boolean, got ${actualType}`);
428
+ }
429
+ break;
430
+ case "array":
431
+ if (!Array.isArray(value)) {
432
+ throw new Error(`Parameter '${name}' must be an array, got ${actualType}`);
433
+ }
434
+ break;
435
+ case "object":
436
+ if (actualType !== "object" || value === null || Array.isArray(value)) {
437
+ throw new Error(`Parameter '${name}' must be an object, got ${actualType}`);
438
+ }
439
+ break;
440
+ }
441
+ }
442
+ /**
443
+ * Validate tool output
444
+ */
445
+ validateToolOutput(result) {
446
+ // Basic output validation
447
+ if (!result) {
448
+ throw new Error("Tool returned no result");
449
+ }
450
+ // Check for error indicators
451
+ if (result.error) {
452
+ throw new Error(`Tool execution error: ${result.error}`);
453
+ }
454
+ if (result.isError === true) {
455
+ throw new Error("Tool execution failed");
456
+ }
457
+ }
458
+ /**
459
+ * Update tool statistics
460
+ */
461
+ updateToolStats(toolKey, success, duration) {
462
+ const toolInfo = this.toolRegistry.get(toolKey);
463
+ if (!toolInfo) {
464
+ return;
465
+ }
466
+ toolInfo.stats.totalCalls++;
467
+ toolInfo.lastCalled = new Date();
468
+ toolInfo.stats.lastExecutionTime = duration;
469
+ if (success) {
470
+ toolInfo.stats.successfulCalls++;
471
+ }
472
+ else {
473
+ toolInfo.stats.failedCalls++;
474
+ }
475
+ // Update average execution time
476
+ const totalTime = toolInfo.stats.averageExecutionTime * (toolInfo.stats.totalCalls - 1) +
477
+ duration;
478
+ toolInfo.stats.averageExecutionTime = totalTime / toolInfo.stats.totalCalls;
479
+ }
480
+ /**
481
+ * Get tool by name and server
482
+ */
483
+ getTool(toolName, serverId) {
484
+ const toolKey = this.createToolKey(serverId, toolName);
485
+ return this.toolRegistry.get(toolKey);
486
+ }
487
+ /**
488
+ * Get all tools for a server
489
+ */
490
+ getServerTools(serverId) {
491
+ const serverTools = this.serverToolStorage.get(serverId);
492
+ if (serverTools) {
493
+ return serverTools.map((tool) => ({
494
+ name: tool.name,
495
+ description: tool.description,
496
+ serverId,
497
+ inputSchema: tool.inputSchema,
498
+ isAvailable: true,
499
+ stats: {
500
+ totalCalls: 0,
501
+ successfulCalls: 0,
502
+ failedCalls: 0,
503
+ averageExecutionTime: 0,
504
+ lastExecutionTime: 0,
505
+ },
506
+ }));
507
+ }
508
+ // Fallback to legacy storage
509
+ const tools = [];
510
+ const serverToolNames = this.serverTools.get(serverId);
511
+ if (serverToolNames) {
512
+ for (const toolName of serverToolNames) {
513
+ const toolKey = this.createToolKey(serverId, toolName);
514
+ const toolInfo = this.toolRegistry.get(toolKey);
515
+ if (toolInfo) {
516
+ tools.push(toolInfo);
517
+ }
518
+ }
519
+ }
520
+ return tools;
521
+ }
522
+ /**
523
+ * Get all registered tools
524
+ */
525
+ getAllTools() {
526
+ const allTools = [];
527
+ // Add tools from server-based storage (preferred)
528
+ for (const [serverId, serverTools] of this.serverToolStorage.entries()) {
529
+ for (const tool of serverTools) {
530
+ allTools.push({
531
+ name: tool.name,
532
+ description: tool.description,
533
+ serverId,
534
+ inputSchema: tool.inputSchema,
535
+ isAvailable: true,
536
+ stats: {
537
+ totalCalls: 0,
538
+ successfulCalls: 0,
539
+ failedCalls: 0,
540
+ averageExecutionTime: 0,
541
+ lastExecutionTime: 0,
542
+ },
543
+ });
544
+ }
545
+ }
546
+ // Fallback to legacy storage for any tools not in server-based storage
547
+ const legacyTools = Array.from(this.toolRegistry.values()).filter((tool) => !allTools.some((t) => t.name === tool.name && t.serverId === tool.serverId));
548
+ return [...allTools, ...legacyTools];
549
+ }
550
+ /**
551
+ * Clear tools for a server
552
+ */
553
+ clearServerTools(serverId) {
554
+ const serverTools = this.serverToolStorage.get(serverId);
555
+ if (serverTools) {
556
+ // Emit unregistered events for server-based tools
557
+ for (const tool of serverTools) {
558
+ this.emit("toolUnregistered", {
559
+ serverId,
560
+ toolName: tool.name,
561
+ timestamp: new Date(),
562
+ });
563
+ }
564
+ this.serverToolStorage.delete(serverId);
565
+ }
566
+ // Legacy cleanup
567
+ const serverToolNames = this.serverTools.get(serverId);
568
+ if (serverToolNames) {
569
+ for (const toolName of serverToolNames) {
570
+ const toolKey = this.createToolKey(serverId, toolName);
571
+ this.toolRegistry.delete(toolKey);
572
+ // Emit tool unregistered event (only if not already emitted above)
573
+ if (!serverTools || !serverTools.find((t) => t.name === toolName)) {
574
+ this.emit("toolUnregistered", {
575
+ serverId,
576
+ toolName,
577
+ timestamp: new Date(),
578
+ });
579
+ }
580
+ }
581
+ this.serverTools.delete(serverId);
582
+ }
583
+ mcpLogger.debug(`[ToolDiscoveryService] Cleared tools for server: ${serverId}`);
584
+ }
585
+ /**
586
+ * Update tool availability
587
+ */
588
+ updateToolAvailability(toolName, serverId, isAvailable) {
589
+ const toolKey = this.createToolKey(serverId, toolName);
590
+ const toolInfo = this.toolRegistry.get(toolKey);
591
+ if (toolInfo) {
592
+ toolInfo.isAvailable = isAvailable;
593
+ mcpLogger.debug(`[ToolDiscoveryService] Updated availability for ${toolName}: ${isAvailable}`);
594
+ }
595
+ }
596
+ /**
597
+ * Create tool key for registry
598
+ */
599
+ createToolKey(serverId, toolName) {
600
+ return `${serverId}:${toolName}`;
601
+ }
602
+ /**
603
+ * Create timeout promise
604
+ */
605
+ createTimeoutPromise(timeout, message) {
606
+ return new Promise((_, reject) => {
607
+ setTimeout(() => {
608
+ reject(new Error(message));
609
+ }, timeout);
610
+ });
611
+ }
612
+ /**
613
+ * Get discovery statistics
614
+ */
615
+ getStatistics() {
616
+ const toolsByServer = {};
617
+ const toolsByCategory = {};
618
+ let availableTools = 0;
619
+ let unavailableTools = 0;
620
+ for (const toolInfo of this.toolRegistry.values()) {
621
+ // Count by server
622
+ toolsByServer[toolInfo.serverId] =
623
+ (toolsByServer[toolInfo.serverId] || 0) + 1;
624
+ // Count by category
625
+ const category = typeof toolInfo.metadata?.category === "string"
626
+ ? toolInfo.metadata.category
627
+ : "unknown";
628
+ toolsByCategory[category] = (toolsByCategory[category] || 0) + 1;
629
+ // Count availability
630
+ if (toolInfo.isAvailable) {
631
+ availableTools++;
632
+ }
633
+ else {
634
+ unavailableTools++;
635
+ }
636
+ }
637
+ return {
638
+ totalTools: this.toolRegistry.size,
639
+ availableTools,
640
+ unavailableTools,
641
+ totalServers: this.serverTools.size,
642
+ toolsByServer,
643
+ toolsByCategory,
644
+ };
645
+ }
646
+ }