@juspay/neurolink 7.14.0 → 7.14.2
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 +12 -0
- package/dist/agent/directTools.d.ts +3 -3
- package/dist/agent/directTools.js +1 -1
- package/dist/cli/commands/mcp.js +67 -207
- package/dist/cli/factories/commandFactory.js +7 -1
- package/dist/cli/utils/interactiveSetup.js +1 -1
- package/dist/core/baseProvider.d.ts +6 -40
- package/dist/core/baseProvider.js +102 -75
- package/dist/core/dynamicModels.d.ts +14 -14
- package/dist/core/dynamicModels.js +1 -1
- package/dist/core/evaluation.js +1 -1
- package/dist/core/factory.js +1 -12
- package/dist/index.d.ts +23 -4
- package/dist/index.js +22 -1
- package/dist/lib/agent/directTools.js +1 -1
- package/dist/lib/core/baseProvider.d.ts +6 -40
- package/dist/lib/core/baseProvider.js +102 -75
- package/dist/lib/core/dynamicModels.js +1 -1
- package/dist/lib/core/evaluation.js +1 -1
- package/dist/lib/core/factory.js +1 -12
- package/dist/lib/index.d.ts +23 -4
- package/dist/lib/index.js +22 -1
- package/dist/lib/mcp/externalServerManager.d.ts +46 -13
- package/dist/lib/mcp/externalServerManager.js +393 -32
- package/dist/lib/mcp/mcpClientFactory.d.ts +5 -4
- package/dist/lib/mcp/mcpClientFactory.js +8 -3
- package/dist/lib/mcp/toolDiscoveryService.d.ts +1 -0
- package/dist/lib/mcp/toolDiscoveryService.js +76 -8
- package/dist/lib/mcp/toolRegistry.d.ts +15 -11
- package/dist/lib/mcp/toolRegistry.js +118 -55
- package/dist/lib/models/modelResolver.js +1 -1
- package/dist/lib/neurolink.d.ts +32 -31
- package/dist/lib/neurolink.js +173 -210
- package/dist/lib/providers/googleVertex.d.ts +6 -0
- package/dist/lib/providers/googleVertex.js +11 -0
- package/dist/lib/providers/huggingFace.js +1 -1
- package/dist/lib/providers/mistral.js +3 -3
- package/dist/lib/providers/ollama.js +1 -1
- package/dist/lib/providers/openAI.d.ts +3 -2
- package/dist/lib/providers/openAI.js +2 -2
- package/dist/lib/providers/openaiCompatible.d.ts +1 -1
- package/dist/lib/providers/openaiCompatible.js +2 -2
- package/dist/lib/providers/sagemaker/config.js +1 -1
- package/dist/lib/sdk/toolRegistration.d.ts +4 -13
- package/dist/lib/sdk/toolRegistration.js +19 -66
- package/dist/lib/types/cli.d.ts +0 -1
- package/dist/lib/types/cli.js +0 -1
- package/dist/lib/types/common.d.ts +1 -2
- package/dist/lib/types/common.js +0 -1
- package/dist/lib/types/contextTypes.d.ts +1 -1
- package/dist/lib/types/contextTypes.js +3 -3
- package/dist/lib/types/externalMcp.d.ts +6 -0
- package/dist/lib/types/externalMcp.js +1 -0
- package/dist/lib/types/index.d.ts +2 -3
- package/dist/lib/types/index.js +0 -1
- package/dist/lib/types/mcpTypes.d.ts +53 -99
- package/dist/lib/types/providers.d.ts +0 -1
- package/dist/lib/types/providers.js +0 -1
- package/dist/lib/types/tools.d.ts +2 -2
- package/dist/lib/types/tools.js +2 -2
- package/dist/lib/utils/factoryProcessing.js +1 -1
- package/dist/lib/utils/mcpDefaults.d.ts +54 -0
- package/dist/lib/utils/mcpDefaults.js +125 -0
- package/dist/lib/utils/providerConfig.d.ts +1 -1
- package/dist/lib/utils/providerConfig.js +2 -2
- package/dist/lib/utils/providerHealth.js +6 -6
- package/dist/mcp/externalServerManager.d.ts +46 -13
- package/dist/mcp/externalServerManager.js +393 -32
- package/dist/mcp/mcpClientFactory.d.ts +5 -4
- package/dist/mcp/mcpClientFactory.js +8 -3
- package/dist/mcp/toolDiscoveryService.d.ts +1 -0
- package/dist/mcp/toolDiscoveryService.js +76 -8
- package/dist/mcp/toolRegistry.d.ts +15 -11
- package/dist/mcp/toolRegistry.js +118 -55
- package/dist/models/modelResolver.js +1 -1
- package/dist/neurolink.d.ts +32 -31
- package/dist/neurolink.js +173 -210
- package/dist/providers/googleVertex.d.ts +6 -0
- package/dist/providers/googleVertex.js +11 -0
- package/dist/providers/huggingFace.js +1 -1
- package/dist/providers/mistral.js +3 -3
- package/dist/providers/ollama.js +1 -1
- package/dist/providers/openAI.d.ts +3 -2
- package/dist/providers/openAI.js +2 -2
- package/dist/providers/openaiCompatible.d.ts +1 -1
- package/dist/providers/openaiCompatible.js +2 -2
- package/dist/providers/sagemaker/config.js +1 -1
- package/dist/sdk/toolRegistration.d.ts +4 -13
- package/dist/sdk/toolRegistration.js +19 -66
- package/dist/types/cli.d.ts +0 -1
- package/dist/types/cli.js +0 -1
- package/dist/types/common.d.ts +1 -2
- package/dist/types/common.js +0 -1
- package/dist/types/contextTypes.d.ts +1 -1
- package/dist/types/contextTypes.js +3 -3
- package/dist/types/externalMcp.d.ts +6 -0
- package/dist/types/externalMcp.js +1 -0
- package/dist/types/index.d.ts +2 -3
- package/dist/types/index.js +0 -1
- package/dist/types/mcpTypes.d.ts +53 -99
- package/dist/types/providers.d.ts +0 -1
- package/dist/types/providers.js +0 -1
- package/dist/types/tools.d.ts +2 -2
- package/dist/types/tools.js +2 -2
- package/dist/utils/factoryProcessing.js +1 -1
- package/dist/utils/mcpDefaults.d.ts +54 -0
- package/dist/utils/mcpDefaults.js +125 -0
- package/dist/utils/providerConfig.d.ts +1 -1
- package/dist/utils/providerConfig.js +2 -2
- package/dist/utils/providerHealth.js +6 -6
- package/package.json +1 -1
|
@@ -10,16 +10,74 @@ import { EventEmitter } from "events";
|
|
|
10
10
|
import { mcpLogger } from "../utils/logger.js";
|
|
11
11
|
import { MCPClientFactory } from "./mcpClientFactory.js";
|
|
12
12
|
import { ToolDiscoveryService } from "./toolDiscoveryService.js";
|
|
13
|
+
import { toolRegistry } from "./toolRegistry.js";
|
|
14
|
+
import { detectCategory } from "../utils/mcpDefaults.js";
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
* Core class for managing external MCP servers
|
|
16
|
+
* Type guard to validate if an object can be safely used as Record<string, JsonValue>
|
|
16
17
|
*/
|
|
18
|
+
function isValidJsonRecord(value) {
|
|
19
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const record = value;
|
|
23
|
+
return Object.values(record).every((val) => {
|
|
24
|
+
// JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }
|
|
25
|
+
if (val === null ||
|
|
26
|
+
typeof val === "string" ||
|
|
27
|
+
typeof val === "number" ||
|
|
28
|
+
typeof val === "boolean") {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(val)) {
|
|
32
|
+
return val.every((item) => isValidJsonRecord(item) ||
|
|
33
|
+
typeof item === "string" ||
|
|
34
|
+
typeof item === "number" ||
|
|
35
|
+
typeof item === "boolean" ||
|
|
36
|
+
item === null);
|
|
37
|
+
}
|
|
38
|
+
if (typeof val === "object" && val !== null) {
|
|
39
|
+
return isValidJsonRecord(val);
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Safely converts unknown metadata to Record<string, JsonValue> or returns undefined
|
|
46
|
+
*/
|
|
47
|
+
function safeMetadataConversion(metadata) {
|
|
48
|
+
return isValidJsonRecord(metadata) ? metadata : undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Type guard to validate external MCP server configuration
|
|
52
|
+
*/
|
|
53
|
+
function isValidExternalMCPServerConfig(config) {
|
|
54
|
+
if (typeof config !== "object" || config === null) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const record = config;
|
|
58
|
+
return (typeof record.command === "string" &&
|
|
59
|
+
(record.args === undefined || Array.isArray(record.args)) &&
|
|
60
|
+
(record.env === undefined ||
|
|
61
|
+
(typeof record.env === "object" && record.env !== null)) &&
|
|
62
|
+
(record.transport === undefined || typeof record.transport === "string") &&
|
|
63
|
+
(record.timeout === undefined || typeof record.timeout === "number") &&
|
|
64
|
+
(record.retries === undefined || typeof record.retries === "number") &&
|
|
65
|
+
(record.healthCheckInterval === undefined ||
|
|
66
|
+
typeof record.healthCheckInterval === "number") &&
|
|
67
|
+
(record.autoRestart === undefined ||
|
|
68
|
+
typeof record.autoRestart === "boolean") &&
|
|
69
|
+
(record.cwd === undefined || typeof record.cwd === "string") &&
|
|
70
|
+
(record.url === undefined || typeof record.url === "string") &&
|
|
71
|
+
(record.metadata === undefined ||
|
|
72
|
+
(typeof record.metadata === "object" && record.metadata !== null)));
|
|
73
|
+
}
|
|
17
74
|
export class ExternalServerManager extends EventEmitter {
|
|
18
75
|
servers = new Map();
|
|
19
76
|
config;
|
|
20
77
|
isShuttingDown = false;
|
|
21
78
|
toolDiscovery;
|
|
22
|
-
|
|
79
|
+
enableMainRegistryIntegration;
|
|
80
|
+
constructor(config = {}, options = {}) {
|
|
23
81
|
super();
|
|
24
82
|
// Set defaults for configuration
|
|
25
83
|
this.config = {
|
|
@@ -32,6 +90,9 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
32
90
|
enablePerformanceMonitoring: config.enablePerformanceMonitoring ?? true,
|
|
33
91
|
logLevel: config.logLevel ?? "info",
|
|
34
92
|
};
|
|
93
|
+
// Enable main tool registry integration by default
|
|
94
|
+
this.enableMainRegistryIntegration =
|
|
95
|
+
options.enableMainRegistryIntegration ?? true;
|
|
35
96
|
// Initialize tool discovery service
|
|
36
97
|
this.toolDiscovery = new ToolDiscoveryService();
|
|
37
98
|
// Forward tool discovery events
|
|
@@ -46,6 +107,99 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
46
107
|
process.on("SIGTERM", () => this.shutdown());
|
|
47
108
|
process.on("beforeExit", () => this.shutdown());
|
|
48
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Load MCP server configurations from .mcp-config.json file
|
|
112
|
+
* Automatically registers servers found in the configuration
|
|
113
|
+
* @param configPath Optional path to config file (defaults to .mcp-config.json in cwd)
|
|
114
|
+
* @returns Promise resolving to number of servers loaded
|
|
115
|
+
*/
|
|
116
|
+
async loadMCPConfiguration(configPath) {
|
|
117
|
+
const fs = await import("fs");
|
|
118
|
+
const path = await import("path");
|
|
119
|
+
const finalConfigPath = configPath || path.join(process.cwd(), ".mcp-config.json");
|
|
120
|
+
if (!fs.existsSync(finalConfigPath)) {
|
|
121
|
+
mcpLogger.debug(`[ExternalServerManager] No MCP config found at ${finalConfigPath}`);
|
|
122
|
+
return { serversLoaded: 0, errors: [] };
|
|
123
|
+
}
|
|
124
|
+
mcpLogger.debug(`[ExternalServerManager] Loading MCP configuration from ${finalConfigPath}`);
|
|
125
|
+
try {
|
|
126
|
+
const configContent = fs.readFileSync(finalConfigPath, "utf8");
|
|
127
|
+
const config = JSON.parse(configContent);
|
|
128
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
129
|
+
mcpLogger.debug("[ExternalServerManager] No mcpServers found in configuration");
|
|
130
|
+
return { serversLoaded: 0, errors: [] };
|
|
131
|
+
}
|
|
132
|
+
let serversLoaded = 0;
|
|
133
|
+
const errors = [];
|
|
134
|
+
for (const [serverId, serverConfig] of Object.entries(config.mcpServers)) {
|
|
135
|
+
try {
|
|
136
|
+
// Validate and convert config format to MCPServerInfo
|
|
137
|
+
if (!isValidExternalMCPServerConfig(serverConfig)) {
|
|
138
|
+
throw new Error(`Invalid server config for ${serverId}: missing required properties or wrong types`);
|
|
139
|
+
}
|
|
140
|
+
const externalConfig = {
|
|
141
|
+
id: serverId,
|
|
142
|
+
name: serverId,
|
|
143
|
+
description: `External MCP server: ${serverId}`,
|
|
144
|
+
transport: typeof serverConfig.transport === "string"
|
|
145
|
+
? serverConfig.transport
|
|
146
|
+
: "stdio",
|
|
147
|
+
status: "initializing",
|
|
148
|
+
tools: [],
|
|
149
|
+
command: serverConfig.command,
|
|
150
|
+
args: Array.isArray(serverConfig.args)
|
|
151
|
+
? serverConfig.args
|
|
152
|
+
: [],
|
|
153
|
+
env: typeof serverConfig.env === "object" && serverConfig.env !== null
|
|
154
|
+
? serverConfig.env
|
|
155
|
+
: {},
|
|
156
|
+
timeout: typeof serverConfig.timeout === "number"
|
|
157
|
+
? serverConfig.timeout
|
|
158
|
+
: undefined,
|
|
159
|
+
retries: typeof serverConfig.retries === "number"
|
|
160
|
+
? serverConfig.retries
|
|
161
|
+
: undefined,
|
|
162
|
+
healthCheckInterval: typeof serverConfig.healthCheckInterval === "number"
|
|
163
|
+
? serverConfig.healthCheckInterval
|
|
164
|
+
: undefined,
|
|
165
|
+
autoRestart: typeof serverConfig.autoRestart === "boolean"
|
|
166
|
+
? serverConfig.autoRestart
|
|
167
|
+
: undefined,
|
|
168
|
+
cwd: typeof serverConfig.cwd === "string"
|
|
169
|
+
? serverConfig.cwd
|
|
170
|
+
: undefined,
|
|
171
|
+
url: typeof serverConfig.url === "string"
|
|
172
|
+
? serverConfig.url
|
|
173
|
+
: undefined,
|
|
174
|
+
metadata: safeMetadataConversion(serverConfig.metadata),
|
|
175
|
+
};
|
|
176
|
+
const result = await this.addServer(serverId, externalConfig);
|
|
177
|
+
if (result.success) {
|
|
178
|
+
serversLoaded++;
|
|
179
|
+
mcpLogger.debug(`[ExternalServerManager] Successfully loaded MCP server: ${serverId}`);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
const error = `Failed to load server ${serverId}: ${result.error}`;
|
|
183
|
+
errors.push(error);
|
|
184
|
+
mcpLogger.warn(`[ExternalServerManager] ${error}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
const errorMsg = `Failed to load MCP server ${serverId}: ${error instanceof Error ? error.message : String(error)}`;
|
|
189
|
+
errors.push(errorMsg);
|
|
190
|
+
mcpLogger.warn(`[ExternalServerManager] ${errorMsg}`);
|
|
191
|
+
// Continue with other servers - don't let one failure break everything
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
mcpLogger.info(`[ExternalServerManager] MCP configuration loading complete: ${serversLoaded} servers loaded, ${errors.length} errors`);
|
|
195
|
+
return { serversLoaded, errors };
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
const errorMsg = `Failed to load MCP configuration: ${error instanceof Error ? error.message : String(error)}`;
|
|
199
|
+
mcpLogger.error(`[ExternalServerManager] ${errorMsg}`);
|
|
200
|
+
return { serversLoaded: 0, errors: [errorMsg] };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
49
203
|
/**
|
|
50
204
|
* Validate external MCP server configuration
|
|
51
205
|
*/
|
|
@@ -93,11 +247,42 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
93
247
|
};
|
|
94
248
|
}
|
|
95
249
|
/**
|
|
96
|
-
*
|
|
250
|
+
* Convert MCPServerInfo format (keeping for backward compatibility)
|
|
251
|
+
* Helper function for transitioning to zero-conversion architecture
|
|
97
252
|
*/
|
|
98
|
-
|
|
253
|
+
convertConfigToMCPServerInfo(serverId, config) {
|
|
254
|
+
return {
|
|
255
|
+
id: serverId,
|
|
256
|
+
name: String(config.metadata?.title || serverId),
|
|
257
|
+
description: `External MCP server (${config.transport})`,
|
|
258
|
+
status: "initializing",
|
|
259
|
+
transport: config.transport,
|
|
260
|
+
command: config.command,
|
|
261
|
+
args: config.args,
|
|
262
|
+
env: config.env,
|
|
263
|
+
tools: [], // Will be populated after server connection
|
|
264
|
+
metadata: {
|
|
265
|
+
category: "external",
|
|
266
|
+
// Store additional ExternalMCPServerConfig fields in metadata
|
|
267
|
+
timeout: config.timeout,
|
|
268
|
+
retries: config.retries,
|
|
269
|
+
healthCheckInterval: config.healthCheckInterval,
|
|
270
|
+
autoRestart: config.autoRestart,
|
|
271
|
+
cwd: config.cwd,
|
|
272
|
+
url: config.url,
|
|
273
|
+
...(safeMetadataConversion(config.metadata) || {}),
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
async addServer(serverId, configOrServerInfo) {
|
|
99
278
|
const startTime = Date.now();
|
|
100
279
|
try {
|
|
280
|
+
// Use MCPServerInfo directly (zero-conversion architecture)
|
|
281
|
+
const serverInfo = "transport" in configOrServerInfo &&
|
|
282
|
+
"command" in configOrServerInfo &&
|
|
283
|
+
!("tools" in configOrServerInfo)
|
|
284
|
+
? this.convertConfigToMCPServerInfo(serverId, configOrServerInfo)
|
|
285
|
+
: configOrServerInfo;
|
|
101
286
|
// Check server limit
|
|
102
287
|
if (this.servers.size >= this.config.maxServers) {
|
|
103
288
|
return {
|
|
@@ -107,8 +292,26 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
107
292
|
duration: Date.now() - startTime,
|
|
108
293
|
};
|
|
109
294
|
}
|
|
110
|
-
// Validate configuration
|
|
111
|
-
const
|
|
295
|
+
// Validate configuration (for backward compatibility, create temporary config)
|
|
296
|
+
const tempConfig = {
|
|
297
|
+
id: serverId,
|
|
298
|
+
name: serverInfo.name,
|
|
299
|
+
description: serverInfo.description,
|
|
300
|
+
transport: serverInfo.transport,
|
|
301
|
+
status: serverInfo.status,
|
|
302
|
+
tools: serverInfo.tools,
|
|
303
|
+
command: serverInfo.command || "",
|
|
304
|
+
args: serverInfo.args || [],
|
|
305
|
+
env: serverInfo.env || {},
|
|
306
|
+
timeout: serverInfo.metadata?.timeout,
|
|
307
|
+
retries: serverInfo.metadata?.retries,
|
|
308
|
+
healthCheckInterval: serverInfo.metadata?.healthCheckInterval,
|
|
309
|
+
autoRestart: serverInfo.metadata?.autoRestart,
|
|
310
|
+
cwd: serverInfo.metadata?.cwd,
|
|
311
|
+
url: serverInfo.metadata?.url,
|
|
312
|
+
metadata: safeMetadataConversion(serverInfo.metadata),
|
|
313
|
+
};
|
|
314
|
+
const validation = this.validateConfig(tempConfig);
|
|
112
315
|
if (!validation.isValid) {
|
|
113
316
|
return {
|
|
114
317
|
success: false,
|
|
@@ -127,19 +330,19 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
127
330
|
};
|
|
128
331
|
}
|
|
129
332
|
mcpLogger.info(`[ExternalServerManager] Adding server: ${serverId}`, {
|
|
130
|
-
command:
|
|
131
|
-
transport:
|
|
333
|
+
command: serverInfo.command,
|
|
334
|
+
transport: serverInfo.transport,
|
|
132
335
|
});
|
|
133
|
-
// Create server instance
|
|
336
|
+
// Create server instance as RuntimeMCPServerInfo (transition to zero-conversion)
|
|
134
337
|
const instance = {
|
|
135
|
-
|
|
338
|
+
...serverInfo,
|
|
136
339
|
process: null,
|
|
137
340
|
client: null,
|
|
138
|
-
|
|
341
|
+
transportInstance: null,
|
|
139
342
|
status: "initializing",
|
|
140
343
|
reconnectAttempts: 0,
|
|
141
344
|
maxReconnectAttempts: this.config.maxRestartAttempts,
|
|
142
|
-
|
|
345
|
+
toolsMap: new Map(),
|
|
143
346
|
metrics: {
|
|
144
347
|
totalConnections: 0,
|
|
145
348
|
totalDisconnections: 0,
|
|
@@ -148,21 +351,41 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
148
351
|
averageResponseTime: 0,
|
|
149
352
|
lastResponseTime: 0,
|
|
150
353
|
},
|
|
354
|
+
config: tempConfig,
|
|
151
355
|
};
|
|
152
356
|
// Store the instance
|
|
153
357
|
this.servers.set(serverId, instance);
|
|
154
358
|
// Start the server
|
|
155
359
|
await this.startServer(serverId);
|
|
156
360
|
const finalInstance = this.servers.get(serverId);
|
|
361
|
+
// Convert RuntimeMCPServerInfo to ExternalMCPServerInstance for return
|
|
362
|
+
const convertedInstance = {
|
|
363
|
+
config: finalInstance.config,
|
|
364
|
+
process: finalInstance.process,
|
|
365
|
+
client: finalInstance.client,
|
|
366
|
+
transport: finalInstance.transportInstance,
|
|
367
|
+
status: finalInstance.status,
|
|
368
|
+
lastError: finalInstance.lastError,
|
|
369
|
+
startTime: finalInstance.startTime,
|
|
370
|
+
lastHealthCheck: finalInstance.lastHealthCheck,
|
|
371
|
+
reconnectAttempts: finalInstance.reconnectAttempts,
|
|
372
|
+
maxReconnectAttempts: finalInstance.maxReconnectAttempts,
|
|
373
|
+
tools: finalInstance.toolsMap,
|
|
374
|
+
toolsArray: finalInstance.toolsArray,
|
|
375
|
+
capabilities: finalInstance.capabilities,
|
|
376
|
+
healthTimer: finalInstance.healthTimer,
|
|
377
|
+
restartTimer: finalInstance.restartTimer,
|
|
378
|
+
metrics: finalInstance.metrics,
|
|
379
|
+
};
|
|
157
380
|
return {
|
|
158
381
|
success: true,
|
|
159
|
-
data:
|
|
382
|
+
data: convertedInstance,
|
|
160
383
|
serverId,
|
|
161
384
|
duration: Date.now() - startTime,
|
|
162
385
|
metadata: {
|
|
163
386
|
timestamp: Date.now(),
|
|
164
387
|
operation: "addServer",
|
|
165
|
-
toolsDiscovered: finalInstance.tools.
|
|
388
|
+
toolsDiscovered: finalInstance.tools.length,
|
|
166
389
|
},
|
|
167
390
|
};
|
|
168
391
|
}
|
|
@@ -249,9 +472,9 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
249
472
|
}
|
|
250
473
|
// Store client components
|
|
251
474
|
instance.client = clientResult.client;
|
|
252
|
-
instance.
|
|
475
|
+
instance.transportInstance = clientResult.transport;
|
|
253
476
|
instance.process = clientResult.process || null;
|
|
254
|
-
instance.capabilities = clientResult.capabilities;
|
|
477
|
+
instance.capabilities = safeMetadataConversion(clientResult.capabilities);
|
|
255
478
|
instance.startTime = new Date();
|
|
256
479
|
instance.lastHealthCheck = new Date();
|
|
257
480
|
instance.metrics.totalConnections++;
|
|
@@ -279,12 +502,16 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
279
502
|
this.updateServerStatus(serverId, "connected");
|
|
280
503
|
// Discover tools from the server
|
|
281
504
|
await this.discoverServerTools(serverId);
|
|
505
|
+
// Register tools with main registry if integration is enabled
|
|
506
|
+
if (this.enableMainRegistryIntegration) {
|
|
507
|
+
await this.registerServerToolsWithMainRegistry(serverId);
|
|
508
|
+
}
|
|
282
509
|
// Start health monitoring
|
|
283
510
|
this.startHealthMonitoring(serverId);
|
|
284
511
|
// Emit connected event
|
|
285
512
|
this.emit("connected", {
|
|
286
513
|
serverId,
|
|
287
|
-
toolCount: instance.
|
|
514
|
+
toolCount: instance.toolsMap.size,
|
|
288
515
|
timestamp: new Date(),
|
|
289
516
|
});
|
|
290
517
|
mcpLogger.info(`[ExternalServerManager] Server started successfully: ${serverId}`);
|
|
@@ -316,18 +543,22 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
316
543
|
clearTimeout(instance.restartTimer);
|
|
317
544
|
instance.restartTimer = undefined;
|
|
318
545
|
}
|
|
546
|
+
// Unregister tools from main registry if integration is enabled
|
|
547
|
+
if (this.enableMainRegistryIntegration) {
|
|
548
|
+
this.unregisterServerToolsFromMainRegistry(serverId);
|
|
549
|
+
}
|
|
319
550
|
// Clear server tools from discovery service
|
|
320
551
|
this.toolDiscovery.clearServerTools(serverId);
|
|
321
552
|
// Close MCP client using factory cleanup
|
|
322
|
-
if (instance.client && instance.
|
|
553
|
+
if (instance.client && instance.transportInstance) {
|
|
323
554
|
try {
|
|
324
|
-
await MCPClientFactory.closeClient(instance.client, instance.
|
|
555
|
+
await MCPClientFactory.closeClient(instance.client, instance.transportInstance, instance.process || undefined);
|
|
325
556
|
}
|
|
326
557
|
catch (error) {
|
|
327
558
|
mcpLogger.debug(`[ExternalServerManager] Error closing client for ${serverId}:`, error);
|
|
328
559
|
}
|
|
329
560
|
instance.client = null;
|
|
330
|
-
instance.
|
|
561
|
+
instance.transportInstance = null;
|
|
331
562
|
instance.process = null;
|
|
332
563
|
}
|
|
333
564
|
this.updateServerStatus(serverId, "stopped");
|
|
@@ -347,7 +578,17 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
347
578
|
return;
|
|
348
579
|
}
|
|
349
580
|
const oldStatus = instance.status;
|
|
350
|
-
|
|
581
|
+
// Map ExternalMCPServerStatus to MCPServerInfo status
|
|
582
|
+
const mappedStatus = newStatus === "connecting" || newStatus === "restarting"
|
|
583
|
+
? "initializing"
|
|
584
|
+
: newStatus === "stopping" || newStatus === "stopped"
|
|
585
|
+
? "stopping"
|
|
586
|
+
: newStatus === "connected"
|
|
587
|
+
? "connected"
|
|
588
|
+
: newStatus === "disconnected"
|
|
589
|
+
? "disconnected"
|
|
590
|
+
: "failed";
|
|
591
|
+
instance.status = mappedStatus;
|
|
351
592
|
// Emit status change event
|
|
352
593
|
this.emit("statusChanged", {
|
|
353
594
|
serverId,
|
|
@@ -476,7 +717,7 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
476
717
|
status: instance.status,
|
|
477
718
|
checkedAt: new Date(),
|
|
478
719
|
responseTime,
|
|
479
|
-
toolCount: instance.
|
|
720
|
+
toolCount: instance.toolsMap.size,
|
|
480
721
|
issues,
|
|
481
722
|
performance: {
|
|
482
723
|
uptime: instance.startTime
|
|
@@ -502,16 +743,64 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
502
743
|
}
|
|
503
744
|
}
|
|
504
745
|
/**
|
|
505
|
-
* Get server instance
|
|
746
|
+
* Get server instance - converted to ExternalMCPServerInstance for compatibility
|
|
506
747
|
*/
|
|
507
748
|
getServer(serverId) {
|
|
508
|
-
|
|
749
|
+
const runtime = this.servers.get(serverId);
|
|
750
|
+
if (!runtime) {
|
|
751
|
+
return undefined;
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
config: runtime.config,
|
|
755
|
+
process: runtime.process,
|
|
756
|
+
client: runtime.client,
|
|
757
|
+
transport: runtime.transportInstance,
|
|
758
|
+
status: runtime.status,
|
|
759
|
+
lastError: runtime.lastError,
|
|
760
|
+
startTime: runtime.startTime,
|
|
761
|
+
lastHealthCheck: runtime.lastHealthCheck,
|
|
762
|
+
reconnectAttempts: runtime.reconnectAttempts,
|
|
763
|
+
maxReconnectAttempts: runtime.maxReconnectAttempts,
|
|
764
|
+
tools: runtime.toolsMap,
|
|
765
|
+
toolsArray: runtime.toolsArray,
|
|
766
|
+
capabilities: runtime.capabilities,
|
|
767
|
+
healthTimer: runtime.healthTimer,
|
|
768
|
+
restartTimer: runtime.restartTimer,
|
|
769
|
+
metrics: runtime.metrics,
|
|
770
|
+
};
|
|
509
771
|
}
|
|
510
772
|
/**
|
|
511
|
-
* Get all servers
|
|
773
|
+
* Get all servers - converted to ExternalMCPServerInstance for compatibility
|
|
512
774
|
*/
|
|
513
775
|
getAllServers() {
|
|
514
|
-
|
|
776
|
+
const converted = new Map();
|
|
777
|
+
for (const [serverId, runtime] of this.servers.entries()) {
|
|
778
|
+
converted.set(serverId, {
|
|
779
|
+
config: runtime.config,
|
|
780
|
+
process: runtime.process,
|
|
781
|
+
client: runtime.client,
|
|
782
|
+
transport: runtime.transportInstance,
|
|
783
|
+
status: runtime.status,
|
|
784
|
+
lastError: runtime.lastError,
|
|
785
|
+
startTime: runtime.startTime,
|
|
786
|
+
lastHealthCheck: runtime.lastHealthCheck,
|
|
787
|
+
reconnectAttempts: runtime.reconnectAttempts,
|
|
788
|
+
maxReconnectAttempts: runtime.maxReconnectAttempts,
|
|
789
|
+
tools: runtime.toolsMap,
|
|
790
|
+
toolsArray: runtime.toolsArray,
|
|
791
|
+
capabilities: runtime.capabilities,
|
|
792
|
+
healthTimer: runtime.healthTimer,
|
|
793
|
+
restartTimer: runtime.restartTimer,
|
|
794
|
+
metrics: runtime.metrics,
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
return converted;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* List servers as MCPServerInfo - ZERO conversion needed
|
|
801
|
+
*/
|
|
802
|
+
listServers() {
|
|
803
|
+
return Array.from(this.servers.values());
|
|
515
804
|
}
|
|
516
805
|
/**
|
|
517
806
|
* Get server statuses
|
|
@@ -527,7 +816,7 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
527
816
|
isHealthy: instance.status === "connected",
|
|
528
817
|
status: instance.status,
|
|
529
818
|
checkedAt: instance.lastHealthCheck || new Date(),
|
|
530
|
-
toolCount: instance.
|
|
819
|
+
toolCount: instance.toolsMap.size,
|
|
531
820
|
issues: instance.lastError ? [instance.lastError] : [],
|
|
532
821
|
performance: {
|
|
533
822
|
uptime,
|
|
@@ -569,7 +858,7 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
569
858
|
else if (instance.status === "failed") {
|
|
570
859
|
failedServers++;
|
|
571
860
|
}
|
|
572
|
-
totalTools += instance.
|
|
861
|
+
totalTools += instance.toolsMap.size;
|
|
573
862
|
totalConnections += instance.metrics.totalConnections;
|
|
574
863
|
totalErrors += instance.metrics.totalErrors;
|
|
575
864
|
}
|
|
@@ -594,10 +883,16 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
594
883
|
mcpLogger.debug(`[ExternalServerManager] Discovering tools for server: ${serverId}`);
|
|
595
884
|
const discoveryResult = await this.toolDiscovery.discoverTools(serverId, instance.client, this.config.defaultTimeout);
|
|
596
885
|
if (discoveryResult.success) {
|
|
597
|
-
|
|
598
|
-
instance.
|
|
886
|
+
instance.toolsMap.clear();
|
|
887
|
+
instance.toolsArray = undefined;
|
|
888
|
+
instance.tools = [];
|
|
599
889
|
for (const tool of discoveryResult.tools) {
|
|
600
|
-
instance.
|
|
890
|
+
instance.toolsMap.set(tool.name, tool);
|
|
891
|
+
instance.tools.push({
|
|
892
|
+
name: tool.name,
|
|
893
|
+
description: tool.description,
|
|
894
|
+
inputSchema: tool.inputSchema,
|
|
895
|
+
});
|
|
601
896
|
}
|
|
602
897
|
mcpLogger.info(`[ExternalServerManager] Discovered ${discoveryResult.toolCount} tools for ${serverId}`);
|
|
603
898
|
}
|
|
@@ -609,6 +904,72 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
609
904
|
mcpLogger.error(`[ExternalServerManager] Tool discovery error for ${serverId}:`, error);
|
|
610
905
|
}
|
|
611
906
|
}
|
|
907
|
+
/**
|
|
908
|
+
* Register server tools with main tool registry for unified access
|
|
909
|
+
* This enables external MCP tools to be accessed via the main toolRegistry.executeTool()
|
|
910
|
+
*/
|
|
911
|
+
async registerServerToolsWithMainRegistry(serverId) {
|
|
912
|
+
const instance = this.servers.get(serverId);
|
|
913
|
+
if (!instance) {
|
|
914
|
+
throw new Error(`Server '${serverId}' not found`);
|
|
915
|
+
}
|
|
916
|
+
try {
|
|
917
|
+
mcpLogger.debug(`[ExternalServerManager] Registering ${instance.toolsMap.size} tools with main registry for server: ${serverId}`);
|
|
918
|
+
for (const [toolName, tool] of instance.toolsMap.entries()) {
|
|
919
|
+
const toolId = `${serverId}.${toolName}`;
|
|
920
|
+
const toolInfo = {
|
|
921
|
+
name: toolName,
|
|
922
|
+
description: tool.description || toolName,
|
|
923
|
+
inputSchema: tool.inputSchema || {},
|
|
924
|
+
serverId: serverId,
|
|
925
|
+
category: detectCategory({ isExternal: true, serverId }),
|
|
926
|
+
};
|
|
927
|
+
// Register with main tool registry
|
|
928
|
+
try {
|
|
929
|
+
toolRegistry.registerTool(toolId, toolInfo, {
|
|
930
|
+
execute: async (params, context) => {
|
|
931
|
+
// Execute tool via ExternalServerManager for proper lifecycle management
|
|
932
|
+
return await this.executeTool(serverId, toolName, params, { timeout: this.config.defaultTimeout });
|
|
933
|
+
},
|
|
934
|
+
});
|
|
935
|
+
mcpLogger.debug(`[ExternalServerManager] Registered tool with main registry: ${toolId}`);
|
|
936
|
+
}
|
|
937
|
+
catch (registrationError) {
|
|
938
|
+
mcpLogger.warn(`[ExternalServerManager] Failed to register tool ${toolId} with main registry:`, registrationError);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
mcpLogger.info(`[ExternalServerManager] Successfully registered ${instance.toolsMap.size} tools with main registry for ${serverId}`);
|
|
942
|
+
}
|
|
943
|
+
catch (error) {
|
|
944
|
+
mcpLogger.error(`[ExternalServerManager] Failed to register tools with main registry for ${serverId}:`, error);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Unregister server tools from main tool registry
|
|
949
|
+
*/
|
|
950
|
+
unregisterServerToolsFromMainRegistry(serverId) {
|
|
951
|
+
const instance = this.servers.get(serverId);
|
|
952
|
+
if (!instance || !this.enableMainRegistryIntegration) {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
try {
|
|
956
|
+
mcpLogger.debug(`[ExternalServerManager] Unregistering tools from main registry for server: ${serverId}`);
|
|
957
|
+
for (const [toolName] of instance.toolsMap.entries()) {
|
|
958
|
+
const toolId = `${serverId}.${toolName}`;
|
|
959
|
+
try {
|
|
960
|
+
toolRegistry.removeTool(toolId);
|
|
961
|
+
mcpLogger.debug(`[ExternalServerManager] Unregistered tool from main registry: ${toolId}`);
|
|
962
|
+
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
mcpLogger.debug(`[ExternalServerManager] Failed to unregister tool ${toolId}:`, error);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
mcpLogger.debug(`[ExternalServerManager] Completed unregistering tools from main registry for ${serverId}`);
|
|
968
|
+
}
|
|
969
|
+
catch (error) {
|
|
970
|
+
mcpLogger.error(`[ExternalServerManager] Error unregistering tools from main registry for ${serverId}:`, error);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
612
973
|
/**
|
|
613
974
|
* Execute a tool on a specific server
|
|
614
975
|
*/
|
|
@@ -7,7 +7,8 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
7
7
|
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
8
8
|
import type { ClientCapabilities } from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
import { ChildProcess } from "child_process";
|
|
10
|
-
import type {
|
|
10
|
+
import type { MCPTransportType } from "../types/externalMcp.js";
|
|
11
|
+
import type { MCPServerInfo } from "../types/mcpTypes.js";
|
|
11
12
|
/**
|
|
12
13
|
* MCP client creation result
|
|
13
14
|
*/
|
|
@@ -37,7 +38,7 @@ export declare class MCPClientFactory {
|
|
|
37
38
|
/**
|
|
38
39
|
* Create an MCP client for the given server configuration
|
|
39
40
|
*/
|
|
40
|
-
static createClient(config:
|
|
41
|
+
static createClient(config: MCPServerInfo, timeout?: number): Promise<MCPClientResult>;
|
|
41
42
|
/**
|
|
42
43
|
* Internal client creation logic
|
|
43
44
|
*/
|
|
@@ -81,7 +82,7 @@ export declare class MCPClientFactory {
|
|
|
81
82
|
/**
|
|
82
83
|
* Test connection to an MCP server
|
|
83
84
|
*/
|
|
84
|
-
static testConnection(config:
|
|
85
|
+
static testConnection(config: MCPServerInfo, timeout?: number): Promise<{
|
|
85
86
|
success: boolean;
|
|
86
87
|
error?: string;
|
|
87
88
|
capabilities?: ClientCapabilities;
|
|
@@ -89,7 +90,7 @@ export declare class MCPClientFactory {
|
|
|
89
90
|
/**
|
|
90
91
|
* Validate MCP server configuration for client creation
|
|
91
92
|
*/
|
|
92
|
-
static validateClientConfig(config:
|
|
93
|
+
static validateClientConfig(config: MCPServerInfo): {
|
|
93
94
|
isValid: boolean;
|
|
94
95
|
errors: string[];
|
|
95
96
|
};
|
|
@@ -133,8 +133,12 @@ export class MCPClientFactory {
|
|
|
133
133
|
command: config.command,
|
|
134
134
|
args: config.args,
|
|
135
135
|
});
|
|
136
|
+
// Validate command is present
|
|
137
|
+
if (!config.command) {
|
|
138
|
+
throw new Error(`Command is required for stdio transport`);
|
|
139
|
+
}
|
|
136
140
|
// Spawn the process
|
|
137
|
-
const childProcess = spawn(config.command, config.args, {
|
|
141
|
+
const childProcess = spawn(config.command, config.args || [], {
|
|
138
142
|
stdio: ["pipe", "pipe", "pipe"],
|
|
139
143
|
env: {
|
|
140
144
|
...process.env,
|
|
@@ -159,13 +163,14 @@ export class MCPClientFactory {
|
|
|
159
163
|
processErrorPromise,
|
|
160
164
|
]);
|
|
161
165
|
// Check if process is still running
|
|
162
|
-
if (childProcess.killed ||
|
|
166
|
+
if (childProcess.killed ||
|
|
167
|
+
childProcess.exitCode !== null) {
|
|
163
168
|
throw new Error("Process failed to start or exited immediately");
|
|
164
169
|
}
|
|
165
170
|
// Create transport
|
|
166
171
|
const transport = new StdioClientTransport({
|
|
167
172
|
command: config.command,
|
|
168
|
-
args: config.args,
|
|
173
|
+
args: config.args || [],
|
|
169
174
|
env: Object.fromEntries(Object.entries({
|
|
170
175
|
...process.env,
|
|
171
176
|
...config.env,
|
|
@@ -94,6 +94,7 @@ export interface ToolRegistryEvents {
|
|
|
94
94
|
* Handles automatic tool discovery and registration from external MCP servers
|
|
95
95
|
*/
|
|
96
96
|
export declare class ToolDiscoveryService extends EventEmitter {
|
|
97
|
+
private serverToolStorage;
|
|
97
98
|
private toolRegistry;
|
|
98
99
|
private serverTools;
|
|
99
100
|
private discoveryInProgress;
|