@juspay/neurolink 1.6.0 → 1.9.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 (176) hide show
  1. package/CHANGELOG.md +193 -7
  2. package/README.md +100 -17
  3. package/dist/agent/direct-tools.d.ts +1203 -0
  4. package/dist/agent/direct-tools.js +387 -0
  5. package/dist/cli/commands/agent-generate.d.ts +2 -0
  6. package/dist/cli/commands/agent-generate.js +70 -0
  7. package/dist/cli/commands/config.d.ts +6 -6
  8. package/dist/cli/commands/config.js +326 -273
  9. package/dist/cli/commands/mcp.d.ts +2 -1
  10. package/dist/cli/commands/mcp.js +874 -146
  11. package/dist/cli/commands/ollama.d.ts +1 -1
  12. package/dist/cli/commands/ollama.js +153 -143
  13. package/dist/cli/index.js +589 -323
  14. package/dist/cli/utils/complete-setup.d.ts +19 -0
  15. package/dist/cli/utils/complete-setup.js +81 -0
  16. package/dist/cli/utils/env-manager.d.ts +44 -0
  17. package/dist/cli/utils/env-manager.js +226 -0
  18. package/dist/cli/utils/interactive-setup.d.ts +48 -0
  19. package/dist/cli/utils/interactive-setup.js +302 -0
  20. package/dist/core/dynamic-models.d.ts +208 -0
  21. package/dist/core/dynamic-models.js +250 -0
  22. package/dist/core/factory.d.ts +13 -6
  23. package/dist/core/factory.js +176 -61
  24. package/dist/core/types.d.ts +4 -2
  25. package/dist/core/types.js +4 -4
  26. package/dist/index.d.ts +16 -16
  27. package/dist/index.js +16 -16
  28. package/dist/lib/agent/direct-tools.d.ts +1203 -0
  29. package/dist/lib/agent/direct-tools.js +387 -0
  30. package/dist/lib/core/dynamic-models.d.ts +208 -0
  31. package/dist/lib/core/dynamic-models.js +250 -0
  32. package/dist/lib/core/factory.d.ts +13 -6
  33. package/dist/lib/core/factory.js +176 -61
  34. package/dist/lib/core/types.d.ts +4 -2
  35. package/dist/lib/core/types.js +4 -4
  36. package/dist/lib/index.d.ts +16 -16
  37. package/dist/lib/index.js +16 -16
  38. package/dist/lib/mcp/auto-discovery.d.ts +120 -0
  39. package/dist/lib/mcp/auto-discovery.js +793 -0
  40. package/dist/lib/mcp/client.d.ts +66 -0
  41. package/dist/lib/mcp/client.js +245 -0
  42. package/dist/lib/mcp/config.d.ts +31 -0
  43. package/dist/lib/mcp/config.js +74 -0
  44. package/dist/lib/mcp/context-manager.d.ts +4 -4
  45. package/dist/lib/mcp/context-manager.js +24 -18
  46. package/dist/lib/mcp/factory.d.ts +28 -11
  47. package/dist/lib/mcp/factory.js +36 -29
  48. package/dist/lib/mcp/function-calling.d.ts +51 -0
  49. package/dist/lib/mcp/function-calling.js +510 -0
  50. package/dist/lib/mcp/index.d.ts +190 -0
  51. package/dist/lib/mcp/index.js +156 -0
  52. package/dist/lib/mcp/initialize-tools.d.ts +28 -0
  53. package/dist/lib/mcp/initialize-tools.js +209 -0
  54. package/dist/lib/mcp/initialize.d.ts +17 -0
  55. package/dist/lib/mcp/initialize.js +51 -0
  56. package/dist/lib/mcp/logging.d.ts +71 -0
  57. package/dist/lib/mcp/logging.js +183 -0
  58. package/dist/lib/mcp/manager.d.ts +67 -0
  59. package/dist/lib/mcp/manager.js +176 -0
  60. package/dist/lib/mcp/neurolink-mcp-client.d.ts +96 -0
  61. package/dist/lib/mcp/neurolink-mcp-client.js +417 -0
  62. package/dist/lib/mcp/orchestrator.d.ts +3 -3
  63. package/dist/lib/mcp/orchestrator.js +46 -43
  64. package/dist/lib/mcp/registry.d.ts +2 -2
  65. package/dist/lib/mcp/registry.js +42 -33
  66. package/dist/lib/mcp/servers/ai-providers/ai-analysis-tools.d.ts +1 -1
  67. package/dist/lib/mcp/servers/ai-providers/ai-analysis-tools.js +204 -65
  68. package/dist/lib/mcp/servers/ai-providers/ai-core-server.js +142 -102
  69. package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.d.ts +6 -6
  70. package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.js +197 -142
  71. package/dist/lib/mcp/servers/utilities/utility-server.d.ts +8 -0
  72. package/dist/lib/mcp/servers/utilities/utility-server.js +326 -0
  73. package/dist/lib/mcp/tool-integration.d.ts +67 -0
  74. package/dist/lib/mcp/tool-integration.js +179 -0
  75. package/dist/lib/mcp/unified-registry.d.ts +269 -0
  76. package/dist/lib/mcp/unified-registry.js +1411 -0
  77. package/dist/lib/neurolink.d.ts +68 -6
  78. package/dist/lib/neurolink.js +304 -42
  79. package/dist/lib/providers/agent-enhanced-provider.d.ts +59 -0
  80. package/dist/lib/providers/agent-enhanced-provider.js +242 -0
  81. package/dist/lib/providers/amazonBedrock.d.ts +3 -3
  82. package/dist/lib/providers/amazonBedrock.js +54 -50
  83. package/dist/lib/providers/anthropic.d.ts +2 -2
  84. package/dist/lib/providers/anthropic.js +92 -84
  85. package/dist/lib/providers/azureOpenAI.d.ts +2 -2
  86. package/dist/lib/providers/azureOpenAI.js +97 -86
  87. package/dist/lib/providers/function-calling-provider.d.ts +70 -0
  88. package/dist/lib/providers/function-calling-provider.js +359 -0
  89. package/dist/lib/providers/googleAIStudio.d.ts +10 -5
  90. package/dist/lib/providers/googleAIStudio.js +60 -38
  91. package/dist/lib/providers/googleVertexAI.d.ts +3 -3
  92. package/dist/lib/providers/googleVertexAI.js +96 -86
  93. package/dist/lib/providers/huggingFace.d.ts +3 -3
  94. package/dist/lib/providers/huggingFace.js +70 -63
  95. package/dist/lib/providers/index.d.ts +11 -11
  96. package/dist/lib/providers/index.js +18 -18
  97. package/dist/lib/providers/mcp-provider.d.ts +62 -0
  98. package/dist/lib/providers/mcp-provider.js +183 -0
  99. package/dist/lib/providers/mistralAI.d.ts +3 -3
  100. package/dist/lib/providers/mistralAI.js +42 -36
  101. package/dist/lib/providers/ollama.d.ts +4 -4
  102. package/dist/lib/providers/ollama.js +113 -98
  103. package/dist/lib/providers/openAI.d.ts +7 -3
  104. package/dist/lib/providers/openAI.js +45 -33
  105. package/dist/lib/utils/logger.js +2 -2
  106. package/dist/lib/utils/providerUtils.js +53 -31
  107. package/dist/mcp/auto-discovery.d.ts +120 -0
  108. package/dist/mcp/auto-discovery.js +794 -0
  109. package/dist/mcp/client.d.ts +66 -0
  110. package/dist/mcp/client.js +245 -0
  111. package/dist/mcp/config.d.ts +31 -0
  112. package/dist/mcp/config.js +74 -0
  113. package/dist/mcp/context-manager.d.ts +4 -4
  114. package/dist/mcp/context-manager.js +24 -18
  115. package/dist/mcp/factory.d.ts +28 -11
  116. package/dist/mcp/factory.js +36 -29
  117. package/dist/mcp/function-calling.d.ts +51 -0
  118. package/dist/mcp/function-calling.js +510 -0
  119. package/dist/mcp/index.d.ts +190 -0
  120. package/dist/mcp/index.js +156 -0
  121. package/dist/mcp/initialize-tools.d.ts +28 -0
  122. package/dist/mcp/initialize-tools.js +210 -0
  123. package/dist/mcp/initialize.d.ts +17 -0
  124. package/dist/mcp/initialize.js +51 -0
  125. package/dist/mcp/logging.d.ts +71 -0
  126. package/dist/mcp/logging.js +183 -0
  127. package/dist/mcp/manager.d.ts +67 -0
  128. package/dist/mcp/manager.js +176 -0
  129. package/dist/mcp/neurolink-mcp-client.d.ts +96 -0
  130. package/dist/mcp/neurolink-mcp-client.js +417 -0
  131. package/dist/mcp/orchestrator.d.ts +3 -3
  132. package/dist/mcp/orchestrator.js +46 -43
  133. package/dist/mcp/registry.d.ts +2 -2
  134. package/dist/mcp/registry.js +42 -33
  135. package/dist/mcp/servers/ai-providers/ai-analysis-tools.d.ts +1 -1
  136. package/dist/mcp/servers/ai-providers/ai-analysis-tools.js +204 -65
  137. package/dist/mcp/servers/ai-providers/ai-core-server.js +142 -102
  138. package/dist/mcp/servers/ai-providers/ai-workflow-tools.d.ts +6 -6
  139. package/dist/mcp/servers/ai-providers/ai-workflow-tools.js +197 -142
  140. package/dist/mcp/servers/utilities/utility-server.d.ts +8 -0
  141. package/dist/mcp/servers/utilities/utility-server.js +326 -0
  142. package/dist/mcp/tool-integration.d.ts +67 -0
  143. package/dist/mcp/tool-integration.js +179 -0
  144. package/dist/mcp/unified-registry.d.ts +269 -0
  145. package/dist/mcp/unified-registry.js +1411 -0
  146. package/dist/neurolink.d.ts +68 -6
  147. package/dist/neurolink.js +304 -42
  148. package/dist/providers/agent-enhanced-provider.d.ts +59 -0
  149. package/dist/providers/agent-enhanced-provider.js +242 -0
  150. package/dist/providers/amazonBedrock.d.ts +3 -3
  151. package/dist/providers/amazonBedrock.js +54 -50
  152. package/dist/providers/anthropic.d.ts +2 -2
  153. package/dist/providers/anthropic.js +92 -84
  154. package/dist/providers/azureOpenAI.d.ts +2 -2
  155. package/dist/providers/azureOpenAI.js +97 -86
  156. package/dist/providers/function-calling-provider.d.ts +70 -0
  157. package/dist/providers/function-calling-provider.js +359 -0
  158. package/dist/providers/googleAIStudio.d.ts +10 -5
  159. package/dist/providers/googleAIStudio.js +60 -38
  160. package/dist/providers/googleVertexAI.d.ts +3 -3
  161. package/dist/providers/googleVertexAI.js +96 -86
  162. package/dist/providers/huggingFace.d.ts +3 -3
  163. package/dist/providers/huggingFace.js +70 -63
  164. package/dist/providers/index.d.ts +11 -11
  165. package/dist/providers/index.js +18 -18
  166. package/dist/providers/mcp-provider.d.ts +62 -0
  167. package/dist/providers/mcp-provider.js +183 -0
  168. package/dist/providers/mistralAI.d.ts +3 -3
  169. package/dist/providers/mistralAI.js +42 -36
  170. package/dist/providers/ollama.d.ts +4 -4
  171. package/dist/providers/ollama.js +113 -98
  172. package/dist/providers/openAI.d.ts +7 -3
  173. package/dist/providers/openAI.js +45 -33
  174. package/dist/utils/logger.js +2 -2
  175. package/dist/utils/providerUtils.js +53 -31
  176. package/package.json +175 -161
