@juspay/neurolink 4.1.1 → 4.2.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 (74) hide show
  1. package/CHANGELOG.md +8 -2
  2. package/README.md +1 -12
  3. package/dist/cli/commands/mcp.d.ts +11 -0
  4. package/dist/cli/commands/mcp.js +332 -223
  5. package/dist/cli/index.js +69 -8
  6. package/dist/core/factory.js +2 -2
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/lib/core/factory.js +2 -2
  10. package/dist/lib/index.d.ts +1 -1
  11. package/dist/lib/index.js +1 -1
  12. package/dist/lib/mcp/context-manager.d.ts +6 -0
  13. package/dist/lib/mcp/context-manager.js +8 -0
  14. package/dist/lib/mcp/contracts/mcpContract.d.ts +1 -0
  15. package/dist/lib/mcp/external-client.js +6 -2
  16. package/dist/lib/mcp/initialize.d.ts +2 -1
  17. package/dist/lib/mcp/initialize.js +8 -7
  18. package/dist/lib/mcp/orchestrator.js +9 -0
  19. package/dist/lib/mcp/registry.d.ts +1 -1
  20. package/dist/lib/mcp/servers/ai-providers/ai-analysis-tools.js +1 -1
  21. package/dist/lib/mcp/servers/ai-providers/ai-core-server.js +3 -3
  22. package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
  23. package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.js +1 -1
  24. package/dist/lib/mcp/session-manager.js +1 -1
  25. package/dist/lib/mcp/session-persistence.js +1 -1
  26. package/dist/lib/mcp/tool-registry.d.ts +31 -11
  27. package/dist/lib/mcp/tool-registry.js +226 -38
  28. package/dist/lib/mcp/unified-mcp.d.ts +12 -2
  29. package/dist/lib/mcp/unified-registry.d.ts +21 -7
  30. package/dist/lib/mcp/unified-registry.js +179 -17
  31. package/dist/lib/neurolink.js +17 -25
  32. package/dist/lib/providers/googleVertexAI.js +19 -1
  33. package/dist/lib/providers/openAI.js +18 -1
  34. package/dist/lib/utils/provider-setup-messages.d.ts +8 -0
  35. package/dist/lib/utils/provider-setup-messages.js +120 -0
  36. package/dist/lib/utils/provider-validation.d.ts +35 -0
  37. package/dist/lib/utils/provider-validation.js +625 -0
  38. package/dist/lib/utils/providerUtils-fixed.js +20 -1
  39. package/dist/lib/utils/providerUtils.d.ts +2 -2
  40. package/dist/lib/utils/providerUtils.js +38 -7
  41. package/dist/lib/utils/timeout-manager.d.ts +75 -0
  42. package/dist/lib/utils/timeout-manager.js +244 -0
  43. package/dist/mcp/context-manager.d.ts +6 -0
  44. package/dist/mcp/context-manager.js +8 -0
  45. package/dist/mcp/contracts/mcpContract.d.ts +1 -0
  46. package/dist/mcp/external-client.js +6 -2
  47. package/dist/mcp/initialize.d.ts +2 -1
  48. package/dist/mcp/initialize.js +8 -7
  49. package/dist/mcp/orchestrator.js +9 -0
  50. package/dist/mcp/registry.d.ts +1 -1
  51. package/dist/mcp/servers/ai-providers/ai-analysis-tools.js +1 -1
  52. package/dist/mcp/servers/ai-providers/ai-core-server.js +3 -3
  53. package/dist/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
  54. package/dist/mcp/servers/ai-providers/ai-workflow-tools.js +1 -1
  55. package/dist/mcp/session-manager.js +1 -1
  56. package/dist/mcp/session-persistence.js +1 -1
  57. package/dist/mcp/tool-registry.d.ts +31 -11
  58. package/dist/mcp/tool-registry.js +226 -38
  59. package/dist/mcp/unified-mcp.d.ts +12 -2
  60. package/dist/mcp/unified-registry.d.ts +21 -7
  61. package/dist/mcp/unified-registry.js +179 -17
  62. package/dist/neurolink.js +17 -25
  63. package/dist/providers/googleVertexAI.js +19 -1
  64. package/dist/providers/openAI.js +18 -1
  65. package/dist/utils/provider-setup-messages.d.ts +8 -0
  66. package/dist/utils/provider-setup-messages.js +120 -0
  67. package/dist/utils/provider-validation.d.ts +35 -0
  68. package/dist/utils/provider-validation.js +625 -0
  69. package/dist/utils/providerUtils-fixed.js +20 -1
  70. package/dist/utils/providerUtils.d.ts +2 -2
  71. package/dist/utils/providerUtils.js +38 -7
  72. package/dist/utils/timeout-manager.d.ts +75 -0
  73. package/dist/utils/timeout-manager.js +244 -0
  74. package/package.json +1 -1
