@juspay/neurolink 7.12.0 → 7.14.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 (65) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +90 -25
  3. package/dist/config/conversationMemoryConfig.js +2 -1
  4. package/dist/context/ContextManager.d.ts +28 -0
  5. package/dist/context/ContextManager.js +113 -0
  6. package/dist/context/config.d.ts +5 -0
  7. package/dist/context/config.js +42 -0
  8. package/dist/context/types.d.ts +20 -0
  9. package/dist/context/types.js +1 -0
  10. package/dist/context/utils.d.ts +7 -0
  11. package/dist/context/utils.js +8 -0
  12. package/dist/core/baseProvider.d.ts +16 -1
  13. package/dist/core/baseProvider.js +208 -9
  14. package/dist/core/conversationMemoryManager.js +3 -2
  15. package/dist/core/factory.js +13 -2
  16. package/dist/factories/providerFactory.js +5 -11
  17. package/dist/factories/providerRegistry.js +2 -2
  18. package/dist/lib/config/conversationMemoryConfig.js +2 -1
  19. package/dist/lib/context/ContextManager.d.ts +28 -0
  20. package/dist/lib/context/ContextManager.js +113 -0
  21. package/dist/lib/context/config.d.ts +5 -0
  22. package/dist/lib/context/config.js +42 -0
  23. package/dist/lib/context/types.d.ts +20 -0
  24. package/dist/lib/context/types.js +1 -0
  25. package/dist/lib/context/utils.d.ts +7 -0
  26. package/dist/lib/context/utils.js +8 -0
  27. package/dist/lib/core/baseProvider.d.ts +16 -1
  28. package/dist/lib/core/baseProvider.js +208 -9
  29. package/dist/lib/core/conversationMemoryManager.js +3 -2
  30. package/dist/lib/core/factory.js +13 -2
  31. package/dist/lib/factories/providerFactory.js +5 -11
  32. package/dist/lib/factories/providerRegistry.js +2 -2
  33. package/dist/lib/mcp/externalServerManager.d.ts +115 -0
  34. package/dist/lib/mcp/externalServerManager.js +677 -0
  35. package/dist/lib/mcp/mcpCircuitBreaker.d.ts +184 -0
  36. package/dist/lib/mcp/mcpCircuitBreaker.js +338 -0
  37. package/dist/lib/mcp/mcpClientFactory.d.ts +104 -0
  38. package/dist/lib/mcp/mcpClientFactory.js +416 -0
  39. package/dist/lib/mcp/toolDiscoveryService.d.ts +192 -0
  40. package/dist/lib/mcp/toolDiscoveryService.js +578 -0
  41. package/dist/lib/neurolink.d.ts +128 -16
  42. package/dist/lib/neurolink.js +555 -49
  43. package/dist/lib/providers/googleVertex.d.ts +1 -1
  44. package/dist/lib/providers/googleVertex.js +23 -7
  45. package/dist/lib/types/externalMcp.d.ts +282 -0
  46. package/dist/lib/types/externalMcp.js +6 -0
  47. package/dist/lib/types/generateTypes.d.ts +0 -1
  48. package/dist/lib/types/index.d.ts +1 -0
  49. package/dist/mcp/externalServerManager.d.ts +115 -0
  50. package/dist/mcp/externalServerManager.js +677 -0
  51. package/dist/mcp/mcpCircuitBreaker.d.ts +184 -0
  52. package/dist/mcp/mcpCircuitBreaker.js +338 -0
  53. package/dist/mcp/mcpClientFactory.d.ts +104 -0
  54. package/dist/mcp/mcpClientFactory.js +416 -0
  55. package/dist/mcp/toolDiscoveryService.d.ts +192 -0
  56. package/dist/mcp/toolDiscoveryService.js +578 -0
  57. package/dist/neurolink.d.ts +128 -16
  58. package/dist/neurolink.js +555 -49
  59. package/dist/providers/googleVertex.d.ts +1 -1
  60. package/dist/providers/googleVertex.js +23 -7
  61. package/dist/types/externalMcp.d.ts +282 -0
  62. package/dist/types/externalMcp.js +6 -0
  63. package/dist/types/generateTypes.d.ts +0 -1
  64. package/dist/types/index.d.ts +1 -0
  65. package/package.json +1 -1