@@ -0,0 +1,1411 @@
1
+ /**
2
+ * NeuroLink Unified MCP Registry System
3
+ * Combines manual configuration, auto-discovery, and default tool registry
4
+ * Provides intelligent server selection with configurable priorities
5
+ */
6
+ import fs from "fs";
7
+ import path from "path";
8
+ import { z } from "zod";
9
+ import { discoverMCPServers, autoRegisterMCPServers, } from "./auto-discovery.js";
10
+ import { defaultToolRegistry, MCPToolRegistry, } from "./registry.js";
11
+ import { unifiedRegistryLogger } from "./logging.js";
12
+ /**
13
+ * Unified MCP Registry that combines all sources
14
+ */
15
+ export class UnifiedMCPRegistry {
16
+ configPath;
17
+ config;
18
+ manualServers = new Map();
19
+ autoServers = new Map();
20
+ defaultServers = new Map();
21
+ autoRegistryInstance;
22
+ lastAutoDiscovery = 0;
23
+ autoDiscoveryCacheMs = 30000; // 30 seconds
24
+ _isInitialized = false;
25
+ constructor(configPath) {
26
+ this.configPath =
27
+ configPath || path.join(process.cwd(), ".mcp-config.json");
28
+ this.config = this.loadConfig();
29
+ this.autoRegistryInstance = new MCPToolRegistry();
30
+ }
31
+ /**
32
+ * Check if the registry has been initialized
33
+ */
34
+ get isInitialized() {
35
+ return this._isInitialized;
36
+ }
37
+ /**
38
+ * Load MCP configuration with defaults
39
+ */
40
+ loadConfig() {
41
+ const defaultConfig = {
42
+ mcpServers: {},
43
+ autoDiscovery: {
44
+ enabled: true,
45
+ sources: ["claude", "vscode", "cursor", "windsurf"],
46
+ autoRegister: true,
47
+ excludeServers: [],
48
+ preferManualConfig: true,
49
+ },
50
+ defaultRegistry: {
51
+ enabled: true,
52
+ includeBuiltInTools: true,
53
+ },
54
+ };
55
+ if (!fs.existsSync(this.configPath)) {
56
+ return defaultConfig;
57
+ }
58
+ try {
59
+ const content = fs.readFileSync(this.configPath, "utf-8");
60
+ const userConfig = JSON.parse(content);
61
+ // Merge with defaults
62
+ return {
63
+ ...defaultConfig,
64
+ ...userConfig,
65
+ autoDiscovery: {
66
+ ...defaultConfig.autoDiscovery,
67
+ ...userConfig.autoDiscovery,
68
+ },
69
+ defaultRegistry: {
70
+ ...defaultConfig.defaultRegistry,
71
+ ...userConfig.defaultRegistry,
72
+ },
73
+ };
74
+ }
75
+ catch (error) {
76
+ unifiedRegistryLogger.warn(`Failed to load config from ${this.configPath}, using defaults:`, error);
77
+ return defaultConfig;
78
+ }
79
+ }
80
+ /**
81
+ * Save configuration to file
82
+ */
83
+ saveConfig() {
84
+ try {
85
+ fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
86
+ }
87
+ catch (error) {
88
+ unifiedRegistryLogger.error(`Failed to save config to ${this.configPath}:`, error);
89
+ }
90
+ }
91
+ /**
92
+ * Initialize the unified registry by loading all server sources
93
+ */
94
+ async initialize() {
95
+ if (this._isInitialized) {
96
+ return; // Already initialized
97
+ }
98
+ unifiedRegistryLogger.debug("Initializing unified registry...");
99
+ // 1. Load manual servers
100
+ await this.loadManualServers();
101
+ // 2. Auto-discover servers if enabled
102
+ if (this.config.autoDiscovery?.enabled) {
103
+ await this.loadAutoDiscoveredServers();
104
+ }
105
+ // 3. Load default registry tools if enabled
106
+ if (this.config.defaultRegistry?.enabled) {
107
+ await this.loadDefaultRegistryTools();
108
+ }
109
+ // 4. Lazy activation - mark all discovered servers as available but test on demand
110
+ await this.markServersAsAvailable();
111
+ this._isInitialized = true;
112
+ unifiedRegistryLogger.info(`Initialization complete: ${this.getTotalServerCount()} servers discovered (${this.getAvailableServerCount()} marked as available)`);
113
+ }
114
+ /**
115
+ * Mark all discovered servers as available for lazy activation
116
+ */
117
+ async markServersAsAvailable() {
118
+ unifiedRegistryLogger.debug("Marking discovered servers as available for lazy activation...");
119
+ // Mark manual servers as available
120
+ for (const [serverId, entry] of this.manualServers.entries()) {
121
+ if (entry.config) {
122
+ entry.status = "available"; // Will be tested on first use
123
+ unifiedRegistryLogger.debug(`Marked manual server '${serverId}' as available`);
124
+ }
125
+ }
126
+ // Mark auto-discovered servers as available
127
+ for (const [serverId, entry] of this.autoServers.entries()) {
128
+ if (entry.config) {
129
+ entry.status = "available"; // Will be tested on first use
130
+ unifiedRegistryLogger.debug(`Marked auto-discovered server '${serverId}' as available`);
131
+ }
132
+ }
133
+ const availableCount = this.getAvailableServerCount();
134
+ unifiedRegistryLogger.info(`Marked ${availableCount} servers as available for lazy activation`);
135
+ }
136
+ /**
137
+ * Load manual servers from configuration
138
+ */
139
+ async loadManualServers() {
140
+ this.manualServers.clear();
141
+ for (const [serverId, serverConfig] of Object.entries(this.config.mcpServers)) {
142
+ const entry = {
143
+ id: serverId,
144
+ config: serverConfig,
145
+ source: {
146
+ type: "manual",
147
+ priority: 10, // Highest priority
148
+ metadata: { configPath: this.configPath },
149
+ },
150
+ status: "unknown",
151
+ };
152
+ this.manualServers.set(serverId, entry);
153
+ }
154
+ unifiedRegistryLogger.debug(`Loaded ${this.manualServers.size} manual servers`);
155
+ }
156
+ /**
157
+ * Load auto-discovered servers
158
+ */
159
+ async loadAutoDiscoveredServers() {
160
+ // Check cache
161
+ if (Date.now() - this.lastAutoDiscovery < this.autoDiscoveryCacheMs) {
162
+ unifiedRegistryLogger.debug("Using cached auto-discovery results");
163
+ return;
164
+ }
165
+ try {
166
+ const discoveryOptions = {
167
+ searchGlobal: true,
168
+ searchWorkspace: true,
169
+ searchCommonPaths: true,
170
+ includeInactive: true,
171
+ preferredTools: this.config.autoDiscovery?.sources || [],
172
+ };
173
+ unifiedRegistryLogger.debug("Starting auto-discovery with options:", discoveryOptions);
174
+ const discoveryResult = await discoverMCPServers(discoveryOptions);
175
+ unifiedRegistryLogger.debug(`Auto-discovery completed: found ${discoveryResult.discovered.length} servers, ${discoveryResult.errors.length} errors`);
176
+ // Clear previous auto-discovered servers
177
+ this.autoServers.clear();
178
+ // Auto-register discovered servers
179
+ if (this.config.autoDiscovery?.autoRegister) {
180
+ unifiedRegistryLogger.debug("Auto-registering discovered servers...");
181
+ const registrationResult = await autoRegisterMCPServers(this.autoRegistryInstance, discoveryOptions);
182
+ unifiedRegistryLogger.debug(`Auto-registered ${registrationResult.registered.length} servers, ` +
183
+ `${registrationResult.failed.length} failed`);
184
+ }
185
+ // Add discovered servers to registry
186
+ let addedCount = 0;
187
+ let skippedCount = 0;
188
+ for (const discoveredServer of discoveryResult.discovered) {
189
+ // Skip if manually configured and preferManualConfig is true
190
+ if (this.config.autoDiscovery?.preferManualConfig &&
191
+ this.manualServers.has(discoveredServer.id)) {
192
+ unifiedRegistryLogger.debug(`Skipping auto-discovered '${discoveredServer.id}' - manual config takes precedence`);
193
+ skippedCount++;
194
+ continue;
195
+ }
196
+ // Skip if explicitly excluded
197
+ if (this.config.autoDiscovery?.excludeServers?.includes(discoveredServer.id)) {
198
+ unifiedRegistryLogger.debug(`Skipping excluded server '${discoveredServer.id}'`);
199
+ continue;
200
+ }
201
+ const entry = {
202
+ id: discoveredServer.id,
203
+ config: {
204
+ name: discoveredServer.id,
205
+ command: discoveredServer.command,
206
+ args: discoveredServer.args,
207
+ env: discoveredServer.env,
208
+ cwd: discoveredServer.cwd,
209
+ transport: "stdio",
210
+ },
211
+ source: {
212
+ type: "auto",
213
+ priority: 7, // Medium priority
214
+ metadata: {
215
+ discoverySource: discoveredServer.source,
216
+ configPath: discoveredServer.configPath,
217
+ },
218
+ },
219
+ status: "unknown",
220
+ };
221
+ this.autoServers.set(discoveredServer.id, entry);
222
+ addedCount++;
223
+ }
224
+ this.lastAutoDiscovery = Date.now();
225
+ unifiedRegistryLogger.debug(`Auto-discovery complete: added ${addedCount} servers, skipped ${skippedCount}, total auto servers: ${this.autoServers.size}`);
226
+ }
227
+ catch (error) {
228
+ unifiedRegistryLogger.error(`Auto-discovery failed: ${error}`);
229
+ }
230
+ }
231
+ /**
232
+ * Load default registry tools
233
+ */
234
+ async loadDefaultRegistryTools() {
235
+ if (!this.config.defaultRegistry?.includeBuiltInTools) {
236
+ return;
237
+ }
238
+ this.defaultServers.clear();
239
+ // Ensure built-in NeuroLink servers are initialized
240
+ try {
241
+ const { initializeNeuroLinkMCP, isNeuroLinkMCPInitialized } = await import("./initialize.js");
242
+ if (!isNeuroLinkMCPInitialized()) {
243
+ unifiedRegistryLogger.debug("Initializing built-in NeuroLink MCP servers...");
244
+ await initializeNeuroLinkMCP();
245
+ }
246
+ }
247
+ catch (error) {
248
+ unifiedRegistryLogger.warn("Failed to initialize built-in NeuroLink MCP servers:", error);
249
+ }
250
+ // Get tools from default registry
251
+ const tools = defaultToolRegistry.listTools();
252
+ const serverGroups = new Map();
253
+ // Group tools by server
254
+ for (const tool of tools) {
255
+ if (!serverGroups.has(tool.server)) {
256
+ serverGroups.set(tool.server, []);
257
+ }
258
+ serverGroups.get(tool.server).push(tool);
259
+ }
260
+ // Create server entries for each server
261
+ for (const [serverId, serverTools] of serverGroups) {
262
+ const entry = {
263
+ id: serverId,
264
+ source: {
265
+ type: "default",
266
+ priority: 5, // Lowest priority
267
+ metadata: { toolCount: serverTools.length },
268
+ },
269
+ status: "available",
270
+ };
271
+ this.defaultServers.set(serverId, entry);
272
+ }
273
+ unifiedRegistryLogger.debug(`Loaded ${this.defaultServers.size} default registry servers with ${tools.length} total tools`);
274
+ }
275
+ /**
276
+ * Execute a tool using unified registry with fallback
277
+ */
278
+ async executeTool(toolName, params, context, options = {}) {
279
+ const { preferredSource, fallbackEnabled = true, validateBeforeExecution = true, timeoutMs = 30000, } = options;
280
+ // Determine execution order based on preferences and tool type
281
+ const executionOrder = this.getExecutionOrder(preferredSource, toolName);
282
+ for (const sourceType of executionOrder) {
283
+ try {
284
+ const result = await this.executeFromSource(sourceType, toolName, params, context, { timeoutMs, validateBeforeExecution });
285
+ if (result.success || !fallbackEnabled) {
286
+ return result;
287
+ }
288
+ unifiedRegistryLogger.debug(`Execution failed from ${sourceType}, trying next source...`);
289
+ }
290
+ catch (error) {
291
+ unifiedRegistryLogger.debug(`Error executing from ${sourceType}: ${error}`);
292
+ if (!fallbackEnabled) {
293
+ throw error;
294
+ }
295
+ }
296
+ }
297
+ // All sources failed
298
+ throw new Error(`Tool '${toolName}' execution failed from all available sources`);
299
+ }
300
+ /**
301
+ * Execute tool from specific source
302
+ */
303
+ async executeFromSource(sourceType, toolName, params, context, options) {
304
+ switch (sourceType) {
305
+ case "manual":
306
+ return this.executeFromManualConfig(toolName, params, context, options);
307
+ case "auto":
308
+ return this.executeFromAutoRegistry(toolName, params, context, options);
309
+ case "default":
310
+ return this.executeFromDefaultRegistry(toolName, params, context, options);
311
+ default:
312
+ throw new Error(`Unknown source type: ${sourceType}`);
313
+ }
314
+ }
315
+ /**
316
+ * Execute tool from manual configuration
317
+ */
318
+ async executeFromManualConfig(toolName, params, context, options) {
319
+ // Find manual servers that might have this tool
320
+ for (const [serverId, serverEntry] of this.manualServers) {
321
+ if (!serverEntry.config) {
322
+ continue;
323
+ }
324
+ try {
325
+ unifiedRegistryLogger.debug(`Trying tool '${toolName}' on manual server '${serverId}'...`);
326
+ // First, try to lazy activate the server if not already activated
327
+ if (serverEntry.status !== "activated") {
328
+ unifiedRegistryLogger.debug(`Lazy activating manual server '${serverId}'...`);
329
+ // Add timeout to prevent hanging on server activation
330
+ const activated = await Promise.race([
331
+ this.lazyActivateServer(serverId, serverEntry),
332
+ new Promise((resolve) => {
333
+ const timer = setTimeout(() => resolve(false), 3000); // 3 second timeout
334
+ timer.unref();
335
+ }),
336
+ ]);
337
+ if (!activated) {
338
+ unifiedRegistryLogger.debug(`Failed to activate manual server '${serverId}' within timeout`);
339
+ continue;
340
+ }
341
+ }
342
+ // If server is activated and has a server instance, use it
343
+ if (serverEntry.status === "activated" && serverEntry.server) {
344
+ unifiedRegistryLogger.debug(`Using activated server instance for '${toolName}' on '${serverId}'`);
345
+ // Check if the tool exists on this server
346
+ const tool = serverEntry.server.tools[toolName];
347
+ if (tool) {
348
+ // Execute using the activated server instance
349
+ const result = await tool.execute(params, context);
350
+ unifiedRegistryLogger.debug(`Successfully executed '${toolName}' on activated server '${serverId}'`);
351
+ return {
352
+ success: true,
353
+ data: result,
354
+ metadata: {
355
+ toolName,
356
+ serverId,
357
+ sessionId: context.sessionId,
358
+ timestamp: Date.now(),
359
+ executionTime: 0,
360
+ },
361
+ };
362
+ }
363
+ else {
364
+ unifiedRegistryLogger.debug(`Tool '${toolName}' not found on activated server '${serverId}'`);
365
+ continue;
366
+ }
367
+ }
368
+ // Fallback: execute directly if activation failed but server config exists
369
+ unifiedRegistryLogger.debug(`Fallback: executing '${toolName}' directly on manual server '${serverId}'`);
370
+ const result = await this.executeMCPTool(serverEntry.config, toolName, params, options.timeoutMs);
371
+ unifiedRegistryLogger.debug(`Successfully executed '${toolName}' on manual server '${serverId}' (direct)`);
372
+ return {
373
+ success: true,
374
+ data: result,
375
+ metadata: {
376
+ toolName,
377
+ serverId,
378
+ sessionId: context.sessionId,
379
+ timestamp: Date.now(),
380
+ executionTime: 0,
381
+ },
382
+ };
383
+ }
384
+ catch (error) {
385
+ unifiedRegistryLogger.debug(`Failed to execute '${toolName}' on manual server '${serverId}': ${error instanceof Error ? error.message : String(error)}`);
386
+ continue;
387
+ }
388
+ }
389
+ throw new Error(`Tool '${toolName}' not found on any manual servers`);
390
+ }
391
+ /**
392
+ * Execute MCP tool using manual server configuration
393
+ */
394
+ async executeMCPTool(serverConfig, toolName, toolParams, timeoutMs = 10000) {
395
+ const { spawn } = await import("child_process");
396
+ if (serverConfig.transport === "stdio") {
397
+ const child = spawn(serverConfig.command, serverConfig.args || [], {
398
+ stdio: ["pipe", "pipe", "pipe"],
399
+ env: { ...process.env, ...serverConfig.env },
400
+ cwd: serverConfig.cwd,
401
+ });
402
+ return new Promise((resolve, reject) => {
403
+ let isResolved = false;
404
+ const cleanup = () => {
405
+ if (!isResolved) {
406
+ isResolved = true;
407
+ try {
408
+ child.kill("SIGTERM");
409
+ setTimeout(() => {
410
+ if (!child.killed) {
411
+ child.kill("SIGKILL");
412
+ }
413
+ }, 1000);
414
+ }
415
+ catch {
416
+ // Ignore cleanup errors
417
+ }
418
+ }
419
+ };
420
+ const timeout = setTimeout(() => {
421
+ cleanup();
422
+ reject(new Error(`Timeout executing MCP tool '${toolName}' after ${timeoutMs}ms`));
423
+ }, timeoutMs);
424
+ const resolveOnce = (value) => {
425
+ if (!isResolved) {
426
+ isResolved = true;
427
+ clearTimeout(timeout);
428
+ cleanup();
429
+ resolve(value);
430
+ }
431
+ };
432
+ const rejectOnce = (error) => {
433
+ if (!isResolved) {
434
+ isResolved = true;
435
+ clearTimeout(timeout);
436
+ cleanup();
437
+ reject(error);
438
+ }
439
+ };
440
+ let responseData = "";
441
+ let initialized = false;
442
+ let initTimeout;
443
+ child.stdout?.on("data", (data) => {
444
+ responseData += data.toString();
445
+ try {
446
+ const lines = responseData.split("\n");
447
+ for (const line of lines) {
448
+ if (line.trim() && line.includes('"result"')) {
449
+ const response = JSON.parse(line.trim());
450
+ if (response.id === 1 && response.result?.capabilities) {
451
+ // Initialize successful, clear init timeout and send notifications/initialized
452
+ if (initTimeout) {
453
+ clearTimeout(initTimeout);
454
+ }
455
+ initialized = true;
456
+ // Send notifications/initialized first
457
+ const initializedNotification = {
458
+ jsonrpc: "2.0",
459
+ method: "notifications/initialized",
460
+ };
461
+ child.stdin?.write(JSON.stringify(initializedNotification) + "\n");
462
+ // Then execute the tool
463
+ const toolCallRequest = {
464
+ jsonrpc: "2.0",
465
+ id: 2,
466
+ method: "tools/call",
467
+ params: {
468
+ name: toolName,
469
+ arguments: toolParams,
470
+ },
471
+ };
472
+ child.stdin?.write(JSON.stringify(toolCallRequest) + "\n");
473
+ }
474
+ else if (response.id === 2) {
475
+ if (response.result) {
476
+ // Extract the text content from MCP result format
477
+ if (response.result.content &&
478
+ Array.isArray(response.result.content)) {
479
+ const textContent = response.result.content.find((item) => item.type === "text");
480
+ if (textContent) {
481
+ try {
482
+ resolveOnce(JSON.parse(textContent.text));
483
+ }
484
+ catch {
485
+ resolveOnce(textContent.text);
486
+ }
487
+ }
488
+ else {
489
+ resolveOnce(response.result);
490
+ }
491
+ }
492
+ else {
493
+ resolveOnce(response.result);
494
+ }
495
+ }
496
+ else if (response.error) {
497
+ rejectOnce(new Error(`MCP Error: ${response.error.message || "Unknown error"}`));
498
+ }
499
+ else {
500
+ rejectOnce(new Error("Unknown MCP response format"));
501
+ }
502
+ return;
503
+ }
504
+ }
505
+ else if (line.trim() && line.includes('"error"')) {
506
+ const response = JSON.parse(line.trim());
507
+ if (response.error) {
508
+ rejectOnce(new Error(`MCP Error: ${response.error.message || "Unknown error"}`));
509
+ return;
510
+ }
511
+ }
512
+ }
513
+ }
514
+ catch (parseError) {
515
+ // Continue parsing - don't fail on parse errors
516
+ unifiedRegistryLogger.debug(`Parse error (continuing): ${parseError}`);
517
+ }
518
+ });
519
+ child.stderr?.on("data", (data) => {
520
+ const output = data.toString();
521
+ if (output.includes("running on stdio") ||
522
+ output.includes("Allowed directories")) {
523
+ unifiedRegistryLogger.debug(`MCP server status: ${output.trim()}`);
524
+ // Start initialization once server is ready (only if not already initialized)
525
+ if (!initialized) {
526
+ initialized = true;
527
+ if (initTimeout) {
528
+ clearTimeout(initTimeout);
529
+ }
530
+ setTimeout(() => {
531
+ const initRequest = {
532
+ jsonrpc: "2.0",
533
+ id: 1,
534
+ method: "initialize",
535
+ params: {
536
+ protocolVersion: "2024-11-05",
537
+ capabilities: {},
538
+ clientInfo: {
539
+ name: "neurolink-unified-registry",
540
+ version: "1.0.0",
541
+ },
542
+ },
543
+ };
544
+ try {
545
+ child.stdin?.write(JSON.stringify(initRequest) + "\n");
546
+ }
547
+ catch (writeError) {
548
+ rejectOnce(new Error(`Failed to write to MCP server: ${writeError}`));
549
+ }
550
+ }, 200);
551
+ }
552
+ }
553
+ else {
554
+ unifiedRegistryLogger.error(`MCP Server Error: ${output.trim()}`);
555
+ }
556
+ });
557
+ child.on("error", (error) => {
558
+ rejectOnce(new Error(`MCP server spawn error: ${error.message}`));
559
+ });
560
+ child.on("exit", (code, signal) => {
561
+ if (!isResolved) {
562
+ rejectOnce(new Error(`MCP server exited unexpectedly with code ${code}, signal ${signal}`));
563
+ }
564
+ });
565
+ child.on("close", (code) => {
566
+ if (!isResolved) {
567
+ rejectOnce(new Error(`MCP server closed unexpectedly with code ${code}`));
568
+ }
569
+ });
570
+ });
571
+ }
572
+ throw new Error("SSE transport not yet implemented for unified registry");
573
+ }
574
+ /**
575
+ * Execute tool from auto-registered servers
576
+ */
577
+ async executeFromAutoRegistry(toolName, params, context, options) {
578
+ return this.autoRegistryInstance.executeTool(toolName, params, context, {
579
+ validateInput: options.validateBeforeExecution,
580
+ validatePermissions: options.validateBeforeExecution,
581
+ timeoutMs: options.timeoutMs,
582
+ });
583
+ }
584
+ /**
585
+ * Execute tool from default registry
586
+ */
587
+ async executeFromDefaultRegistry(toolName, params, context, options) {
588
+ return defaultToolRegistry.executeTool(toolName, params, context, {
589
+ validateInput: options.validateBeforeExecution,
590
+ validatePermissions: options.validateBeforeExecution,
591
+ timeoutMs: options.timeoutMs,
592
+ });
593
+ }
594
+ /**
595
+ * Get execution order based on preferences and tool type
596
+ */
597
+ getExecutionOrder(preferredSource, toolName) {
598
+ // For AI-related tools, prioritize built-in NeuroLink tools first
599
+ const aiToolNames = [
600
+ "generate-text",
601
+ "select-provider",
602
+ "check-provider-status",
603
+ "analyze-ai-usage",
604
+ "benchmark-provider-performance",
605
+ "optimize-prompt-parameters",
606
+ "generate-test-cases",
607
+ "refactor-code",
608
+ "generate-documentation",
609
+ "debug-ai-output",
610
+ "get-current-time",
611
+ "format-text",
612
+ ];
613
+ // Built-in utility tools that should use default registry to avoid hanging
614
+ const builtinUtilityTools = [
615
+ "calculate-date-difference",
616
+ "format-number",
617
+ "get-current-time",
618
+ ];
619
+ const isAITool = toolName && aiToolNames.includes(toolName);
620
+ const isBuiltinTool = toolName && builtinUtilityTools.includes(toolName);
621
+ unifiedRegistryLogger.debug(`Tool: ${toolName}, isAITool: ${isAITool}, isBuiltinTool: ${isBuiltinTool}, preferredSource: ${preferredSource}`);
622
+ // For builtin utility tools, always prioritize default registry to avoid hanging manual servers
623
+ if (isBuiltinTool) {
624
+ const builtinOrder = [
625
+ "default",
626
+ "auto",
627
+ "manual",
628
+ ];
629
+ unifiedRegistryLogger.debug(`Using builtin tool execution order: [${builtinOrder.join(", ")}]`);
630
+ return builtinOrder;
631
+ }
632
+ // Default order prioritizes built-in AI tools for AI-related commands
633
+ const defaultOrder = isAITool
634
+ ? ["default", "manual", "auto"] // AI tools: try built-in first
635
+ : ["manual", "auto", "default"]; // Other tools: try manual config first
636
+ if (!preferredSource) {
637
+ unifiedRegistryLogger.debug(`Using default execution order: [${defaultOrder.join(", ")}]`);
638
+ return defaultOrder;
639
+ }
640
+ // Put preferred source first
641
+ const order = [preferredSource];
642
+ for (const source of defaultOrder) {
643
+ if (source !== preferredSource) {
644
+ order.push(source);
645
+ }
646
+ }
647
+ unifiedRegistryLogger.debug(`Using custom execution order: [${order.join(", ")}]`);
648
+ return order;
649
+ }
650
+ /**
651
+ * List all available tools from all sources
652
+ */
653
+ async listAllTools(criteria = {}) {
654
+ const tools = [];
655
+ // Add tools from each source based on criteria
656
+ if (!criteria.source || criteria.source === "manual") {
657
+ // For manual servers, just list placeholders without trying to activate
658
+ // Activation will happen on-demand during tool execution
659
+ for (const [serverId, entry] of this.manualServers) {
660
+ if (entry.status === "activated" && entry.server) {
661
+ // Get tools from already activated servers
662
+ const serverTools = Object.values(entry.server.tools);
663
+ tools.push(...serverTools.map((tool) => ({
664
+ name: tool.name,
665
+ server: serverId,
666
+ source: "manual",
667
+ description: tool.description,
668
+ category: "manual",
669
+ isImplemented: true,
670
+ })));
671
+ }
672
+ else {
673
+ // Add placeholder for non-activated servers (no activation attempt)
674
+ tools.push({
675
+ name: `${serverId}-placeholder`,
676
+ server: serverId,
677
+ source: "manual",
678
+ description: `Manual server: ${serverId} (not activated)`,
679
+ category: "manual",
680
+ isImplemented: false,
681
+ });
682
+ }
683
+ }
684
+ }
685
+ if (!criteria.source || criteria.source === "auto") {
686
+ // For auto-discovered servers, don't try to activate them all during listing
687
+ // Instead, just show placeholder tools and activate on demand
688
+ for (const [serverId, entry] of this.autoServers) {
689
+ if (entry.status === "activated" && entry.server) {
690
+ // Get tools from already activated server
691
+ const serverTools = Object.values(entry.server.tools);
692
+ tools.push(...serverTools.map((tool) => ({
693
+ name: tool.name,
694
+ server: serverId,
695
+ source: "auto",
696
+ description: tool.description,
697
+ category: "auto",
698
+ isImplemented: true,
699
+ })));
700
+ }
701
+ else if (entry.status === "available") {
702
+ // For available but not activated servers, add placeholder tools
703
+ const serverName = entry.config?.name || serverId;
704
+ tools.push({
705
+ name: `${serverName}-tools`,
706
+ server: serverId,
707
+ source: "auto",
708
+ description: `Tools from ${serverName} server (will be activated on first use)`,
709
+ category: "auto-placeholder",
710
+ isImplemented: false,
711
+ });
712
+ }
713
+ }
714
+ // Also include default auto registry tools
715
+ const autoTools = this.autoRegistryInstance.listTools(criteria);
716
+ tools.push(...autoTools.map((tool) => ({
717
+ ...tool,
718
+ source: "auto",
719
+ })));
720
+ }
721
+ if (!criteria.source || criteria.source === "default") {
722
+ const defaultTools = defaultToolRegistry.listTools(criteria);
723
+ tools.push(...defaultTools.map((tool) => ({
724
+ ...tool,
725
+ source: "default",
726
+ })));
727
+ }
728
+ return tools;
729
+ }
730
+ /**
731
+ * List all servers from all sources
732
+ */
733
+ listAllServers() {
734
+ const servers = [];
735
+ // Add servers from all sources
736
+ servers.push(...Array.from(this.manualServers.values()));
737
+ servers.push(...Array.from(this.autoServers.values()));
738
+ servers.push(...Array.from(this.defaultServers.values()));
739
+ // Sort by priority (higher priority first)
740
+ return servers.sort((a, b) => b.source.priority - a.source.priority);
741
+ }
742
+ /**
743
+ * Get total server count across all sources
744
+ */
745
+ getTotalServerCount() {
746
+ return (this.manualServers.size + this.autoServers.size + this.defaultServers.size);
747
+ }
748
+ /**
749
+ * Add a manual server configuration
750
+ */
751
+ addManualServer(serverId, config) {
752
+ this.config.mcpServers[serverId] = config;
753
+ this.saveConfig();
754
+ const entry = {
755
+ id: serverId,
756
+ config,
757
+ source: {
758
+ type: "manual",
759
+ priority: 10,
760
+ metadata: { configPath: this.configPath },
761
+ },
762
+ status: "unknown",
763
+ };
764
+ this.manualServers.set(serverId, entry);
765
+ unifiedRegistryLogger.info(`Added manual server: ${serverId}`);
766
+ }
767
+ /**
768
+ * Remove a manual server configuration
769
+ */
770
+ removeManualServer(serverId) {
771
+ if (this.config.mcpServers[serverId]) {
772
+ delete this.config.mcpServers[serverId];
773
+ this.saveConfig();
774
+ this.manualServers.delete(serverId);
775
+ unifiedRegistryLogger.info(`Removed manual server: ${serverId}`);
776
+ return true;
777
+ }
778
+ return false;
779
+ }
780
+ /**
781
+ * Update auto-discovery configuration
782
+ */
783
+ updateAutoDiscoveryConfig(config) {
784
+ this.config.autoDiscovery = {
785
+ ...this.config.autoDiscovery,
786
+ ...config,
787
+ };
788
+ this.saveConfig();
789
+ unifiedRegistryLogger.info("Updated auto-discovery configuration");
790
+ }
791
+ /**
792
+ * Force refresh of auto-discovered servers
793
+ */
794
+ async refreshAutoDiscovery() {
795
+ this.lastAutoDiscovery = 0; // Reset cache
796
+ await this.loadAutoDiscoveredServers();
797
+ }
798
+ /**
799
+ * Get current configuration
800
+ */
801
+ getConfig() {
802
+ return { ...this.config };
803
+ }
804
+ /**
805
+ * Get registry statistics
806
+ */
807
+ getStats() {
808
+ return {
809
+ manual: {
810
+ servers: this.manualServers.size,
811
+ tools: 0, // Would be calculated from actual manual servers
812
+ },
813
+ auto: {
814
+ servers: this.autoServers.size,
815
+ tools: this.autoRegistryInstance.listTools().length,
816
+ },
817
+ default: {
818
+ servers: this.defaultServers.size,
819
+ tools: defaultToolRegistry.listTools().length,
820
+ },
821
+ total: {
822
+ servers: this.getTotalServerCount(),
823
+ tools: this.autoRegistryInstance.listTools().length +
824
+ defaultToolRegistry.listTools().length +
825
+ this.getActivatedToolCount(),
826
+ },
827
+ };
828
+ }
829
+ /**
830
+ * Get auto-discovered servers
831
+ */
832
+ getAutoDiscoveredServers() {
833
+ return this.autoServers;
834
+ }
835
+ /**
836
+ * Get manual servers
837
+ */
838
+ getManualServers() {
839
+ return this.manualServers;
840
+ }
841
+ /**
842
+ * Get server summary with counts and status
843
+ */
844
+ getServerSummary() {
845
+ return {
846
+ manual: {
847
+ servers: this.manualServers.size,
848
+ tools: this.autoRegistryInstance.listTools().length,
849
+ },
850
+ auto: {
851
+ servers: this.autoServers.size,
852
+ tools: this.autoRegistryInstance.listTools().length,
853
+ },
854
+ default: {
855
+ servers: this.defaultServers.size,
856
+ tools: defaultToolRegistry.listTools().length,
857
+ },
858
+ total: {
859
+ servers: this.getTotalServerCount(),
860
+ tools: this.autoRegistryInstance.listTools().length +
861
+ defaultToolRegistry.listTools().length +
862
+ this.getActivatedToolCount(),
863
+ },
864
+ };
865
+ }
866
+ /**
867
+ * Activate discovered servers by testing connectivity and loading available tools
868
+ */
869
+ async activateDiscoveredServers() {
870
+ unifiedRegistryLogger.debug("Activating discovered servers...");
871
+ // const activationPromises: Promise<void>[] = [];
872
+ const maxConcurrentActivations = 3; // Reduced from 5 to 3 to avoid overwhelming system
873
+ // Collect all servers to activate (manual + auto-discovered)
874
+ const serversToActivate = [
875
+ ...Array.from(this.manualServers.entries()),
876
+ ...Array.from(this.autoServers.entries()),
877
+ ];
878
+ unifiedRegistryLogger.debug(`Found ${serversToActivate.length} servers to activate`);
879
+ // Process servers in batches
880
+ for (let i = 0; i < serversToActivate.length; i += maxConcurrentActivations) {
881
+ const batch = serversToActivate.slice(i, i + maxConcurrentActivations);
882
+ unifiedRegistryLogger.debug(`Activating batch ${Math.floor(i / maxConcurrentActivations) + 1}: servers ${i + 1}-${Math.min(i + maxConcurrentActivations, serversToActivate.length)}`);
883
+ const batchPromises = batch.map(async ([serverId, entry]) => {
884
+ try {
885
+ await this.activateServer(serverId, entry);
886
+ }
887
+ catch (error) {
888
+ unifiedRegistryLogger.warn(`Failed to activate server '${serverId}': ${error instanceof Error ? error.message : String(error)}`);
889
+ }
890
+ });
891
+ await Promise.allSettled(batchPromises);
892
+ }
893
+ const availableCount = this.getAvailableServerCount();
894
+ unifiedRegistryLogger.info(`Server activation complete: ${availableCount} servers activated`);
895
+ }
896
+ /**
897
+ * Activate a single server by testing connectivity and loading tools
898
+ */
899
+ async activateServer(serverId, entry) {
900
+ if (!entry.config) {
901
+ return; // Skip servers without config (default registry servers)
902
+ }
903
+ const activationTimeout = 5000; // Reduced from 10 seconds to 5 seconds
904
+ try {
905
+ // Test server connectivity with timeout
906
+ const isConnected = await Promise.race([
907
+ this.testServerConnectivity(entry.config),
908
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Activation timeout")), activationTimeout)),
909
+ ]);
910
+ if (isConnected) {
911
+ // Try to load tools from the server
912
+ const tools = await this.loadServerTools(entry.config);
913
+ if (tools && tools.length > 0) {
914
+ entry.status = "activated"; // Changed from 'available' to 'activated'
915
+ entry.server = await this.createServerInstance(serverId, entry.config, tools);
916
+ // Register tools with the auto registry instance
917
+ if (entry.server) {
918
+ await this.autoRegistryInstance.registerServer(entry.server);
919
+ }
920
+ unifiedRegistryLogger.debug(`Activated server '${serverId}' with ${tools.length} tools`);
921
+ }
922
+ else {
923
+ entry.status = "unavailable";
924
+ unifiedRegistryLogger.debug(`Server '${serverId}' connected but no tools available`);
925
+ }
926
+ }
927
+ else {
928
+ entry.status = "unavailable";
929
+ unifiedRegistryLogger.debug(`Server '${serverId}' connectivity test failed`);
930
+ }
931
+ }
932
+ catch (error) {
933
+ entry.status = "unavailable";
934
+ unifiedRegistryLogger.debug(`Server '${serverId}' activation failed: ${error instanceof Error ? error.message : String(error)}`);
935
+ }
936
+ }
937
+ /**
938
+ * Test server connectivity
939
+ */
940
+ async testServerConnectivity(serverConfig) {
941
+ try {
942
+ const { spawn } = await import("child_process");
943
+ if (serverConfig.transport === "stdio") {
944
+ return new Promise((resolve) => {
945
+ let resolved = false;
946
+ let child;
947
+ // Much shorter timeout for basic connectivity test
948
+ const timeout = setTimeout(() => {
949
+ if (!resolved) {
950
+ resolved = true;
951
+ if (child) {
952
+ this.cleanupChildProcess(child);
953
+ }
954
+ resolve(false);
955
+ }
956
+ }, 2000); // Reduced from 5000ms to 2000ms
957
+ // CRITICAL FIX: Unref the timeout to prevent event loop hanging
958
+ timeout.unref();
959
+ const safeResolve = (value) => {
960
+ if (!resolved) {
961
+ resolved = true;
962
+ clearTimeout(timeout);
963
+ if (child) {
964
+ this.cleanupChildProcess(child);
965
+ }
966
+ resolve(value);
967
+ }
968
+ };
969
+ child = spawn(serverConfig.command, serverConfig.args || [], {
970
+ stdio: ["pipe", "pipe", "pipe"],
971
+ env: { ...process.env, ...serverConfig.env },
972
+ cwd: serverConfig.cwd,
973
+ });
974
+ // CRITICAL FIX: Unref the child process to prevent event loop hanging
975
+ child.unref();
976
+ child.on("spawn", () => {
977
+ safeResolve(true);
978
+ });
979
+ child.on("error", (error) => {
980
+ // Log specific error types that are common
981
+ if (error.message.includes("ENOENT")) {
982
+ unifiedRegistryLogger.debug(`Server command not found: ${serverConfig.command}`);
983
+ }
984
+ safeResolve(false);
985
+ });
986
+ child.on("exit", () => {
987
+ safeResolve(true); // If it exits cleanly, consider it working
988
+ });
989
+ });
990
+ }
991
+ // TODO: Add SSE transport connectivity testing
992
+ return false;
993
+ }
994
+ catch (error) {
995
+ unifiedRegistryLogger.debug(`Connectivity test error for ${serverConfig.command}: ${error instanceof Error ? error.message : String(error)}`);
996
+ return false;
997
+ }
998
+ }
999
+ /**
1000
+ * Load tools from a server
1001
+ */
1002
+ async loadServerTools(serverConfig) {
1003
+ try {
1004
+ const { spawn } = await import("child_process");
1005
+ if (serverConfig.transport === "stdio") {
1006
+ return new Promise((resolve, reject) => {
1007
+ const child = spawn(serverConfig.command, serverConfig.args || [], {
1008
+ stdio: ["pipe", "pipe", "pipe"],
1009
+ env: { ...process.env, ...serverConfig.env },
1010
+ cwd: serverConfig.cwd,
1011
+ });
1012
+ // CRITICAL FIX: Unref the child process to prevent event loop hanging
1013
+ child.unref();
1014
+ const timeout = setTimeout(() => {
1015
+ this.cleanupChildProcess(child);
1016
+ reject(new Error("Tool loading timeout"));
1017
+ }, 8000);
1018
+ // CRITICAL FIX: Unref the timeout to prevent event loop hanging
1019
+ timeout.unref();
1020
+ let responseData = "";
1021
+ let initialized = false;
1022
+ let resolved = false;
1023
+ const safeResolve = (result) => {
1024
+ if (resolved) {
1025
+ return;
1026
+ }
1027
+ resolved = true;
1028
+ clearTimeout(timeout);
1029
+ this.cleanupChildProcess(child);
1030
+ resolve(result);
1031
+ };
1032
+ const safeReject = (error) => {
1033
+ if (resolved) {
1034
+ return;
1035
+ }
1036
+ resolved = true;
1037
+ clearTimeout(timeout);
1038
+ this.cleanupChildProcess(child);
1039
+ reject(error);
1040
+ };
1041
+ // Handle server status messages on stderr
1042
+ child.stderr?.on("data", (data) => {
1043
+ const output = data.toString();
1044
+ if (output.includes("running on stdio") ||
1045
+ output.includes("Allowed directories")) {
1046
+ unifiedRegistryLogger.debug(`MCP server status: ${output.trim()}`);
1047
+ // Start initialization once server is ready
1048
+ if (!initialized) {
1049
+ initialized = true;
1050
+ // Use unref timeout for non-critical initialization delay
1051
+ const initTimeout = setTimeout(() => {
1052
+ const initRequest = {
1053
+ jsonrpc: "2.0",
1054
+ id: 1,
1055
+ method: "initialize",
1056
+ params: {
1057
+ protocolVersion: "2024-11-05",
1058
+ capabilities: { tools: {} },
1059
+ clientInfo: { name: "neurolink", version: "1.0.0" },
1060
+ },
1061
+ };
1062
+ child.stdin?.write(JSON.stringify(initRequest) + "\n");
1063
+ }, 200);
1064
+ initTimeout.unref();
1065
+ }
1066
+ }
1067
+ });
1068
+ child.stdout?.on("data", (data) => {
1069
+ responseData += data.toString();
1070
+ const lines = responseData.split("\n");
1071
+ for (const line of lines) {
1072
+ if (line.trim() && line.includes('"result"')) {
1073
+ try {
1074
+ const response = JSON.parse(line.trim());
1075
+ if (response.id === 1 && response.result?.capabilities) {
1076
+ // Initialize successful, send notifications/initialized
1077
+ const initializedNotification = {
1078
+ jsonrpc: "2.0",
1079
+ method: "notifications/initialized",
1080
+ };
1081
+ child.stdin?.write(JSON.stringify(initializedNotification) + "\n");
1082
+ // Then list tools with unref timeout
1083
+ const toolsTimeout = setTimeout(() => {
1084
+ const listToolsRequest = {
1085
+ jsonrpc: "2.0",
1086
+ id: 2,
1087
+ method: "tools/list",
1088
+ };
1089
+ child.stdin?.write(JSON.stringify(listToolsRequest) + "\n");
1090
+ }, 100);
1091
+ toolsTimeout.unref();
1092
+ }
1093
+ else if (response.id === 2 && response.result?.tools) {
1094
+ safeResolve(response.result.tools || []);
1095
+ return;
1096
+ }
1097
+ }
1098
+ catch {
1099
+ // Ignore JSON parsing errors for this line
1100
+ }
1101
+ }
1102
+ }
1103
+ });
1104
+ child.on("error", (error) => {
1105
+ safeReject(new Error(`Server process error: ${error.message}`));
1106
+ });
1107
+ child.on("exit", (code) => {
1108
+ if (!resolved) {
1109
+ safeReject(new Error(`Server exited with code ${code}`));
1110
+ }
1111
+ });
1112
+ });
1113
+ }
1114
+ return [];
1115
+ }
1116
+ catch (error) {
1117
+ unifiedRegistryLogger.debug(`loadServerTools error: ${error instanceof Error ? error.message : String(error)}`);
1118
+ return [];
1119
+ }
1120
+ }
1121
+ /**
1122
+ * Properly cleanup child process and its streams to prevent hanging
1123
+ */
1124
+ cleanupChildProcess(child) {
1125
+ try {
1126
+ // First, close stdin explicitly to prevent PIPEWRAP handle retention
1127
+ if (child.stdin && !child.stdin.destroyed) {
1128
+ child.stdin.end(); // Close stdin explicitly first
1129
+ child.stdin.destroy();
1130
+ }
1131
+ // Destroy stdout/stderr streams to release handles
1132
+ if (child.stdout && !child.stdout.destroyed) {
1133
+ child.stdout.destroy();
1134
+ }
1135
+ if (child.stderr && !child.stderr.destroyed) {
1136
+ child.stderr.destroy();
1137
+ }
1138
+ // Kill the process if still running
1139
+ if (!child.killed) {
1140
+ child.kill("SIGTERM");
1141
+ // Force kill after 1 second if SIGTERM doesn't work
1142
+ const forceKillTimeout = setTimeout(() => {
1143
+ if (!child.killed) {
1144
+ child.kill("SIGKILL");
1145
+ }
1146
+ }, 1000);
1147
+ forceKillTimeout.unref();
1148
+ }
1149
+ }
1150
+ catch (error) {
1151
+ // Ignore cleanup errors
1152
+ unifiedRegistryLogger.debug(`Child process cleanup error: ${error instanceof Error ? error.message : String(error)}`);
1153
+ }
1154
+ }
1155
+ /**
1156
+ * Create a server instance from config and tools
1157
+ */
1158
+ async createServerInstance(serverId, config, tools) {
1159
+ try {
1160
+ const { createMCPServer } = await import("./factory.js");
1161
+ const server = createMCPServer({
1162
+ id: serverId,
1163
+ title: config.name || serverId,
1164
+ description: `Auto-discovered MCP server from ${config.command}`,
1165
+ category: "automation",
1166
+ version: "1.0.0",
1167
+ capabilities: ["tools"],
1168
+ });
1169
+ // Register discovered tools with direct MCP execution
1170
+ for (const tool of tools) {
1171
+ if (tool.name && tool.description) {
1172
+ server.registerTool({
1173
+ name: tool.name,
1174
+ description: tool.description,
1175
+ inputSchema: tool.inputSchema
1176
+ ? z.object(tool.inputSchema)
1177
+ : z.record(z.unknown()).optional(),
1178
+ execute: async (params, context) => {
1179
+ const startTime = Date.now();
1180
+ try {
1181
+ unifiedRegistryLogger.debug(`Executing external MCP tool ${tool.name} on server ${serverId}`);
1182
+ // Execute tool directly on external MCP server
1183
+ const result = await this.executeMCPTool(config, tool.name, params);
1184
+ const executionTime = Date.now() - startTime;
1185
+ return {
1186
+ success: true,
1187
+ data: result,
1188
+ metadata: {
1189
+ toolName: tool.name,
1190
+ serverId: serverId,
1191
+ sessionId: context.sessionId,
1192
+ timestamp: Date.now(),
1193
+ executionTime,
1194
+ isExternalMCP: true,
1195
+ },
1196
+ };
1197
+ }
1198
+ catch (error) {
1199
+ const executionTime = Date.now() - startTime;
1200
+ const errorMessage = error instanceof Error ? error.message : String(error);
1201
+ unifiedRegistryLogger.debug(`External MCP tool execution failed: ${errorMessage}`);
1202
+ return {
1203
+ success: false,
1204
+ error: `External MCP tool '${tool.name}' failed: ${errorMessage}`,
1205
+ metadata: {
1206
+ toolName: tool.name,
1207
+ serverId: serverId,
1208
+ sessionId: context.sessionId,
1209
+ timestamp: Date.now(),
1210
+ executionTime,
1211
+ isExternalMCP: true,
1212
+ error: errorMessage,
1213
+ },
1214
+ };
1215
+ }
1216
+ },
1217
+ });
1218
+ }
1219
+ }
1220
+ return server;
1221
+ }
1222
+ catch (error) {
1223
+ unifiedRegistryLogger.debug(`Failed to create server instance: ${error instanceof Error ? error.message : String(error)}`);
1224
+ return undefined;
1225
+ }
1226
+ }
1227
+ /**
1228
+ * Get count of activated servers
1229
+ */
1230
+ getAvailableServerCount() {
1231
+ let count = 0;
1232
+ for (const entry of this.manualServers.values()) {
1233
+ if (entry.status === "activated") {
1234
+ // Changed from 'available' to 'activated'
1235
+ count++;
1236
+ }
1237
+ }
1238
+ for (const entry of this.autoServers.values()) {
1239
+ if (entry.status === "activated") {
1240
+ // Changed from 'available' to 'activated'
1241
+ count++;
1242
+ }
1243
+ }
1244
+ for (const entry of this.defaultServers.values()) {
1245
+ if (entry.status === "activated") {
1246
+ // Changed from 'available' to 'activated'
1247
+ count++;
1248
+ }
1249
+ }
1250
+ return count;
1251
+ }
1252
+ /**
1253
+ * Get count of tools from activated servers
1254
+ */
1255
+ getActivatedToolCount() {
1256
+ let count = 0;
1257
+ for (const entry of this.manualServers.values()) {
1258
+ if (entry.status === "activated" && entry.server) {
1259
+ count += Object.keys(entry.server.tools).length;
1260
+ }
1261
+ }
1262
+ for (const entry of this.autoServers.values()) {
1263
+ if (entry.status === "activated" && entry.server) {
1264
+ count += Object.keys(entry.server.tools).length;
1265
+ }
1266
+ }
1267
+ return count;
1268
+ }
1269
+ /**
1270
+ * Activate a server on-demand when its tools are first requested
1271
+ */
1272
+ async lazyActivateServer(serverId, entry) {
1273
+ if (entry.status === "activated" || !entry.config) {
1274
+ return entry.status === "activated";
1275
+ }
1276
+ unifiedRegistryLogger.debug(`Lazy activating server '${serverId}'...`);
1277
+ try {
1278
+ // Handle known servers with predefined tools (bypass hanging tool discovery)
1279
+ if (serverId === "filesystem" &&
1280
+ entry.config.command === "mcp-server-filesystem") {
1281
+ unifiedRegistryLogger.debug(`Using predefined tools for filesystem server '${serverId}'`);
1282
+ // Quick connectivity test first
1283
+ const isConnected = await Promise.race([
1284
+ this.testServerConnectivity(entry.config),
1285
+ new Promise((resolve) => setTimeout(() => resolve(false), 1500)),
1286
+ ]);
1287
+ if (isConnected) {
1288
+ // Create predefined filesystem tools with correct tool names from the actual MCP server
1289
+ const predefinedTools = [
1290
+ {
1291
+ name: "list_directory",
1292
+ description: "Get a detailed listing of all files and directories in a specified path. Results clearly distinguish between files and directories with [FILE] and [DIR] prefixes.",
1293
+ inputSchema: {
1294
+ type: "object",
1295
+ properties: {
1296
+ path: {
1297
+ type: "string",
1298
+ description: "Path to list directory contents from",
1299
+ },
1300
+ },
1301
+ required: ["path"],
1302
+ },
1303
+ },
1304
+ {
1305
+ name: "read_file",
1306
+ description: "Read the complete contents of a file from the file system. Handles various text encodings and provides detailed error messages if the file cannot be read.",
1307
+ inputSchema: {
1308
+ type: "object",
1309
+ properties: {
1310
+ path: {
1311
+ type: "string",
1312
+ description: "Path to the file to read",
1313
+ },
1314
+ },
1315
+ required: ["path"],
1316
+ },
1317
+ },
1318
+ {
1319
+ name: "write_file",
1320
+ description: "Create a new file or completely overwrite an existing file with new content. Use with caution as it will overwrite existing files without warning.",
1321
+ inputSchema: {
1322
+ type: "object",
1323
+ properties: {
1324
+ path: {
1325
+ type: "string",
1326
+ description: "Path to the file to write",
1327
+ },
1328
+ content: {
1329
+ type: "string",
1330
+ description: "Content to write to the file",
1331
+ },
1332
+ },
1333
+ required: ["path", "content"],
1334
+ },
1335
+ },
1336
+ {
1337
+ name: "create_directory",
1338
+ description: "Create a new directory or ensure a directory exists. Can create multiple nested directories in one operation.",
1339
+ inputSchema: {
1340
+ type: "object",
1341
+ properties: {
1342
+ path: {
1343
+ type: "string",
1344
+ description: "Path to the directory to create",
1345
+ },
1346
+ },
1347
+ required: ["path"],
1348
+ },
1349
+ },
1350
+ {
1351
+ name: "search_files",
1352
+ description: "Recursively search for files and directories matching a pattern. Searches through all subdirectories from the starting path.",
1353
+ inputSchema: {
1354
+ type: "object",
1355
+ properties: {
1356
+ path: {
1357
+ type: "string",
1358
+ description: "Starting path to search from",
1359
+ },
1360
+ pattern: {
1361
+ type: "string",
1362
+ description: "Pattern to search for",
1363
+ },
1364
+ },
1365
+ required: ["path", "pattern"],
1366
+ },
1367
+ },
1368
+ ];
1369
+ entry.status = "activated";
1370
+ entry.server = await this.createServerInstance(serverId, entry.config, predefinedTools);
1371
+ if (entry.server) {
1372
+ await this.autoRegistryInstance.registerServer(entry.server);
1373
+ }
1374
+ unifiedRegistryLogger.debug(`Lazy activated filesystem server '${serverId}' with ${predefinedTools.length} predefined tools`);
1375
+ return true;
1376
+ }
1377
+ }
1378
+ // Original logic for other servers
1379
+ // Quick connectivity test with short timeout
1380
+ const isConnected = await Promise.race([
1381
+ this.testServerConnectivity(entry.config),
1382
+ new Promise((resolve) => setTimeout(() => resolve(false), 1500)),
1383
+ ]);
1384
+ if (isConnected) {
1385
+ // Try to load tools
1386
+ const tools = await this.loadServerTools(entry.config);
1387
+ if (tools && tools.length > 0) {
1388
+ entry.status = "activated";
1389
+ entry.server = await this.createServerInstance(serverId, entry.config, tools);
1390
+ if (entry.server) {
1391
+ await this.autoRegistryInstance.registerServer(entry.server);
1392
+ }
1393
+ unifiedRegistryLogger.debug(`Lazy activated server '${serverId}' with ${tools.length} tools`);
1394
+ return true;
1395
+ }
1396
+ }
1397
+ entry.status = "unavailable";
1398
+ unifiedRegistryLogger.debug(`Server '${serverId}' lazy activation failed`);
1399
+ return false;
1400
+ }
1401
+ catch (error) {
1402
+ entry.status = "unavailable";
1403
+ unifiedRegistryLogger.debug(`Server '${serverId}' lazy activation error: ${error instanceof Error ? error.message : String(error)}`);
1404
+ return false;
1405
+ }
1406
+ }
1407
+ }
1408
+ /**
1409
+ * Default unified registry instance
1410
+ */
1411
+ export const defaultUnifiedRegistry = new UnifiedMCPRegistry();