@juspay/neurolink 4.1.0 → 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.
- package/CHANGELOG.md +14 -2
- package/README.md +105 -116
- package/dist/cli/commands/mcp.d.ts +11 -0
- package/dist/cli/commands/mcp.js +332 -223
- package/dist/cli/index.js +69 -8
- package/dist/core/factory.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/core/factory.js +2 -2
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/lib/mcp/context-manager.d.ts +6 -0
- package/dist/lib/mcp/context-manager.js +8 -0
- package/dist/lib/mcp/contracts/mcpContract.d.ts +1 -0
- package/dist/lib/mcp/external-client.js +6 -2
- package/dist/lib/mcp/initialize.d.ts +2 -1
- package/dist/lib/mcp/initialize.js +8 -7
- package/dist/lib/mcp/orchestrator.js +9 -0
- package/dist/lib/mcp/registry.d.ts +1 -1
- package/dist/lib/mcp/servers/ai-providers/ai-analysis-tools.js +1 -1
- package/dist/lib/mcp/servers/ai-providers/ai-core-server.js +3 -3
- package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
- package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.js +1 -1
- package/dist/lib/mcp/session-manager.js +1 -1
- package/dist/lib/mcp/session-persistence.js +1 -1
- package/dist/lib/mcp/tool-registry.d.ts +31 -11
- package/dist/lib/mcp/tool-registry.js +226 -38
- package/dist/lib/mcp/unified-mcp.d.ts +12 -2
- package/dist/lib/mcp/unified-registry.d.ts +21 -7
- package/dist/lib/mcp/unified-registry.js +179 -17
- package/dist/lib/neurolink.js +17 -25
- package/dist/lib/providers/googleVertexAI.js +19 -1
- package/dist/lib/providers/openAI.js +18 -1
- package/dist/lib/utils/provider-setup-messages.d.ts +8 -0
- package/dist/lib/utils/provider-setup-messages.js +120 -0
- package/dist/lib/utils/provider-validation.d.ts +35 -0
- package/dist/lib/utils/provider-validation.js +625 -0
- package/dist/lib/utils/providerUtils-fixed.js +20 -1
- package/dist/lib/utils/providerUtils.d.ts +2 -2
- package/dist/lib/utils/providerUtils.js +38 -7
- package/dist/lib/utils/timeout-manager.d.ts +75 -0
- package/dist/lib/utils/timeout-manager.js +244 -0
- package/dist/mcp/context-manager.d.ts +6 -0
- package/dist/mcp/context-manager.js +8 -0
- package/dist/mcp/contracts/mcpContract.d.ts +1 -0
- package/dist/mcp/external-client.js +6 -2
- package/dist/mcp/initialize.d.ts +2 -1
- package/dist/mcp/initialize.js +8 -7
- package/dist/mcp/orchestrator.js +9 -0
- package/dist/mcp/plugins/core/neurolink-mcp.json +15 -15
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/mcp/servers/ai-providers/ai-analysis-tools.js +1 -1
- package/dist/mcp/servers/ai-providers/ai-core-server.js +3 -3
- package/dist/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
- package/dist/mcp/servers/ai-providers/ai-workflow-tools.js +1 -1
- package/dist/mcp/session-manager.js +1 -1
- package/dist/mcp/session-persistence.js +1 -1
- package/dist/mcp/tool-registry.d.ts +31 -11
- package/dist/mcp/tool-registry.js +226 -38
- package/dist/mcp/unified-mcp.d.ts +12 -2
- package/dist/mcp/unified-registry.d.ts +21 -7
- package/dist/mcp/unified-registry.js +179 -17
- package/dist/neurolink.js +17 -25
- package/dist/providers/googleVertexAI.js +19 -1
- package/dist/providers/openAI.js +18 -1
- package/dist/utils/provider-setup-messages.d.ts +8 -0
- package/dist/utils/provider-setup-messages.js +120 -0
- package/dist/utils/provider-validation.d.ts +35 -0
- package/dist/utils/provider-validation.js +625 -0
- package/dist/utils/providerUtils-fixed.js +20 -1
- package/dist/utils/providerUtils.d.ts +2 -2
- package/dist/utils/providerUtils.js +38 -7
- package/dist/utils/timeout-manager.d.ts +75 -0
- package/dist/utils/timeout-manager.js +244 -0
- package/package.json +245 -245
|
@@ -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(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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 ||
|
|
100
|
+
sessionId: context?.sessionId || randomUUID(),
|
|
56
101
|
userId: context?.userId,
|
|
57
102
|
...context,
|
|
58
103
|
};
|
|
59
|
-
//
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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
|
|
317
|
+
* Get comprehensive statistics
|
|
150
318
|
*/
|
|
151
319
|
getStats() {
|
|
152
|
-
|
|
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):
|
|
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):
|
|
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():
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
|
308
|
+
// Return full stats interface as expected by MCPOrchestrator
|
|
147
309
|
return super.getStats();
|
|
148
310
|
}
|
|
149
311
|
/**
|