@@ -0,0 +1,677 @@
1
+ /**
2
+ * External MCP Server Manager
3
+ * Handles lifecycle management of external MCP servers including:
4
+ * - Process spawning and management
5
+ * - Health monitoring and automatic restart
6
+ * - Connection management and cleanup
7
+ * - Tool discovery and registration
8
+ */
9
+ import { EventEmitter } from "events";
10
+ import { mcpLogger } from "../utils/logger.js";
11
+ import { MCPClientFactory } from "./mcpClientFactory.js";
12
+ import { ToolDiscoveryService } from "./toolDiscoveryService.js";
13
+ /**
14
+ * ExternalServerManager
15
+ * Core class for managing external MCP servers
16
+ */
17
+ export class ExternalServerManager extends EventEmitter {
18
+ servers = new Map();
19
+ config;
20
+ isShuttingDown = false;
21
+ toolDiscovery;
22
+ constructor(config = {}) {
23
+ super();
24
+ // Set defaults for configuration
25
+ this.config = {
26
+ maxServers: config.maxServers ?? 10,
27
+ defaultTimeout: config.defaultTimeout ?? 10000,
28
+ defaultHealthCheckInterval: config.defaultHealthCheckInterval ?? 30000,
29
+ enableAutoRestart: config.enableAutoRestart ?? true,
30
+ maxRestartAttempts: config.maxRestartAttempts ?? 3,
31
+ restartBackoffMultiplier: config.restartBackoffMultiplier ?? 2,
32
+ enablePerformanceMonitoring: config.enablePerformanceMonitoring ?? true,
33
+ logLevel: config.logLevel ?? "info",
34
+ };
35
+ // Initialize tool discovery service
36
+ this.toolDiscovery = new ToolDiscoveryService();
37
+ // Forward tool discovery events
38
+ this.toolDiscovery.on("toolRegistered", (event) => {
39
+ this.emit("toolDiscovered", event);
40
+ });
41
+ this.toolDiscovery.on("toolUnregistered", (event) => {
42
+ this.emit("toolRemoved", event);
43
+ });
44
+ // Handle process cleanup
45
+ process.on("SIGINT", () => this.shutdown());
46
+ process.on("SIGTERM", () => this.shutdown());
47
+ process.on("beforeExit", () => this.shutdown());
48
+ }
49
+ /**
50
+ * Validate external MCP server configuration
51
+ */
52
+ validateConfig(config) {
53
+ const errors = [];
54
+ const warnings = [];
55
+ const suggestions = [];
56
+ // Required fields validation
57
+ if (!config.id || typeof config.id !== "string") {
58
+ errors.push("Server ID is required and must be a string");
59
+ }
60
+ if (!config.command || typeof config.command !== "string") {
61
+ errors.push("Command is required and must be a string");
62
+ }
63
+ if (!Array.isArray(config.args)) {
64
+ errors.push("Args must be an array");
65
+ }
66
+ if (!["stdio", "sse", "websocket"].includes(config.transport)) {
67
+ errors.push("Transport must be one of: stdio, sse, websocket");
68
+ }
69
+ // URL validation for non-stdio transports
70
+ if ((config.transport === "sse" || config.transport === "websocket") &&
71
+ !config.url) {
72
+ errors.push(`URL is required for ${config.transport} transport`);
73
+ }
74
+ // Warnings for common issues
75
+ if (config.timeout && config.timeout < 5000) {
76
+ warnings.push("Timeout less than 5 seconds may cause connection issues");
77
+ }
78
+ if (config.retries && config.retries > 5) {
79
+ warnings.push("High retry count may slow down error recovery");
80
+ }
81
+ // Suggestions for optimization
82
+ if (!config.healthCheckInterval) {
83
+ suggestions.push("Consider setting a health check interval for better reliability");
84
+ }
85
+ if (config.autoRestart === undefined) {
86
+ suggestions.push("Consider enabling auto-restart for production use");
87
+ }
88
+ return {
89
+ isValid: errors.length === 0,
90
+ errors,
91
+ warnings,
92
+ suggestions,
93
+ };
94
+ }
95
+ /**
96
+ * Add a new external MCP server
97
+ */
98
+ async addServer(serverId, config) {
99
+ const startTime = Date.now();
100
+ try {
101
+ // Check server limit
102
+ if (this.servers.size >= this.config.maxServers) {
103
+ return {
104
+ success: false,
105
+ error: `Maximum number of servers (${this.config.maxServers}) reached`,
106
+ serverId,
107
+ duration: Date.now() - startTime,
108
+ };
109
+ }
110
+ // Validate configuration
111
+ const validation = this.validateConfig(config);
112
+ if (!validation.isValid) {
113
+ return {
114
+ success: false,
115
+ error: `Configuration validation failed: ${validation.errors.join(", ")}`,
116
+ serverId,
117
+ duration: Date.now() - startTime,
118
+ };
119
+ }
120
+ // Check for duplicate server ID
121
+ if (this.servers.has(serverId)) {
122
+ return {
123
+ success: false,
124
+ error: `Server with ID '${serverId}' already exists`,
125
+ serverId,
126
+ duration: Date.now() - startTime,
127
+ };
128
+ }
129
+ mcpLogger.info(`[ExternalServerManager] Adding server: ${serverId}`, {
130
+ command: config.command,
131
+ transport: config.transport,
132
+ });
133
+ // Create server instance
134
+ const instance = {
135
+ config: { ...config, id: serverId },
136
+ process: null,
137
+ client: null,
138
+ transport: null,
139
+ status: "initializing",
140
+ reconnectAttempts: 0,
141
+ maxReconnectAttempts: this.config.maxRestartAttempts,
142
+ tools: new Map(),
143
+ metrics: {
144
+ totalConnections: 0,
145
+ totalDisconnections: 0,
146
+ totalErrors: 0,
147
+ totalToolCalls: 0,
148
+ averageResponseTime: 0,
149
+ lastResponseTime: 0,
150
+ },
151
+ };
152
+ // Store the instance
153
+ this.servers.set(serverId, instance);
154
+ // Start the server
155
+ await this.startServer(serverId);
156
+ const finalInstance = this.servers.get(serverId);
157
+ return {
158
+ success: true,
159
+ data: finalInstance,
160
+ serverId,
161
+ duration: Date.now() - startTime,
162
+ metadata: {
163
+ timestamp: Date.now(),
164
+ operation: "addServer",
165
+ toolsDiscovered: finalInstance.tools.size,
166
+ },
167
+ };
168
+ }
169
+ catch (error) {
170
+ mcpLogger.error(`[ExternalServerManager] Failed to add server ${serverId}:`, error);
171
+ // Clean up if instance was created
172
+ this.servers.delete(serverId);
173
+ return {
174
+ success: false,
175
+ error: error instanceof Error ? error.message : String(error),
176
+ serverId,
177
+ duration: Date.now() - startTime,
178
+ };
179
+ }
180
+ }
181
+ /**
182
+ * Remove an external MCP server
183
+ */
184
+ async removeServer(serverId) {
185
+ const startTime = Date.now();
186
+ try {
187
+ const instance = this.servers.get(serverId);
188
+ if (!instance) {
189
+ return {
190
+ success: false,
191
+ error: `Server '${serverId}' not found`,
192
+ serverId,
193
+ duration: Date.now() - startTime,
194
+ };
195
+ }
196
+ mcpLogger.info(`[ExternalServerManager] Removing server: ${serverId}`);
197
+ // Stop the server
198
+ await this.stopServer(serverId);
199
+ // Remove from registry
200
+ this.servers.delete(serverId);
201
+ // Emit event
202
+ this.emit("disconnected", {
203
+ serverId,
204
+ reason: "Manually removed",
205
+ timestamp: new Date(),
206
+ });
207
+ return {
208
+ success: true,
209
+ serverId,
210
+ duration: Date.now() - startTime,
211
+ metadata: {
212
+ timestamp: Date.now(),
213
+ operation: "removeServer",
214
+ },
215
+ };
216
+ }
217
+ catch (error) {
218
+ mcpLogger.error(`[ExternalServerManager] Failed to remove server ${serverId}:`, error);
219
+ return {
220
+ success: false,
221
+ error: error instanceof Error ? error.message : String(error),
222
+ serverId,
223
+ duration: Date.now() - startTime,
224
+ };
225
+ }
226
+ }
227
+ /**
228
+ * Start an external MCP server
229
+ */
230
+ async startServer(serverId) {
231
+ const instance = this.servers.get(serverId);
232
+ if (!instance) {
233
+ throw new Error(`Server '${serverId}' not found`);
234
+ }
235
+ const config = instance.config;
236
+ try {
237
+ this.updateServerStatus(serverId, "connecting");
238
+ mcpLogger.debug(`[ExternalServerManager] Starting server: ${serverId}`, {
239
+ command: config.command,
240
+ args: config.args,
241
+ transport: config.transport,
242
+ });
243
+ // Create MCP client using the factory
244
+ const clientResult = await MCPClientFactory.createClient(config, config.timeout || this.config.defaultTimeout);
245
+ if (!clientResult.success ||
246
+ !clientResult.client ||
247
+ !clientResult.transport) {
248
+ throw new Error(`Failed to create MCP client: ${clientResult.error}`);
249
+ }
250
+ // Store client components
251
+ instance.client = clientResult.client;
252
+ instance.transport = clientResult.transport;
253
+ instance.process = clientResult.process || null;
254
+ instance.capabilities = clientResult.capabilities;
255
+ instance.startTime = new Date();
256
+ instance.lastHealthCheck = new Date();
257
+ instance.metrics.totalConnections++;
258
+ // Handle process events if there's a process
259
+ if (instance.process) {
260
+ instance.process.on("error", (error) => {
261
+ mcpLogger.error(`[ExternalServerManager] Process error for ${serverId}:`, error);
262
+ this.handleServerError(serverId, error);
263
+ });
264
+ instance.process.on("exit", (code, signal) => {
265
+ mcpLogger.warn(`[ExternalServerManager] Process exited for ${serverId}`, {
266
+ code,
267
+ signal,
268
+ });
269
+ this.handleServerDisconnection(serverId, `Process exited with code ${code}`);
270
+ });
271
+ // Log stderr for debugging
272
+ instance.process.stderr?.on("data", (data) => {
273
+ const message = data.toString().trim();
274
+ if (message) {
275
+ mcpLogger.debug(`[ExternalServerManager] ${serverId} stderr:`, message);
276
+ }
277
+ });
278
+ }
279
+ this.updateServerStatus(serverId, "connected");
280
+ // Discover tools from the server
281
+ await this.discoverServerTools(serverId);
282
+ // Start health monitoring
283
+ this.startHealthMonitoring(serverId);
284
+ // Emit connected event
285
+ this.emit("connected", {
286
+ serverId,
287
+ toolCount: instance.tools.size,
288
+ timestamp: new Date(),
289
+ });
290
+ mcpLogger.info(`[ExternalServerManager] Server started successfully: ${serverId}`);
291
+ }
292
+ catch (error) {
293
+ mcpLogger.error(`[ExternalServerManager] Failed to start server ${serverId}:`, error);
294
+ this.updateServerStatus(serverId, "failed");
295
+ instance.lastError =
296
+ error instanceof Error ? error.message : String(error);
297
+ throw error;
298
+ }
299
+ }
300
+ /**
301
+ * Stop an external MCP server
302
+ */
303
+ async stopServer(serverId) {
304
+ const instance = this.servers.get(serverId);
305
+ if (!instance) {
306
+ return;
307
+ }
308
+ try {
309
+ this.updateServerStatus(serverId, "stopping");
310
+ // Clear timers
311
+ if (instance.healthTimer) {
312
+ clearInterval(instance.healthTimer);
313
+ instance.healthTimer = undefined;
314
+ }
315
+ if (instance.restartTimer) {
316
+ clearTimeout(instance.restartTimer);
317
+ instance.restartTimer = undefined;
318
+ }
319
+ // Clear server tools from discovery service
320
+ this.toolDiscovery.clearServerTools(serverId);
321
+ // Close MCP client using factory cleanup
322
+ if (instance.client && instance.transport) {
323
+ try {
324
+ await MCPClientFactory.closeClient(instance.client, instance.transport, instance.process || undefined);
325
+ }
326
+ catch (error) {
327
+ mcpLogger.debug(`[ExternalServerManager] Error closing client for ${serverId}:`, error);
328
+ }
329
+ instance.client = null;
330
+ instance.transport = null;
331
+ instance.process = null;
332
+ }
333
+ this.updateServerStatus(serverId, "stopped");
334
+ mcpLogger.info(`[ExternalServerManager] Server stopped: ${serverId}`);
335
+ }
336
+ catch (error) {
337
+ mcpLogger.error(`[ExternalServerManager] Error stopping server ${serverId}:`, error);
338
+ this.updateServerStatus(serverId, "failed");
339
+ }
340
+ }
341
+ /**
342
+ * Update server status and emit events
343
+ */
344
+ updateServerStatus(serverId, newStatus) {
345
+ const instance = this.servers.get(serverId);
346
+ if (!instance) {
347
+ return;
348
+ }
349
+ const oldStatus = instance.status;
350
+ instance.status = newStatus;
351
+ // Emit status change event
352
+ this.emit("statusChanged", {
353
+ serverId,
354
+ oldStatus,
355
+ newStatus,
356
+ timestamp: new Date(),
357
+ });
358
+ mcpLogger.debug(`[ExternalServerManager] Status changed for ${serverId}: ${oldStatus} -> ${newStatus}`);
359
+ }
360
+ /**
361
+ * Handle server errors
362
+ */
363
+ handleServerError(serverId, error) {
364
+ const instance = this.servers.get(serverId);
365
+ if (!instance) {
366
+ return;
367
+ }
368
+ instance.lastError = error.message;
369
+ instance.metrics.totalErrors++;
370
+ mcpLogger.error(`[ExternalServerManager] Server error for ${serverId}:`, error);
371
+ // Emit failed event
372
+ this.emit("failed", {
373
+ serverId,
374
+ error: error.message,
375
+ timestamp: new Date(),
376
+ });
377
+ // Attempt restart if enabled
378
+ if (this.config.enableAutoRestart && !this.isShuttingDown) {
379
+ this.scheduleRestart(serverId);
380
+ }
381
+ else {
382
+ this.updateServerStatus(serverId, "failed");
383
+ }
384
+ }
385
+ /**
386
+ * Handle server disconnection
387
+ */
388
+ handleServerDisconnection(serverId, reason) {
389
+ const instance = this.servers.get(serverId);
390
+ if (!instance) {
391
+ return;
392
+ }
393
+ instance.metrics.totalDisconnections++;
394
+ mcpLogger.warn(`[ExternalServerManager] Server disconnected ${serverId}: ${reason}`);
395
+ // Emit disconnected event
396
+ this.emit("disconnected", {
397
+ serverId,
398
+ reason,
399
+ timestamp: new Date(),
400
+ });
401
+ // Attempt restart if enabled
402
+ if (this.config.enableAutoRestart && !this.isShuttingDown) {
403
+ this.scheduleRestart(serverId);
404
+ }
405
+ else {
406
+ this.updateServerStatus(serverId, "disconnected");
407
+ }
408
+ }
409
+ /**
410
+ * Schedule server restart with exponential backoff
411
+ */
412
+ scheduleRestart(serverId) {
413
+ const instance = this.servers.get(serverId);
414
+ if (!instance) {
415
+ return;
416
+ }
417
+ if (instance.reconnectAttempts >= instance.maxReconnectAttempts) {
418
+ mcpLogger.error(`[ExternalServerManager] Max restart attempts reached for ${serverId}`);
419
+ this.updateServerStatus(serverId, "failed");
420
+ return;
421
+ }
422
+ instance.reconnectAttempts++;
423
+ this.updateServerStatus(serverId, "restarting");
424
+ const delay = Math.min(1000 *
425
+ Math.pow(this.config.restartBackoffMultiplier, instance.reconnectAttempts - 1), 30000);
426
+ mcpLogger.info(`[ExternalServerManager] Scheduling restart for ${serverId} in ${delay}ms (attempt ${instance.reconnectAttempts})`);
427
+ instance.restartTimer = setTimeout(async () => {
428
+ try {
429
+ await this.stopServer(serverId);
430
+ await this.startServer(serverId);
431
+ // Reset restart attempts on successful restart
432
+ instance.reconnectAttempts = 0;
433
+ }
434
+ catch (error) {
435
+ mcpLogger.error(`[ExternalServerManager] Restart failed for ${serverId}:`, error);
436
+ this.scheduleRestart(serverId); // Try again
437
+ }
438
+ }, delay);
439
+ }
440
+ /**
441
+ * Start health monitoring for a server
442
+ */
443
+ startHealthMonitoring(serverId) {
444
+ const instance = this.servers.get(serverId);
445
+ if (!instance || !this.config.enablePerformanceMonitoring) {
446
+ return;
447
+ }
448
+ const interval = instance.config.healthCheckInterval ??
449
+ this.config.defaultHealthCheckInterval;
450
+ instance.healthTimer = setInterval(async () => {
451
+ await this.performHealthCheck(serverId);
452
+ }, interval);
453
+ }
454
+ /**
455
+ * Perform health check on a server
456
+ */
457
+ async performHealthCheck(serverId) {
458
+ const instance = this.servers.get(serverId);
459
+ if (!instance || instance.status !== "connected") {
460
+ return;
461
+ }
462
+ const startTime = Date.now();
463
+ try {
464
+ // For now, simple process check
465
+ let isHealthy = true;
466
+ const issues = [];
467
+ if (instance.process && instance.process.killed) {
468
+ isHealthy = false;
469
+ issues.push("Process is killed");
470
+ }
471
+ const responseTime = Date.now() - startTime;
472
+ instance.lastHealthCheck = new Date();
473
+ const health = {
474
+ serverId,
475
+ isHealthy,
476
+ status: instance.status,
477
+ checkedAt: new Date(),
478
+ responseTime,
479
+ toolCount: instance.tools.size,
480
+ issues,
481
+ performance: {
482
+ uptime: instance.startTime
483
+ ? Date.now() - instance.startTime.getTime()
484
+ : 0,
485
+ averageResponseTime: instance.metrics.averageResponseTime,
486
+ },
487
+ };
488
+ // Emit health check event
489
+ this.emit("healthCheck", {
490
+ serverId,
491
+ health,
492
+ timestamp: new Date(),
493
+ });
494
+ if (!isHealthy) {
495
+ mcpLogger.warn(`[ExternalServerManager] Health check failed for ${serverId}:`, issues);
496
+ this.handleServerError(serverId, new Error(`Health check failed: ${issues.join(", ")}`));
497
+ }
498
+ }
499
+ catch (error) {
500
+ mcpLogger.error(`[ExternalServerManager] Health check error for ${serverId}:`, error);
501
+ this.handleServerError(serverId, error instanceof Error ? error : new Error(String(error)));
502
+ }
503
+ }
504
+ /**
505
+ * Get server instance
506
+ */
507
+ getServer(serverId) {
508
+ return this.servers.get(serverId);
509
+ }
510
+ /**
511
+ * Get all servers
512
+ */
513
+ getAllServers() {
514
+ return new Map(this.servers);
515
+ }
516
+ /**
517
+ * Get server statuses
518
+ */
519
+ getServerStatuses() {
520
+ const statuses = [];
521
+ for (const [serverId, instance] of Array.from(this.servers.entries())) {
522
+ const uptime = instance.startTime
523
+ ? Date.now() - instance.startTime.getTime()
524
+ : 0;
525
+ statuses.push({
526
+ serverId,
527
+ isHealthy: instance.status === "connected",
528
+ status: instance.status,
529
+ checkedAt: instance.lastHealthCheck || new Date(),
530
+ toolCount: instance.tools.size,
531
+ issues: instance.lastError ? [instance.lastError] : [],
532
+ performance: {
533
+ uptime,
534
+ averageResponseTime: instance.metrics.averageResponseTime,
535
+ },
536
+ });
537
+ }
538
+ return statuses;
539
+ }
540
+ /**
541
+ * Shutdown all servers
542
+ */
543
+ async shutdown() {
544
+ if (this.isShuttingDown) {
545
+ return;
546
+ }
547
+ this.isShuttingDown = true;
548
+ mcpLogger.info("[ExternalServerManager] Shutting down all servers...");
549
+ const shutdownPromises = Array.from(this.servers.keys()).map((serverId) => this.stopServer(serverId).catch((error) => {
550
+ mcpLogger.error(`[ExternalServerManager] Error shutting down ${serverId}:`, error);
551
+ }));
552
+ await Promise.all(shutdownPromises);
553
+ this.servers.clear();
554
+ mcpLogger.info("[ExternalServerManager] All servers shut down");
555
+ }
556
+ /**
557
+ * Get manager statistics
558
+ */
559
+ getStatistics() {
560
+ let connectedServers = 0;
561
+ let failedServers = 0;
562
+ let totalTools = 0;
563
+ let totalConnections = 0;
564
+ let totalErrors = 0;
565
+ for (const instance of Array.from(this.servers.values())) {
566
+ if (instance.status === "connected") {
567
+ connectedServers++;
568
+ }
569
+ else if (instance.status === "failed") {
570
+ failedServers++;
571
+ }
572
+ totalTools += instance.tools.size;
573
+ totalConnections += instance.metrics.totalConnections;
574
+ totalErrors += instance.metrics.totalErrors;
575
+ }
576
+ return {
577
+ totalServers: this.servers.size,
578
+ connectedServers,
579
+ failedServers,
580
+ totalTools,
581
+ totalConnections,
582
+ totalErrors,
583
+ };
584
+ }
585
+ /**
586
+ * Discover tools from a server
587
+ */
588
+ async discoverServerTools(serverId) {
589
+ const instance = this.servers.get(serverId);
590
+ if (!instance || !instance.client) {
591
+ throw new Error(`Server '${serverId}' not found or not connected`);
592
+ }
593
+ try {
594
+ mcpLogger.debug(`[ExternalServerManager] Discovering tools for server: ${serverId}`);
595
+ const discoveryResult = await this.toolDiscovery.discoverTools(serverId, instance.client, this.config.defaultTimeout);
596
+ if (discoveryResult.success) {
597
+ // Update instance tools
598
+ instance.tools.clear();
599
+ for (const tool of discoveryResult.tools) {
600
+ instance.tools.set(tool.name, tool);
601
+ }
602
+ mcpLogger.info(`[ExternalServerManager] Discovered ${discoveryResult.toolCount} tools for ${serverId}`);
603
+ }
604
+ else {
605
+ mcpLogger.warn(`[ExternalServerManager] Tool discovery failed for ${serverId}: ${discoveryResult.error}`);
606
+ }
607
+ }
608
+ catch (error) {
609
+ mcpLogger.error(`[ExternalServerManager] Tool discovery error for ${serverId}:`, error);
610
+ }
611
+ }
612
+ /**
613
+ * Execute a tool on a specific server
614
+ */
615
+ async executeTool(serverId, toolName, parameters, options) {
616
+ const instance = this.servers.get(serverId);
617
+ if (!instance) {
618
+ throw new Error(`Server '${serverId}' not found`);
619
+ }
620
+ if (!instance.client) {
621
+ throw new Error(`Server '${serverId}' is not connected`);
622
+ }
623
+ if (instance.status !== "connected") {
624
+ throw new Error(`Server '${serverId}' is not in connected state: ${instance.status}`);
625
+ }
626
+ const startTime = Date.now();
627
+ try {
628
+ // Execute tool through discovery service
629
+ const result = await this.toolDiscovery.executeTool(toolName, serverId, instance.client, parameters, {
630
+ timeout: options?.timeout || this.config.defaultTimeout,
631
+ });
632
+ const duration = Date.now() - startTime;
633
+ // Update metrics
634
+ instance.metrics.totalToolCalls++;
635
+ instance.metrics.lastResponseTime = duration;
636
+ // Update average response time
637
+ const totalTime = instance.metrics.averageResponseTime *
638
+ (instance.metrics.totalToolCalls - 1) +
639
+ duration;
640
+ instance.metrics.averageResponseTime =
641
+ totalTime / instance.metrics.totalToolCalls;
642
+ if (result.success) {
643
+ mcpLogger.debug(`[ExternalServerManager] Tool executed successfully: ${toolName} on ${serverId}`, {
644
+ duration,
645
+ });
646
+ return result.data;
647
+ }
648
+ else {
649
+ throw new Error(result.error || "Tool execution failed");
650
+ }
651
+ }
652
+ catch (error) {
653
+ const duration = Date.now() - startTime;
654
+ instance.metrics.totalErrors++;
655
+ mcpLogger.error(`[ExternalServerManager] Tool execution failed: ${toolName} on ${serverId}`, error);
656
+ throw error;
657
+ }
658
+ }
659
+ /**
660
+ * Get all tools from all servers
661
+ */
662
+ getAllTools() {
663
+ return this.toolDiscovery.getAllTools();
664
+ }
665
+ /**
666
+ * Get tools for a specific server
667
+ */
668
+ getServerTools(serverId) {
669
+ return this.toolDiscovery.getServerTools(serverId);
670
+ }
671
+ /**
672
+ * Get tool discovery service
673
+ */
674
+ getToolDiscovery() {
675
+ return this.toolDiscovery;
676
+ }
677
+ }