@@ -4,43 +4,72 @@
4
4
  */
5
5
  import { MCPRegistry } from "./registry.js";
6
6
  import { registryLogger } from "./logging.js";
7
+ import { randomUUID } from "crypto";
7
8
  export class MCPToolRegistry extends MCPRegistry {
8
9
  tools = new Map();
10
+ toolImpls = new Map(); // Store actual tool implementations
9
11
  toolExecutionStats = new Map();
10
12
  /**
11
13
  * Register a server with its tools (updated signature)
12
14
  */
13
- async registerServer(serverId, serverConfig, context) {
14
- registryLogger.info(`Registering server: ${serverId}`);
15
- // Convert to DiscoveredMcp format for compatibility
16
- const plugin = {
17
- metadata: {
18
- name: serverId,
19
- description: typeof serverConfig === "object" && serverConfig
20
- ? serverConfig.description || "No description"
21
- : "No description",
22
- },
23
- tools: typeof serverConfig === "object" && serverConfig
24
- ? serverConfig.tools
25
- : {},
26
- configuration: typeof serverConfig === "object" && serverConfig
27
- ? serverConfig
28
- : {},
29
- };
15
+ async registerServer(serverOrId, serverConfig, context) {
16
+ let serverId;
17
+ let plugin;
18
+ if (typeof serverOrId === "string") {
19
+ // Original behavior: register by ID and config
20
+ serverId = serverOrId;
21
+ registryLogger.info(`Registering server by ID: ${serverId}`);
22
+ plugin = {
23
+ metadata: {
24
+ name: serverId,
25
+ description: typeof serverConfig === "object" && serverConfig
26
+ ? serverConfig.description || "No description"
27
+ : "No description",
28
+ },
29
+ tools: typeof serverConfig === "object" && serverConfig
30
+ ? serverConfig.tools
31
+ : {},
32
+ configuration: typeof serverConfig === "object" && serverConfig
33
+ ? serverConfig
34
+ : {},
35
+ };
36
+ }
37
+ else {
38
+ // New behavior: register server object
39
+ const server = serverOrId;
40
+ serverId = server.id || server.serverId || "unknown-server";
41
+ registryLogger.info(`Registering server object: ${serverId}`);
42
+ plugin = {
43
+ metadata: {
44
+ name: serverId,
45
+ description: server.description || server.title || "No description",
46
+ category: server.category,
47
+ },
48
+ tools: server.tools || {},
49
+ configuration: server.configuration || {},
50
+ };
51
+ }
30
52
  // Call the parent register method
31
53
  this.register(plugin);
32
54
  // Extract tools from server info if available
33
55
  const tools = plugin.tools || {};
56
+ registryLogger.debug(`Registering ${Object.keys(tools).length} tools for server ${serverId}:`, Object.keys(tools));
34
57
  for (const [toolName, toolDef] of Object.entries(tools)) {
35
58
  const toolId = `${serverId}.${toolName}`;
36
- this.tools.set(toolId, {
59
+ const toolInfo = {
37
60
  name: toolName,
38
61
  description: toolDef?.description,
39
62
  inputSchema: toolDef?.inputSchema,
40
63
  outputSchema: toolDef?.outputSchema,
41
64
  serverId,
42
65
  category: toolDef?.category || "general",
43
- });
66
+ permissions: toolDef?.permissions || [],
67
+ };
68
+ // Register only with fully-qualified toolId to avoid collisions
69
+ this.tools.set(toolId, toolInfo);
70
+ // Store the actual tool implementation for execution using toolId as key
71
+ this.toolImpls.set(toolId, toolDef);
72
+ registryLogger.debug(`Registered tool '${toolName}' with execute function:`, typeof toolDef?.execute);
44
73
  }
45
74
  }
46
75
  /**
@@ -50,39 +79,148 @@ export class MCPToolRegistry extends MCPRegistry {
50
79
  const startTime = Date.now();
51
80
  try {
52
81
  registryLogger.info(`Executing tool: ${toolName}`);
82
+ // Try to find the tool by fully-qualified name first
83
+ let tool = this.tools.get(toolName);
84
+ // If not found, search for tool by name across all entries (for backward compatibility)
85
+ let toolId = toolName;
86
+ if (!tool) {
87
+ for (const [candidateToolId, toolInfo] of this.tools.entries()) {
88
+ if (toolInfo.name === toolName) {
89
+ tool = toolInfo;
90
+ toolId = candidateToolId;
91
+ break;
92
+ }
93
+ }
94
+ }
95
+ if (!tool) {
96
+ throw new Error(`Tool '${toolName}' not found in registry`);
97
+ }
53
98
  // Create execution context if not provided
54
99
  const execContext = {
55
- sessionId: context?.sessionId || crypto.randomUUID(),
100
+ sessionId: context?.sessionId || randomUUID(),
56
101
  userId: context?.userId,
57
102
  ...context,
58
103
  };
59
- // Mock execution for now
104
+ // Get the tool implementation using the resolved toolId
105
+ const toolImpl = this.toolImpls.get(toolId);
106
+ registryLogger.debug(`Looking for tool '${toolName}' (toolId: '${toolId}'), found: ${!!toolImpl}, type: ${typeof toolImpl?.execute}`);
107
+ registryLogger.debug(`Available tools:`, Array.from(this.toolImpls.keys()));
108
+ if (!toolImpl || typeof toolImpl?.execute !== "function") {
109
+ throw new Error(`Tool '${toolName}' implementation not found or not executable`);
110
+ }
111
+ // Execute the actual tool
112
+ registryLogger.debug(`Executing tool '${toolName}' with args:`, args);
113
+ const toolResult = await toolImpl.execute(args, execContext);
114
+ // Add metadata to the tool result (don't double-wrap)
60
115
  const result = {
61
- result: `Mock execution of ${toolName}`,
62
- args,
63
- context: execContext,
116
+ ...toolResult,
117
+ usage: {
118
+ ...toolResult.usage,
119
+ executionTime: Date.now() - startTime,
120
+ },
121
+ metadata: {
122
+ ...toolResult.metadata,
123
+ toolName,
124
+ serverId: tool.serverId,
125
+ sessionId: execContext.sessionId,
126
+ executionTime: Date.now() - startTime,
127
+ },
64
128
  };
65
129
  // Update statistics
66
130
  const duration = Date.now() - startTime;
67
131
  this.updateStats(toolName, duration);
132
+ registryLogger.debug(`Tool '${toolName}' executed successfully in ${duration}ms`);
68
133
  return result;
69
134
  }
70
135
  catch (error) {
71
136
  registryLogger.error(`Tool execution failed: ${toolName}`, error);
72
- throw error;
137
+ // Return error in ToolResult format
138
+ const errorResult = {
139
+ success: false,
140
+ data: null,
141
+ error: error instanceof Error ? error.message : String(error),
142
+ usage: {
143
+ executionTime: Date.now() - startTime,
144
+ },
145
+ metadata: {
146
+ toolName,
147
+ sessionId: context?.sessionId,
148
+ },
149
+ };
150
+ return errorResult;
73
151
  }
74
152
  }
75
- /**
76
- * List all available tools (updated signature)
77
- */
78
- async listTools(context) {
79
- return Array.from(this.tools.values());
153
+ async listTools(filterOrContext) {
154
+ // FIXED: Return unique tools (avoid duplicates from dual registration)
155
+ const uniqueTools = new Map();
156
+ for (const tool of this.tools.values()) {
157
+ const key = `${tool.serverId || "unknown"}.${tool.name}`;
158
+ if (!uniqueTools.has(key)) {
159
+ uniqueTools.set(key, tool);
160
+ }
161
+ }
162
+ let result = Array.from(uniqueTools.values());
163
+ // Determine if parameter is a filter object or just context
164
+ let filter;
165
+ if (filterOrContext) {
166
+ // Check if it's a filter object (has filter-specific properties) or just context
167
+ if ("sessionId" in filterOrContext || "userId" in filterOrContext) {
168
+ // It's an ExecutionContext, treat as no filter
169
+ filter = undefined;
170
+ }
171
+ else {
172
+ // It's a filter object
173
+ filter = filterOrContext;
174
+ }
175
+ }
176
+ // Apply filters if provided
177
+ if (filter) {
178
+ if (filter.category) {
179
+ result = result.filter((tool) => tool.category === filter.category);
180
+ }
181
+ if (filter.serverId) {
182
+ result = result.filter((tool) => tool.serverId === filter.serverId);
183
+ }
184
+ if (filter.serverCategory) {
185
+ result = result.filter((tool) => {
186
+ const server = this.get(tool.serverId || "");
187
+ return server?.metadata?.category === filter.serverCategory;
188
+ });
189
+ }
190
+ if (filter.permissions && filter.permissions.length > 0) {
191
+ result = result.filter((tool) => {
192
+ const toolPermissions = tool.permissions || [];
193
+ return filter.permissions.some((perm) => toolPermissions.includes(perm));
194
+ });
195
+ }
196
+ }
197
+ registryLogger.debug(`Listed ${result.length} unique tools (${filter ? "filtered" : "unfiltered"})`);
198
+ return result;
80
199
  }
81
200
  /**
82
- * Get tool information
201
+ * Get tool information with server details
83
202
  */
84
203
  getToolInfo(toolName) {
85
- return this.tools.get(toolName);
204
+ // Try to find the tool by fully-qualified name first
205
+ let tool = this.tools.get(toolName);
206
+ // If not found, search for tool by name across all entries (for backward compatibility)
207
+ if (!tool) {
208
+ for (const toolInfo of this.tools.values()) {
209
+ if (toolInfo.name === toolName) {
210
+ tool = toolInfo;
211
+ break;
212
+ }
213
+ }
214
+ }
215
+ if (!tool) {
216
+ return undefined;
217
+ }
218
+ return {
219
+ tool,
220
+ server: {
221
+ id: tool.serverId || "unknown-server",
222
+ },
223
+ };
86
224
  }
87
225
  /**
88
226
  * Update execution statistics
@@ -120,22 +258,52 @@ export class MCPToolRegistry extends MCPRegistry {
120
258
  * Get tools by category
121
259
  */
122
260
  getToolsByCategory(category) {
123
- return Array.from(this.tools.values()).filter((tool) => tool.category === category);
261
+ // Return unique tools by fully-qualified toolId
262
+ const uniqueTools = new Map();
263
+ for (const [toolId, tool] of this.tools.entries()) {
264
+ if (tool.category === category && !uniqueTools.has(toolId)) {
265
+ uniqueTools.set(toolId, tool);
266
+ }
267
+ }
268
+ return Array.from(uniqueTools.values());
124
269
  }
125
270
  /**
126
271
  * Check if tool exists
127
272
  */
128
273
  hasTool(toolName) {
129
- return this.tools.has(toolName);
274
+ // Check by fully-qualified name first, then fallback to any matching tool name
275
+ if (this.tools.has(toolName)) {
276
+ return true;
277
+ }
278
+ for (const tool of this.tools.values()) {
279
+ if (tool.name === toolName) {
280
+ return true;
281
+ }
282
+ }
283
+ return false;
130
284
  }
131
285
  /**
132
286
  * Remove a tool
133
287
  */
134
288
  removeTool(toolName) {
135
- const removed = this.tools.delete(toolName);
136
- if (removed) {
289
+ // Remove by fully-qualified name first, then fallback to any matching tool name
290
+ let removed = false;
291
+ if (this.tools.has(toolName)) {
292
+ this.tools.delete(toolName);
137
293
  this.toolExecutionStats.delete(toolName);
138
294
  registryLogger.info(`Removed tool: ${toolName}`);
295
+ removed = true;
296
+ }
297
+ else {
298
+ // Remove all tools with matching name
299
+ for (const [toolId, tool] of Array.from(this.tools.entries())) {
300
+ if (tool.name === toolName) {
301
+ this.tools.delete(toolId);
302
+ this.toolExecutionStats.delete(toolId);
303
+ registryLogger.info(`Removed tool: ${toolId}`);
304
+ removed = true;
305
+ }
306
+ }
139
307
  }
140
308
  return removed;
141
309
  }
@@ -146,10 +314,30 @@ export class MCPToolRegistry extends MCPRegistry {
146
314
  return this.tools.size;
147
315
  }
148
316
  /**
149
- * Get statistics (alias for getExecutionStats)
317
+ * Get comprehensive statistics
150
318
  */
151
319
  getStats() {
152
- return this.getExecutionStats();
320
+ const servers = this.list(); // Get all registered servers
321
+ const allTools = Array.from(this.tools.values());
322
+ // Count servers by category
323
+ const serversByCategory = {};
324
+ for (const server of servers) {
325
+ const category = server.metadata?.category || "uncategorized";
326
+ serversByCategory[category] = (serversByCategory[category] || 0) + 1;
327
+ }
328
+ // Count tools by category
329
+ const toolsByCategory = {};
330
+ for (const tool of allTools) {
331
+ const category = tool.category || "uncategorized";
332
+ toolsByCategory[category] = (toolsByCategory[category] || 0) + 1;
333
+ }
334
+ return {
335
+ totalServers: servers.length,
336
+ totalTools: allTools.length,
337
+ serversByCategory,
338
+ toolsByCategory,
339
+ executionStats: this.getExecutionStats(),
340
+ };
153
341
  }
154
342
  /**
155
343
  * Unregister a server
@@ -67,7 +67,12 @@ export declare class UnifiedMCPSystem {
67
67
  /**
68
68
  * Get tool information
69
69
  */
70
- getToolInfo(toolName: string): import("./tool-registry.js").ToolInfo | undefined;
70
+ getToolInfo(toolName: string): {
71
+ tool: import("./tool-registry.js").ToolInfo;
72
+ server: {
73
+ id: string;
74
+ };
75
+ } | undefined;
71
76
  /**
72
77
  * Get registry instance
73
78
  */
@@ -116,7 +121,12 @@ export declare function listMCPTools(criteria?: any): Promise<import("./tool-reg
116
121
  /**
117
122
  * Get MCP tool information
118
123
  */
119
- export declare function getMCPToolInfo(toolName: string): import("./tool-registry.js").ToolInfo | undefined;
124
+ export declare function getMCPToolInfo(toolName: string): {
125
+ tool: import("./tool-registry.js").ToolInfo;
126
+ server: {
127
+ id: string;
128
+ };
129
+ } | undefined;
120
130
  /**
121
131
  * Get unified MCP system status
122
132
  */
@@ -19,9 +19,13 @@ export declare class UnifiedMCPRegistry extends MCPToolRegistry {
19
19
  private activeConnections;
20
20
  constructor(errorManager?: ErrorManager);
21
21
  /**
22
- * Initialize with auto-discovery
22
+ * Initialize with auto-discovery and manual config
23
23
  */
24
24
  initialize(options?: DiscoveryOptions): Promise<void>;
25
+ /**
26
+ * Load servers from .mcp-config.json
27
+ */
28
+ private loadManualConfig;
25
29
  /**
26
30
  * Enable or disable auto-discovery
27
31
  */
@@ -51,9 +55,13 @@ export declare class UnifiedMCPRegistry extends MCPToolRegistry {
51
55
  */
52
56
  listAllTools(): Promise<ToolInfo[]>;
53
57
  /**
54
- * Execute a tool through the registry
58
+ * Execute a tool through the registry with fallback to direct MCP execution
55
59
  */
56
60
  executeTool<T = unknown>(toolName: string, args?: unknown, context?: ExecutionContext): Promise<T>;
61
+ /**
62
+ * Execute tool via direct MCP server connection (fallback)
63
+ */
64
+ private executeToolViaMCPServer;
57
65
  /**
58
66
  * Lazily activate a server by ID
59
67
  */
@@ -65,11 +73,17 @@ export declare class UnifiedMCPRegistry extends MCPToolRegistry {
65
73
  /**
66
74
  * Get registry statistics (override parent method)
67
75
  */
68
- getStats(): Record<string, {
69
- count: number;
70
- averageTime: number;
71
- totalTime: number;
72
- }>;
76
+ getStats(): {
77
+ totalServers: number;
78
+ totalTools: number;
79
+ serversByCategory: Record<string, number>;
80
+ toolsByCategory: Record<string, number>;
81
+ executionStats: Record<string, {
82
+ count: number;
83
+ averageTime: number;
84
+ totalTime: number;
85
+ }>;
86
+ };
73
87
  /**
74
88
  * Get detailed registry statistics
75
89
  */
@@ -7,6 +7,8 @@ import { MCPToolRegistry, } from "./tool-registry.js";
7
7
  import { TransportManager, TransportConfigSchema, } from "./transport-manager.js";
8
8
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
9
9
  import { ErrorManager } from "./error-manager.js";
10
+ import * as fs from "fs";
11
+ import * as path from "path";
10
12
  /**
11
13
  * Unified registry combining multiple sources
12
14
  */
@@ -24,10 +26,12 @@ export class UnifiedMCPRegistry extends MCPToolRegistry {
24
26
  this.transportManager = new TransportManager(this.errorManager);
25
27
  }
26
28
  /**
27
- * Initialize with auto-discovery
29
+ * Initialize with auto-discovery and manual config
28
30
  */
29
31
  async initialize(options = {}) {
30
32
  unifiedRegistryLogger.info("Initializing unified MCP registry...");
33
+ // Load manual configuration first
34
+ await this.loadManualConfig();
31
35
  if (this.autoDiscoveryEnabled) {
32
36
  const result = await autoRegisterMCPServers(options);
33
37
  unifiedRegistryLogger.info(`Auto-discovery complete: ${result.registered} registered, ${result.failed} failed`);
@@ -39,6 +43,58 @@ export class UnifiedMCPRegistry extends MCPToolRegistry {
39
43
  }
40
44
  }
41
45
  }
46
+ /**
47
+ * Load servers from .mcp-config.json
48
+ */
49
+ async loadManualConfig() {
50
+ const configPath = path.join(process.cwd(), ".mcp-config.json");
51
+ try {
52
+ await fs.promises.access(configPath, fs.constants.F_OK);
53
+ }
54
+ catch {
55
+ unifiedRegistryLogger.debug("No .mcp-config.json found");
56
+ return;
57
+ }
58
+ try {
59
+ const configContent = await fs.promises.readFile(configPath, "utf-8");
60
+ const config = JSON.parse(configContent);
61
+ if (!config.mcpServers) {
62
+ unifiedRegistryLogger.debug("No mcpServers section in config");
63
+ return;
64
+ }
65
+ unifiedRegistryLogger.info(`Loading ${Object.keys(config.mcpServers).length} servers from .mcp-config.json`);
66
+ for (const [serverId, serverConfig] of Object.entries(config.mcpServers)) {
67
+ try {
68
+ // Convert server config to DiscoveredMCP format
69
+ const discoveredMcp = {
70
+ metadata: {
71
+ name: serverId,
72
+ version: "1.0.0",
73
+ main: "index.js",
74
+ engine: { neurolink: ">=4.0.0" },
75
+ description: `MCP server: ${serverId}`,
76
+ permissions: ["filesystem", "network"],
77
+ },
78
+ entryPath: serverConfig.command || "npx",
79
+ source: "project",
80
+ constructor: undefined,
81
+ };
82
+ // Register the server
83
+ this.register(discoveredMcp);
84
+ this.manualServers.set(serverId, serverConfig);
85
+ this.availableServers.add(serverId);
86
+ unifiedRegistryLogger.debug(`Registered manual server: ${serverId}`);
87
+ }
88
+ catch (error) {
89
+ unifiedRegistryLogger.error(`Failed to register server ${serverId}:`, error instanceof Error ? error.message : String(error));
90
+ }
91
+ }
92
+ unifiedRegistryLogger.info(`Manual config loaded: ${this.manualServers.size} servers registered`);
93
+ }
94
+ catch (error) {
95
+ unifiedRegistryLogger.error("Failed to load manual config:", error instanceof Error ? error.message : String(error));
96
+ }
97
+ }
42
98
  /**
43
99
  * Enable or disable auto-discovery
44
100
  */
@@ -84,30 +140,136 @@ export class UnifiedMCPRegistry extends MCPToolRegistry {
84
140
  */
85
141
  async listAllTools() {
86
142
  const allTools = [];
87
- const plugins = this.list();
143
+ try {
144
+ // FIXED: Get built-in tools from base registry
145
+ const builtInTools = await super.listTools();
146
+ allTools.push(...builtInTools.map((tool) => ({
147
+ ...tool,
148
+ id: tool.name,
149
+ serverId: tool.serverId || "built-in",
150
+ source: "built-in",
151
+ isExternal: false,
152
+ })));
153
+ unifiedRegistryLogger.debug(`Found ${builtInTools.length} built-in tools`);
154
+ }
155
+ catch (error) {
156
+ unifiedRegistryLogger.warn("Failed to get built-in tools:", error);
157
+ }
158
+ // FIXED: Get tools from external servers with proper error handling
159
+ // Use the internal plugin registry for accurate server listing
160
+ const plugins = Array.from(this.plugins.values());
161
+ const externalToolPromises = [];
88
162
  for (const plugin of plugins) {
89
- try {
90
- // Get tools from plugin metadata if available
91
- const tools = await this.listTools();
92
- allTools.push(...tools.map((tool) => ({
93
- ...tool,
94
- id: tool.name,
95
- serverId: tool.serverId || plugin.metadata.name,
96
- source: "unified",
97
- })));
98
- }
99
- catch (error) {
100
- unifiedRegistryLogger.warn(`Failed to get tools from ${plugin.metadata.name}:`, error);
163
+ if (plugin.metadata?.name &&
164
+ this.availableServers.has(plugin.metadata.name)) {
165
+ const connection = this.activeConnections.get(plugin.metadata.name);
166
+ if (connection) {
167
+ externalToolPromises.push(connection
168
+ .listTools()
169
+ .then((response) => {
170
+ const serverTools = response.tools || [];
171
+ allTools.push(...serverTools.map((tool) => ({
172
+ name: tool.name,
173
+ description: tool.description,
174
+ inputSchema: tool.inputSchema,
175
+ serverId: plugin.metadata.name,
176
+ id: `${plugin.metadata.name}.${tool.name}`,
177
+ source: "external",
178
+ isExternal: true,
179
+ })));
180
+ unifiedRegistryLogger.debug(`Found ${serverTools.length} tools from ${plugin.metadata.name}`);
181
+ })
182
+ .catch((error) => {
183
+ unifiedRegistryLogger.warn(`Failed to get tools from ${plugin.metadata.name}:`, error);
184
+ }));
185
+ }
101
186
  }
102
187
  }
188
+ await Promise.all(externalToolPromises);
189
+ unifiedRegistryLogger.info(`Total tools available: ${allTools.length}`);
103
190
  return allTools;
104
191
  }
105
192
  /**
106
- * Execute a tool through the registry
193
+ * Execute a tool through the registry with fallback to direct MCP execution
107
194
  */
108
195
  async executeTool(toolName, args, context) {
109
196
  unifiedRegistryLogger.info(`Executing tool: ${toolName}`);
110
- return super.executeTool(toolName, args, context);
197
+ // STEP 1: Try built-in tools first
198
+ try {
199
+ const result = await super.executeTool(toolName, args, context);
200
+ unifiedRegistryLogger.info(`Tool ${toolName} executed successfully via built-in registry`);
201
+ return result;
202
+ }
203
+ catch (builtInError) {
204
+ unifiedRegistryLogger.debug(`Built-in tool execution failed: ${builtInError.message}`);
205
+ }
206
+ // STEP 2: Try external MCP servers
207
+ try {
208
+ const result = await this.executeToolViaMCPServer(toolName, args, context);
209
+ unifiedRegistryLogger.info(`Tool ${toolName} executed successfully via external MCP server`);
210
+ return result;
211
+ }
212
+ catch (externalError) {
213
+ unifiedRegistryLogger.debug(`External MCP execution failed: ${externalError.message}`);
214
+ }
215
+ // STEP 3: Comprehensive error with available tools
216
+ const availableTools = await this.listAllTools();
217
+ const toolNames = availableTools.map((t) => t.name).join(", ");
218
+ const builtInTools = availableTools
219
+ .filter((t) => !t.isExternal)
220
+ .map((t) => t.name)
221
+ .join(", ");
222
+ const externalTools = availableTools
223
+ .filter((t) => t.isExternal)
224
+ .map((t) => `${t.serverId}.${t.name}`)
225
+ .join(", ");
226
+ const errorMessage = [
227
+ `Tool '${toolName}' not found in any registry.`,
228
+ `Available built-in tools: ${builtInTools || "none"}`,
229
+ `Available external tools: ${externalTools || "none"}`,
230
+ `Connected servers: ${Array.from(this.availableServers).join(", ") || "none"}`,
231
+ ].join("\n");
232
+ throw new Error(errorMessage);
233
+ }
234
+ /**
235
+ * Execute tool via direct MCP server connection (fallback)
236
+ */
237
+ async executeToolViaMCPServer(toolName, args, context) {
238
+ const configPath = path.join(process.cwd(), ".mcp-config.json");
239
+ if (!fs.existsSync(configPath)) {
240
+ throw new Error(`Tool '${toolName}' not found and no .mcp-config.json for fallback`);
241
+ }
242
+ const configContent = fs.readFileSync(configPath, "utf-8");
243
+ const config = JSON.parse(configContent);
244
+ if (!config.mcpServers) {
245
+ throw new Error(`Tool '${toolName}' not found and no servers configured`);
246
+ }
247
+ // Try each configured server
248
+ const errors = [];
249
+ for (const [serverId, serverConfig] of Object.entries(config.mcpServers)) {
250
+ try {
251
+ unifiedRegistryLogger.debug(`Trying tool ${toolName} on server ${serverId}`);
252
+ // Import the executeMCPTool function
253
+ const { executeMCPTool } = await import("../../cli/commands/mcp.js");
254
+ const result = await executeMCPTool(serverConfig, toolName, args || {});
255
+ // Convert to ToolResult format
256
+ const toolResult = {
257
+ success: true,
258
+ data: result,
259
+ metadata: { toolName, serverId, sessionId: context?.sessionId },
260
+ };
261
+ unifiedRegistryLogger.info(`Tool ${toolName} executed successfully via server ${serverId}`);
262
+ return toolResult;
263
+ }
264
+ catch (serverError) {
265
+ const errorMsg = serverError instanceof Error
266
+ ? serverError.message
267
+ : String(serverError);
268
+ errors.push(`${serverId}: ${errorMsg}`);
269
+ unifiedRegistryLogger.debug(`Tool ${toolName} failed on server ${serverId}: ${errorMsg}`);
270
+ }
271
+ }
272
+ throw new Error(`Tool '${toolName}' not found on any configured MCP server. Errors: ${errors.join("; ")}`);
111
273
  }
112
274
  /**
113
275
  * Lazily activate a server by ID
@@ -143,7 +305,7 @@ export class UnifiedMCPRegistry extends MCPToolRegistry {
143
305
  * Get registry statistics (override parent method)
144
306
  */
145
307
  getStats() {
146
- // Return execution stats in the expected format
308
+ // Return full stats interface as expected by MCPOrchestrator
147
309
  return super.getStats();
148
310
  }
149
311
  /**