@juspay/neurolink 7.12.0 → 7.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +90 -25
- package/dist/config/conversationMemoryConfig.js +2 -1
- package/dist/context/ContextManager.d.ts +28 -0
- package/dist/context/ContextManager.js +113 -0
- package/dist/context/config.d.ts +5 -0
- package/dist/context/config.js +42 -0
- package/dist/context/types.d.ts +20 -0
- package/dist/context/types.js +1 -0
- package/dist/context/utils.d.ts +7 -0
- package/dist/context/utils.js +8 -0
- package/dist/core/baseProvider.d.ts +16 -1
- package/dist/core/baseProvider.js +208 -9
- package/dist/core/conversationMemoryManager.js +3 -2
- package/dist/core/factory.js +13 -2
- package/dist/factories/providerFactory.js +5 -11
- package/dist/factories/providerRegistry.js +2 -2
- package/dist/lib/config/conversationMemoryConfig.js +2 -1
- package/dist/lib/context/ContextManager.d.ts +28 -0
- package/dist/lib/context/ContextManager.js +113 -0
- package/dist/lib/context/config.d.ts +5 -0
- package/dist/lib/context/config.js +42 -0
- package/dist/lib/context/types.d.ts +20 -0
- package/dist/lib/context/types.js +1 -0
- package/dist/lib/context/utils.d.ts +7 -0
- package/dist/lib/context/utils.js +8 -0
- package/dist/lib/core/baseProvider.d.ts +16 -1
- package/dist/lib/core/baseProvider.js +208 -9
- package/dist/lib/core/conversationMemoryManager.js +3 -2
- package/dist/lib/core/factory.js +13 -2
- package/dist/lib/factories/providerFactory.js +5 -11
- package/dist/lib/factories/providerRegistry.js +2 -2
- package/dist/lib/mcp/externalServerManager.d.ts +115 -0
- package/dist/lib/mcp/externalServerManager.js +677 -0
- package/dist/lib/mcp/mcpCircuitBreaker.d.ts +184 -0
- package/dist/lib/mcp/mcpCircuitBreaker.js +338 -0
- package/dist/lib/mcp/mcpClientFactory.d.ts +104 -0
- package/dist/lib/mcp/mcpClientFactory.js +416 -0
- package/dist/lib/mcp/toolDiscoveryService.d.ts +192 -0
- package/dist/lib/mcp/toolDiscoveryService.js +578 -0
- package/dist/lib/neurolink.d.ts +128 -16
- package/dist/lib/neurolink.js +555 -49
- package/dist/lib/providers/googleVertex.d.ts +1 -1
- package/dist/lib/providers/googleVertex.js +23 -7
- package/dist/lib/types/externalMcp.d.ts +282 -0
- package/dist/lib/types/externalMcp.js +6 -0
- package/dist/lib/types/generateTypes.d.ts +0 -1
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/mcp/externalServerManager.d.ts +115 -0
- package/dist/mcp/externalServerManager.js +677 -0
- package/dist/mcp/mcpCircuitBreaker.d.ts +184 -0
- package/dist/mcp/mcpCircuitBreaker.js +338 -0
- package/dist/mcp/mcpClientFactory.d.ts +104 -0
- package/dist/mcp/mcpClientFactory.js +416 -0
- package/dist/mcp/toolDiscoveryService.d.ts +192 -0
- package/dist/mcp/toolDiscoveryService.js +578 -0
- package/dist/neurolink.d.ts +128 -16
- package/dist/neurolink.js +555 -49
- package/dist/providers/googleVertex.d.ts +1 -1
- package/dist/providers/googleVertex.js +23 -7
- package/dist/types/externalMcp.d.ts +282 -0
- package/dist/types/externalMcp.js +6 -0
- package/dist/types/generateTypes.d.ts +0 -1
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/neurolink.js
CHANGED
|
@@ -29,13 +29,19 @@ import { ErrorFactory, NeuroLinkError, withTimeout, withRetry, isRetriableError,
|
|
|
29
29
|
import { EventEmitter } from "events";
|
|
30
30
|
import { ConversationMemoryManager } from "./core/conversationMemoryManager.js";
|
|
31
31
|
import { applyConversationMemoryDefaults, getConversationMessages, storeConversationTurn, } from "./utils/conversationMemoryUtils.js";
|
|
32
|
+
import { ExternalServerManager } from "./mcp/externalServerManager.js";
|
|
33
|
+
import { ContextManager } from "./context/ContextManager.js";
|
|
34
|
+
import { defaultContextConfig } from "./context/config.js";
|
|
32
35
|
// Core types imported from core/types.js
|
|
33
36
|
export class NeuroLink {
|
|
34
37
|
mcpInitialized = false;
|
|
35
38
|
emitter = new EventEmitter();
|
|
39
|
+
contextManager = null;
|
|
36
40
|
// Tool registration support
|
|
37
41
|
customTools = new Map();
|
|
38
42
|
inMemoryServers = new Map();
|
|
43
|
+
// External MCP server management
|
|
44
|
+
externalServerManager;
|
|
39
45
|
// Enhanced error handling support
|
|
40
46
|
toolCircuitBreakers = new Map();
|
|
41
47
|
toolExecutionMetrics = new Map();
|
|
@@ -70,6 +76,32 @@ export class NeuroLink {
|
|
|
70
76
|
maxTurnsPerSession: memoryConfig.maxTurnsPerSession,
|
|
71
77
|
});
|
|
72
78
|
}
|
|
79
|
+
// Initialize external server manager
|
|
80
|
+
this.externalServerManager = new ExternalServerManager({
|
|
81
|
+
maxServers: 20,
|
|
82
|
+
defaultTimeout: 15000,
|
|
83
|
+
enableAutoRestart: true,
|
|
84
|
+
enablePerformanceMonitoring: true,
|
|
85
|
+
});
|
|
86
|
+
// Forward external server events
|
|
87
|
+
this.externalServerManager.on("connected", (event) => {
|
|
88
|
+
this.emitter.emit("externalMCP:serverConnected", event);
|
|
89
|
+
});
|
|
90
|
+
this.externalServerManager.on("disconnected", (event) => {
|
|
91
|
+
this.emitter.emit("externalMCP:serverDisconnected", event);
|
|
92
|
+
});
|
|
93
|
+
this.externalServerManager.on("failed", (event) => {
|
|
94
|
+
this.emitter.emit("externalMCP:serverFailed", event);
|
|
95
|
+
});
|
|
96
|
+
this.externalServerManager.on("toolDiscovered", (event) => {
|
|
97
|
+
this.emitter.emit("externalMCP:toolDiscovered", event);
|
|
98
|
+
// Tools are already registered on server connection, no need to duplicate here
|
|
99
|
+
});
|
|
100
|
+
this.externalServerManager.on("toolRemoved", (event) => {
|
|
101
|
+
this.emitter.emit("externalMCP:toolRemoved", event);
|
|
102
|
+
// Unregister removed tools from main tool registry
|
|
103
|
+
this.unregisterExternalMCPToolFromRegistry(event.toolName);
|
|
104
|
+
});
|
|
73
105
|
}
|
|
74
106
|
/**
|
|
75
107
|
* Initialize MCP registry with enhanced error handling and resource cleanup
|
|
@@ -120,7 +152,35 @@ export class NeuroLink {
|
|
|
120
152
|
* MAIN ENTRY POINT: Enhanced generate method with new function signature
|
|
121
153
|
* Replaces both generateText and legacy methods
|
|
122
154
|
*/
|
|
155
|
+
/**
|
|
156
|
+
* Extracts the original prompt text from the provided input.
|
|
157
|
+
* If a string is provided, it returns the string directly.
|
|
158
|
+
* If a GenerateOptions object is provided, it returns the input text from the object.
|
|
159
|
+
* @param optionsOrPrompt The prompt input, either as a string or a GenerateOptions object.
|
|
160
|
+
* @returns The original prompt text as a string.
|
|
161
|
+
*/
|
|
162
|
+
_extractOriginalPrompt(optionsOrPrompt) {
|
|
163
|
+
return typeof optionsOrPrompt === "string"
|
|
164
|
+
? optionsOrPrompt
|
|
165
|
+
: optionsOrPrompt.input.text;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Enables automatic context summarization for the NeuroLink instance.
|
|
169
|
+
* Once enabled, the instance will maintain conversation history and
|
|
170
|
+
* automatically summarize it when it exceeds token limits.
|
|
171
|
+
* @param config Optional configuration to override default summarization settings.
|
|
172
|
+
*/
|
|
173
|
+
enableContextSummarization(config) {
|
|
174
|
+
const contextConfig = {
|
|
175
|
+
...defaultContextConfig,
|
|
176
|
+
...config,
|
|
177
|
+
};
|
|
178
|
+
// Pass the internal generator function directly, bound to the correct `this` context.
|
|
179
|
+
this.contextManager = new ContextManager(this.generateTextInternal.bind(this), contextConfig);
|
|
180
|
+
logger.info("[NeuroLink] Automatic context summarization enabled.");
|
|
181
|
+
}
|
|
123
182
|
async generate(optionsOrPrompt) {
|
|
183
|
+
const originalPrompt = this._extractOriginalPrompt(optionsOrPrompt);
|
|
124
184
|
// Convert string prompt to full options
|
|
125
185
|
const options = typeof optionsOrPrompt === "string"
|
|
126
186
|
? { input: { text: optionsOrPrompt } }
|
|
@@ -129,6 +189,11 @@ export class NeuroLink {
|
|
|
129
189
|
if (!options.input?.text || typeof options.input.text !== "string") {
|
|
130
190
|
throw new Error("Input text is required and must be a non-empty string");
|
|
131
191
|
}
|
|
192
|
+
// Handle Context Management if enabled
|
|
193
|
+
if (this.contextManager) {
|
|
194
|
+
// Get the full context for the prompt without permanently adding the user's turn yet
|
|
195
|
+
options.input.text = this.contextManager.getContextForPrompt("user", options.input.text);
|
|
196
|
+
}
|
|
132
197
|
const startTime = Date.now();
|
|
133
198
|
// Emit generation start event
|
|
134
199
|
this.emitter.emit("generation:start", {
|
|
@@ -240,6 +305,11 @@ export class NeuroLink {
|
|
|
240
305
|
}
|
|
241
306
|
: undefined,
|
|
242
307
|
};
|
|
308
|
+
// Add both the user's turn and the AI's response to the permanent history
|
|
309
|
+
if (this.contextManager) {
|
|
310
|
+
await this.contextManager.addTurn("user", originalPrompt);
|
|
311
|
+
await this.contextManager.addTurn("assistant", generateResult.content);
|
|
312
|
+
}
|
|
243
313
|
return generateResult;
|
|
244
314
|
}
|
|
245
315
|
/**
|
|
@@ -282,6 +352,7 @@ export class NeuroLink {
|
|
|
282
352
|
// Try MCP-enhanced generation first (if not explicitly disabled)
|
|
283
353
|
if (!options.disableTools) {
|
|
284
354
|
try {
|
|
355
|
+
logger.debug(`[${functionTag}] Attempting MCP generation...`);
|
|
285
356
|
const mcpResult = await this.tryMCPGeneration(options);
|
|
286
357
|
if (mcpResult && mcpResult.content) {
|
|
287
358
|
logger.debug(`[${functionTag}] MCP generation successful`);
|
|
@@ -289,6 +360,13 @@ export class NeuroLink {
|
|
|
289
360
|
await storeConversationTurn(this.conversationMemory, options, mcpResult);
|
|
290
361
|
return mcpResult;
|
|
291
362
|
}
|
|
363
|
+
else {
|
|
364
|
+
logger.debug(`[${functionTag}] MCP generation returned empty result:`, {
|
|
365
|
+
hasResult: !!mcpResult,
|
|
366
|
+
hasContent: !!(mcpResult && mcpResult.content),
|
|
367
|
+
contentLength: mcpResult?.content?.length || 0,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
292
370
|
}
|
|
293
371
|
catch (error) {
|
|
294
372
|
logger.debug(`[${functionTag}] MCP generation failed, falling back`, {
|
|
@@ -328,34 +406,7 @@ export class NeuroLink {
|
|
|
328
406
|
? await getBestProvider()
|
|
329
407
|
: options.provider;
|
|
330
408
|
// Get available tools
|
|
331
|
-
|
|
332
|
-
try {
|
|
333
|
-
// 1. Get MCP server tools (existing functionality)
|
|
334
|
-
const mcpTools = await toolRegistry.listTools();
|
|
335
|
-
const mappedMcpTools = mcpTools.map((tool) => ({
|
|
336
|
-
name: tool.name || "Unknown",
|
|
337
|
-
description: tool.description || "No description available",
|
|
338
|
-
server: tool.serverId || "Unknown", // Fix: use serverId instead of server
|
|
339
|
-
category: tool.category,
|
|
340
|
-
}));
|
|
341
|
-
// 2. ✅ NEW: Get custom tools from this NeuroLink instance
|
|
342
|
-
const customTools = Array.from(this.customTools.entries()).map(([name, tool]) => ({
|
|
343
|
-
name,
|
|
344
|
-
description: tool.description || "Custom tool",
|
|
345
|
-
server: "custom",
|
|
346
|
-
category: "user-defined",
|
|
347
|
-
}));
|
|
348
|
-
// 3. ✅ NEW: Combine all tools for AI generation
|
|
349
|
-
availableTools = [...mappedMcpTools, ...customTools];
|
|
350
|
-
logger.debug(`[${functionTag}] Available tools for AI generation:`, {
|
|
351
|
-
mcpTools: mappedMcpTools.length,
|
|
352
|
-
customTools: customTools.length,
|
|
353
|
-
total: availableTools.length,
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
catch (error) {
|
|
357
|
-
mcpLogger.warn(`[${functionTag}] Failed to get tools`, { error });
|
|
358
|
-
}
|
|
409
|
+
const availableTools = await this.getAllAvailableTools();
|
|
359
410
|
// Create tool-aware system prompt
|
|
360
411
|
const enhancedSystemPrompt = this.createToolAwareSystemPrompt(options.systemPrompt, availableTools);
|
|
361
412
|
// Get conversation messages for context
|
|
@@ -378,7 +429,7 @@ export class NeuroLink {
|
|
|
378
429
|
if (!result || !result.content || result.content.trim().length === 0) {
|
|
379
430
|
return null; // Let caller fall back to direct generation
|
|
380
431
|
}
|
|
381
|
-
// Return enhanced result
|
|
432
|
+
// Return enhanced result with external tool information
|
|
382
433
|
return {
|
|
383
434
|
content: result.content,
|
|
384
435
|
provider: providerName,
|
|
@@ -393,9 +444,14 @@ export class NeuroLink {
|
|
|
393
444
|
success: true, // Assume success if tool executed (AI providers handle failures differently)
|
|
394
445
|
serverId: teRecord.serverId || undefined,
|
|
395
446
|
};
|
|
396
|
-
}) || [],
|
|
447
|
+
}) || [],
|
|
397
448
|
enhancedWithTools: true,
|
|
398
|
-
availableTools: availableTools.
|
|
449
|
+
availableTools: availableTools.map((tool) => ({
|
|
450
|
+
name: tool.name,
|
|
451
|
+
description: tool.description,
|
|
452
|
+
server: tool.server,
|
|
453
|
+
category: tool.category,
|
|
454
|
+
})),
|
|
399
455
|
// Include analytics and evaluation from BaseProvider
|
|
400
456
|
analytics: result.analytics,
|
|
401
457
|
evaluation: result.evaluation,
|
|
@@ -498,8 +554,23 @@ export class NeuroLink {
|
|
|
498
554
|
return originalSystemPrompt || "";
|
|
499
555
|
}
|
|
500
556
|
const toolDescriptions = availableTools
|
|
501
|
-
.map((tool) =>
|
|
502
|
-
|
|
557
|
+
.map((tool) => {
|
|
558
|
+
const toolWithSchema = tool;
|
|
559
|
+
const schema = (toolWithSchema.inputSchema ||
|
|
560
|
+
toolWithSchema.parameters);
|
|
561
|
+
let params = "";
|
|
562
|
+
if (schema && schema.properties) {
|
|
563
|
+
const requiredParams = new Set(schema.required || []);
|
|
564
|
+
params = Object.entries(schema.properties)
|
|
565
|
+
.map(([key, value]) => {
|
|
566
|
+
const required = requiredParams.has(key) ? " (required)" : "";
|
|
567
|
+
return ` - ${key}: ${value.type}${required}`;
|
|
568
|
+
})
|
|
569
|
+
.join("\n");
|
|
570
|
+
}
|
|
571
|
+
return `- ${tool.name}: ${tool.description} (from ${tool.server})\n${params}`;
|
|
572
|
+
})
|
|
573
|
+
.join("\n\n");
|
|
503
574
|
const toolPrompt = `\n\nYou have access to these additional tools if needed:\n${toolDescriptions}\n\nIMPORTANT: You are a general-purpose AI assistant. Answer all requests directly and creatively. These tools are optional helpers - use them only when they would genuinely improve your response. For creative tasks like storytelling, writing, or general conversation, respond naturally without requiring tools.`;
|
|
504
575
|
return (originalSystemPrompt || "") + toolPrompt;
|
|
505
576
|
}
|
|
@@ -873,6 +944,14 @@ export class NeuroLink {
|
|
|
873
944
|
async executeTool(toolName, params = {}, options) {
|
|
874
945
|
const functionTag = "NeuroLink.executeTool";
|
|
875
946
|
const executionStartTime = Date.now();
|
|
947
|
+
// Debug: Log tool execution attempt
|
|
948
|
+
logger.debug(`[${functionTag}] Tool execution requested:`, {
|
|
949
|
+
toolName,
|
|
950
|
+
params: typeof params === "object"
|
|
951
|
+
? Object.keys(params).length + " params"
|
|
952
|
+
: params,
|
|
953
|
+
hasExternalManager: !!this.externalServerManager,
|
|
954
|
+
});
|
|
876
955
|
// Emit tool start event
|
|
877
956
|
this.emitter.emit("tool:start", {
|
|
878
957
|
toolName,
|
|
@@ -1065,7 +1144,37 @@ export class NeuroLink {
|
|
|
1065
1144
|
}
|
|
1066
1145
|
}
|
|
1067
1146
|
}
|
|
1068
|
-
//
|
|
1147
|
+
// Check external MCP servers
|
|
1148
|
+
const externalTools = this.externalServerManager.getAllTools();
|
|
1149
|
+
const externalTool = externalTools.find((tool) => tool.name === toolName);
|
|
1150
|
+
logger.debug(`[${functionTag}] External MCP tool search:`, {
|
|
1151
|
+
toolName,
|
|
1152
|
+
externalToolsCount: externalTools.length,
|
|
1153
|
+
foundTool: !!externalTool,
|
|
1154
|
+
isAvailable: externalTool?.isAvailable,
|
|
1155
|
+
serverId: externalTool?.serverId,
|
|
1156
|
+
});
|
|
1157
|
+
if (externalTool && externalTool.isAvailable) {
|
|
1158
|
+
try {
|
|
1159
|
+
mcpLogger.debug(`[${functionTag}] Executing external MCP tool: ${toolName} from ${externalTool.serverId}`);
|
|
1160
|
+
const result = await this.externalServerManager.executeTool(externalTool.serverId, toolName, params, { timeout: options.timeout });
|
|
1161
|
+
logger.debug(`[${functionTag}] External MCP tool execution successful:`, {
|
|
1162
|
+
toolName,
|
|
1163
|
+
serverId: externalTool.serverId,
|
|
1164
|
+
resultType: typeof result,
|
|
1165
|
+
});
|
|
1166
|
+
return result;
|
|
1167
|
+
}
|
|
1168
|
+
catch (error) {
|
|
1169
|
+
logger.error(`[${functionTag}] External MCP tool execution failed:`, {
|
|
1170
|
+
toolName,
|
|
1171
|
+
serverId: externalTool.serverId,
|
|
1172
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1173
|
+
});
|
|
1174
|
+
throw ErrorFactory.toolExecutionFailed(toolName, error instanceof Error ? error : new Error(String(error)), externalTool.serverId);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
// If not found in custom tools, in-memory servers, or external servers, try unified registry
|
|
1069
1178
|
try {
|
|
1070
1179
|
const context = {
|
|
1071
1180
|
sessionId: `neurolink-tool-${Date.now()}`,
|
|
@@ -1129,13 +1238,41 @@ export class NeuroLink {
|
|
|
1129
1238
|
}
|
|
1130
1239
|
}
|
|
1131
1240
|
}
|
|
1132
|
-
// 4.
|
|
1133
|
-
const
|
|
1241
|
+
// 4. Get external MCP tools
|
|
1242
|
+
const externalMCPTools = this.externalServerManager
|
|
1243
|
+
.getAllTools()
|
|
1244
|
+
.map((tool) => ({
|
|
1245
|
+
name: tool.name,
|
|
1246
|
+
toolName: tool.name,
|
|
1247
|
+
description: tool.description,
|
|
1248
|
+
serverId: tool.serverId,
|
|
1249
|
+
category: tool.metadata?.category || "external-mcp",
|
|
1250
|
+
inputSchema: tool.inputSchema || {},
|
|
1251
|
+
isAvailable: tool.isAvailable,
|
|
1252
|
+
stats: tool.stats,
|
|
1253
|
+
}));
|
|
1254
|
+
// 5. Combine all tools with deduplication by name
|
|
1255
|
+
const combinedTools = [
|
|
1256
|
+
...mcpTools,
|
|
1257
|
+
...customTools,
|
|
1258
|
+
...inMemoryTools,
|
|
1259
|
+
...externalMCPTools,
|
|
1260
|
+
];
|
|
1261
|
+
const uniqueTools = Array.from(combinedTools
|
|
1262
|
+
.reduce((map, tool) => {
|
|
1263
|
+
const key = tool.name;
|
|
1264
|
+
if (!map.has(key)) {
|
|
1265
|
+
map.set(key, tool);
|
|
1266
|
+
}
|
|
1267
|
+
return map;
|
|
1268
|
+
}, new Map())
|
|
1269
|
+
.values());
|
|
1134
1270
|
mcpLogger.debug("Tool discovery results", {
|
|
1135
1271
|
mcpTools: mcpTools.length,
|
|
1136
1272
|
customTools: customTools.length,
|
|
1137
1273
|
inMemoryTools: inMemoryTools.length,
|
|
1138
|
-
|
|
1274
|
+
externalMCPTools: externalMCPTools.length,
|
|
1275
|
+
total: uniqueTools.length,
|
|
1139
1276
|
});
|
|
1140
1277
|
// Check memory usage after tool enumeration
|
|
1141
1278
|
const endMemory = MemoryManager.getMemoryUsageMB();
|
|
@@ -1143,11 +1280,11 @@ export class NeuroLink {
|
|
|
1143
1280
|
if (memoryDelta > 10) {
|
|
1144
1281
|
mcpLogger.debug(`🔍 Tool listing used ${memoryDelta}MB memory (large tool registry detected)`);
|
|
1145
1282
|
// Suggest periodic cleanup for large tool registries
|
|
1146
|
-
if (
|
|
1283
|
+
if (uniqueTools.length > 100) {
|
|
1147
1284
|
mcpLogger.debug("💡 Suggestion: Consider using tool categories or lazy loading for large tool sets");
|
|
1148
1285
|
}
|
|
1149
1286
|
}
|
|
1150
|
-
return
|
|
1287
|
+
return uniqueTools;
|
|
1151
1288
|
}
|
|
1152
1289
|
catch (error) {
|
|
1153
1290
|
mcpLogger.error("Failed to list available tools", { error });
|
|
@@ -1355,17 +1492,44 @@ export class NeuroLink {
|
|
|
1355
1492
|
*/
|
|
1356
1493
|
async getMCPStatus() {
|
|
1357
1494
|
try {
|
|
1358
|
-
//
|
|
1495
|
+
// Get built-in tools
|
|
1359
1496
|
const allTools = await toolRegistry.listTools();
|
|
1497
|
+
// Get external MCP server statistics
|
|
1498
|
+
const externalStats = this.externalServerManager.getStatistics();
|
|
1499
|
+
const externalServers = this.listExternalMCPServers();
|
|
1500
|
+
// Calculate totals
|
|
1501
|
+
const totalServers = 1 + externalStats.totalServers + this.inMemoryServers.size; // toolRegistry + external + in-memory
|
|
1502
|
+
const availableServers = 1 + externalStats.connectedServers; // toolRegistry always available + connected external
|
|
1503
|
+
const totalTools = allTools.length + externalStats.totalTools + this.customTools.size;
|
|
1504
|
+
// Convert external servers to MCPServerInfo format
|
|
1505
|
+
const externalMCPServers = externalServers.map((server) => ({
|
|
1506
|
+
id: server.serverId,
|
|
1507
|
+
name: typeof server.config.metadata?.title === "string"
|
|
1508
|
+
? server.config.metadata.title
|
|
1509
|
+
: server.serverId,
|
|
1510
|
+
source: `external-${server.config.transport}`,
|
|
1511
|
+
status: server.status,
|
|
1512
|
+
hasServer: server.isHealthy,
|
|
1513
|
+
metadata: {
|
|
1514
|
+
transport: server.config.transport,
|
|
1515
|
+
command: server.config.command,
|
|
1516
|
+
toolCount: server.toolCount,
|
|
1517
|
+
uptime: server.uptime,
|
|
1518
|
+
},
|
|
1519
|
+
}));
|
|
1360
1520
|
return {
|
|
1361
1521
|
mcpInitialized: this.mcpInitialized,
|
|
1362
|
-
totalServers
|
|
1363
|
-
availableServers
|
|
1364
|
-
autoDiscoveredCount: 0, // No auto-discovery
|
|
1365
|
-
totalTools
|
|
1366
|
-
autoDiscoveredServers: [],
|
|
1522
|
+
totalServers,
|
|
1523
|
+
availableServers,
|
|
1524
|
+
autoDiscoveredCount: 0, // No auto-discovery for external servers
|
|
1525
|
+
totalTools,
|
|
1526
|
+
autoDiscoveredServers: [],
|
|
1367
1527
|
customToolsCount: this.customTools.size,
|
|
1368
1528
|
inMemoryServersCount: this.inMemoryServers.size,
|
|
1529
|
+
externalMCPServersCount: externalStats.totalServers,
|
|
1530
|
+
externalMCPConnectedCount: externalStats.connectedServers,
|
|
1531
|
+
externalMCPFailedCount: externalStats.failedServers,
|
|
1532
|
+
externalMCPServers,
|
|
1369
1533
|
};
|
|
1370
1534
|
}
|
|
1371
1535
|
catch (error) {
|
|
@@ -1378,6 +1542,10 @@ export class NeuroLink {
|
|
|
1378
1542
|
autoDiscoveredServers: [],
|
|
1379
1543
|
customToolsCount: this.customTools.size,
|
|
1380
1544
|
inMemoryServersCount: this.inMemoryServers.size,
|
|
1545
|
+
externalMCPServersCount: 0,
|
|
1546
|
+
externalMCPConnectedCount: 0,
|
|
1547
|
+
externalMCPFailedCount: 0,
|
|
1548
|
+
externalMCPServers: [],
|
|
1381
1549
|
error: error instanceof Error ? error.message : String(error),
|
|
1382
1550
|
};
|
|
1383
1551
|
}
|
|
@@ -1387,8 +1555,54 @@ export class NeuroLink {
|
|
|
1387
1555
|
* @returns Promise resolving to array of MCP server information
|
|
1388
1556
|
*/
|
|
1389
1557
|
async listMCPServers() {
|
|
1390
|
-
|
|
1391
|
-
|
|
1558
|
+
const servers = [];
|
|
1559
|
+
// Add built-in toolRegistry as a server
|
|
1560
|
+
servers.push({
|
|
1561
|
+
id: "neurolink-direct",
|
|
1562
|
+
name: "NeuroLink Built-in Tools",
|
|
1563
|
+
source: "built-in",
|
|
1564
|
+
status: "connected",
|
|
1565
|
+
hasServer: true,
|
|
1566
|
+
metadata: {
|
|
1567
|
+
type: "direct-tools",
|
|
1568
|
+
description: "Built-in NeuroLink tools (getCurrentTime, readFile, etc.)",
|
|
1569
|
+
},
|
|
1570
|
+
});
|
|
1571
|
+
// Add in-memory servers
|
|
1572
|
+
for (const [serverId, serverConfig] of this.inMemoryServers.entries()) {
|
|
1573
|
+
servers.push({
|
|
1574
|
+
id: serverId,
|
|
1575
|
+
name: serverConfig.server.title || serverId,
|
|
1576
|
+
source: "in-memory",
|
|
1577
|
+
status: "connected",
|
|
1578
|
+
hasServer: true,
|
|
1579
|
+
metadata: {
|
|
1580
|
+
category: serverConfig.category,
|
|
1581
|
+
provider: serverConfig.metadata?.provider,
|
|
1582
|
+
version: serverConfig.metadata?.version,
|
|
1583
|
+
},
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
// Add external MCP servers
|
|
1587
|
+
const externalServers = this.listExternalMCPServers();
|
|
1588
|
+
for (const server of externalServers) {
|
|
1589
|
+
servers.push({
|
|
1590
|
+
id: server.serverId,
|
|
1591
|
+
name: typeof server.config.metadata?.title === "string"
|
|
1592
|
+
? server.config.metadata.title
|
|
1593
|
+
: server.serverId,
|
|
1594
|
+
source: `external-${server.config.transport}`,
|
|
1595
|
+
status: server.status,
|
|
1596
|
+
hasServer: server.isHealthy,
|
|
1597
|
+
metadata: {
|
|
1598
|
+
transport: server.config.transport,
|
|
1599
|
+
command: server.config.command,
|
|
1600
|
+
toolCount: server.toolCount,
|
|
1601
|
+
uptime: server.uptime,
|
|
1602
|
+
},
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
return servers;
|
|
1392
1606
|
}
|
|
1393
1607
|
/**
|
|
1394
1608
|
* Test connectivity to a specific MCP server
|
|
@@ -1396,8 +1610,29 @@ export class NeuroLink {
|
|
|
1396
1610
|
* @returns Promise resolving to true if server is reachable
|
|
1397
1611
|
*/
|
|
1398
1612
|
async testMCPServer(serverId) {
|
|
1399
|
-
|
|
1400
|
-
|
|
1613
|
+
try {
|
|
1614
|
+
// Test built-in tools
|
|
1615
|
+
if (serverId === "neurolink-direct") {
|
|
1616
|
+
const tools = await toolRegistry.listTools();
|
|
1617
|
+
return tools.length > 0;
|
|
1618
|
+
}
|
|
1619
|
+
// Test in-memory servers
|
|
1620
|
+
if (this.inMemoryServers.has(serverId)) {
|
|
1621
|
+
const serverConfig = this.inMemoryServers.get(serverId);
|
|
1622
|
+
return !!(serverConfig.server && serverConfig.server.tools);
|
|
1623
|
+
}
|
|
1624
|
+
// Test external MCP servers
|
|
1625
|
+
const externalServer = this.externalServerManager.getServer(serverId);
|
|
1626
|
+
if (externalServer) {
|
|
1627
|
+
return (externalServer.status === "connected" &&
|
|
1628
|
+
externalServer.client !== null);
|
|
1629
|
+
}
|
|
1630
|
+
return false;
|
|
1631
|
+
}
|
|
1632
|
+
catch (error) {
|
|
1633
|
+
mcpLogger.error(`[NeuroLink] Error testing MCP server ${serverId}:`, error);
|
|
1634
|
+
return false;
|
|
1635
|
+
}
|
|
1401
1636
|
}
|
|
1402
1637
|
// ==================== PROVIDER HEALTH CHECKING ====================
|
|
1403
1638
|
/**
|
|
@@ -1639,6 +1874,277 @@ export class NeuroLink {
|
|
|
1639
1874
|
}
|
|
1640
1875
|
await this.conversationMemory.clearAllSessions();
|
|
1641
1876
|
}
|
|
1877
|
+
// ===== EXTERNAL MCP SERVER METHODS =====
|
|
1878
|
+
/**
|
|
1879
|
+
* Add an external MCP server
|
|
1880
|
+
* Automatically discovers and registers tools from the server
|
|
1881
|
+
* @param serverId - Unique identifier for the server
|
|
1882
|
+
* @param config - External MCP server configuration
|
|
1883
|
+
* @returns Operation result with server instance
|
|
1884
|
+
*/
|
|
1885
|
+
async addExternalMCPServer(serverId, config) {
|
|
1886
|
+
try {
|
|
1887
|
+
mcpLogger.info(`[NeuroLink] Adding external MCP server: ${serverId}`, {
|
|
1888
|
+
command: config.command,
|
|
1889
|
+
transport: config.transport,
|
|
1890
|
+
});
|
|
1891
|
+
const result = await this.externalServerManager.addServer(serverId, config);
|
|
1892
|
+
if (result.success) {
|
|
1893
|
+
mcpLogger.info(`[NeuroLink] External MCP server added successfully: ${serverId}`, {
|
|
1894
|
+
toolsDiscovered: result.metadata?.toolsDiscovered || 0,
|
|
1895
|
+
duration: result.duration,
|
|
1896
|
+
});
|
|
1897
|
+
// Emit server added event
|
|
1898
|
+
this.emitter.emit("externalMCP:serverAdded", {
|
|
1899
|
+
serverId,
|
|
1900
|
+
config,
|
|
1901
|
+
toolCount: result.metadata?.toolsDiscovered || 0,
|
|
1902
|
+
timestamp: Date.now(),
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
else {
|
|
1906
|
+
mcpLogger.error(`[NeuroLink] Failed to add external MCP server: ${serverId}`, {
|
|
1907
|
+
error: result.error,
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
return result;
|
|
1911
|
+
}
|
|
1912
|
+
catch (error) {
|
|
1913
|
+
mcpLogger.error(`[NeuroLink] Error adding external MCP server: ${serverId}`, error);
|
|
1914
|
+
throw error;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Remove an external MCP server
|
|
1919
|
+
* Stops the server and removes all its tools
|
|
1920
|
+
* @param serverId - ID of the server to remove
|
|
1921
|
+
* @returns Operation result
|
|
1922
|
+
*/
|
|
1923
|
+
async removeExternalMCPServer(serverId) {
|
|
1924
|
+
try {
|
|
1925
|
+
mcpLogger.info(`[NeuroLink] Removing external MCP server: ${serverId}`);
|
|
1926
|
+
const result = await this.externalServerManager.removeServer(serverId);
|
|
1927
|
+
if (result.success) {
|
|
1928
|
+
mcpLogger.info(`[NeuroLink] External MCP server removed successfully: ${serverId}`);
|
|
1929
|
+
// Emit server removed event
|
|
1930
|
+
this.emitter.emit("externalMCP:serverRemoved", {
|
|
1931
|
+
serverId,
|
|
1932
|
+
timestamp: Date.now(),
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
else {
|
|
1936
|
+
mcpLogger.error(`[NeuroLink] Failed to remove external MCP server: ${serverId}`, {
|
|
1937
|
+
error: result.error,
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
return result;
|
|
1941
|
+
}
|
|
1942
|
+
catch (error) {
|
|
1943
|
+
mcpLogger.error(`[NeuroLink] Error removing external MCP server: ${serverId}`, error);
|
|
1944
|
+
throw error;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* List all external MCP servers
|
|
1949
|
+
* @returns Array of server health information
|
|
1950
|
+
*/
|
|
1951
|
+
listExternalMCPServers() {
|
|
1952
|
+
const serverStatuses = this.externalServerManager.getServerStatuses();
|
|
1953
|
+
const allServers = this.externalServerManager.getAllServers();
|
|
1954
|
+
return serverStatuses.map((health) => {
|
|
1955
|
+
const server = allServers.get(health.serverId);
|
|
1956
|
+
return {
|
|
1957
|
+
serverId: health.serverId,
|
|
1958
|
+
status: health.status,
|
|
1959
|
+
toolCount: health.toolCount,
|
|
1960
|
+
uptime: health.performance.uptime,
|
|
1961
|
+
isHealthy: health.isHealthy,
|
|
1962
|
+
config: server?.config || {},
|
|
1963
|
+
};
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Get external MCP server status
|
|
1968
|
+
* @param serverId - ID of the server
|
|
1969
|
+
* @returns Server instance or undefined if not found
|
|
1970
|
+
*/
|
|
1971
|
+
getExternalMCPServer(serverId) {
|
|
1972
|
+
return this.externalServerManager.getServer(serverId);
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Execute a tool from an external MCP server
|
|
1976
|
+
* @param serverId - ID of the server
|
|
1977
|
+
* @param toolName - Name of the tool
|
|
1978
|
+
* @param parameters - Tool parameters
|
|
1979
|
+
* @param options - Execution options
|
|
1980
|
+
* @returns Tool execution result
|
|
1981
|
+
*/
|
|
1982
|
+
async executeExternalMCPTool(serverId, toolName, parameters, options) {
|
|
1983
|
+
try {
|
|
1984
|
+
mcpLogger.debug(`[NeuroLink] Executing external MCP tool: ${toolName} on ${serverId}`);
|
|
1985
|
+
const result = await this.externalServerManager.executeTool(serverId, toolName, parameters, options);
|
|
1986
|
+
mcpLogger.debug(`[NeuroLink] External MCP tool executed successfully: ${toolName}`);
|
|
1987
|
+
return result;
|
|
1988
|
+
}
|
|
1989
|
+
catch (error) {
|
|
1990
|
+
mcpLogger.error(`[NeuroLink] External MCP tool execution failed: ${toolName}`, error);
|
|
1991
|
+
throw error;
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Get all tools from external MCP servers
|
|
1996
|
+
* @returns Array of external tool information
|
|
1997
|
+
*/
|
|
1998
|
+
getExternalMCPTools() {
|
|
1999
|
+
return this.externalServerManager.getAllTools();
|
|
2000
|
+
}
|
|
2001
|
+
/**
|
|
2002
|
+
* Get tools from a specific external MCP server
|
|
2003
|
+
* @param serverId - ID of the server
|
|
2004
|
+
* @returns Array of tool information for the server
|
|
2005
|
+
*/
|
|
2006
|
+
getExternalMCPServerTools(serverId) {
|
|
2007
|
+
return this.externalServerManager.getServerTools(serverId);
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Test connection to an external MCP server
|
|
2011
|
+
* @param config - Server configuration to test
|
|
2012
|
+
* @returns Test result with connection status
|
|
2013
|
+
*/
|
|
2014
|
+
async testExternalMCPConnection(config) {
|
|
2015
|
+
try {
|
|
2016
|
+
const { MCPClientFactory } = await import("./mcp/mcpClientFactory.js");
|
|
2017
|
+
const testResult = await MCPClientFactory.testConnection(config, 10000);
|
|
2018
|
+
return {
|
|
2019
|
+
success: testResult.success,
|
|
2020
|
+
error: testResult.error,
|
|
2021
|
+
toolCount: testResult.capabilities ? 1 : 0, // Basic indication
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
catch (error) {
|
|
2025
|
+
return {
|
|
2026
|
+
success: false,
|
|
2027
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Get external MCP server manager statistics
|
|
2033
|
+
* @returns Statistics about external servers and tools
|
|
2034
|
+
*/
|
|
2035
|
+
getExternalMCPStatistics() {
|
|
2036
|
+
return this.externalServerManager.getStatistics();
|
|
2037
|
+
}
|
|
2038
|
+
/**
|
|
2039
|
+
* Shutdown all external MCP servers
|
|
2040
|
+
* Called automatically on process exit
|
|
2041
|
+
*/
|
|
2042
|
+
async shutdownExternalMCPServers() {
|
|
2043
|
+
try {
|
|
2044
|
+
mcpLogger.info("[NeuroLink] Shutting down all external MCP servers...");
|
|
2045
|
+
// First, unregister all external MCP tools from the main tool registry
|
|
2046
|
+
this.unregisterAllExternalMCPToolsFromRegistry();
|
|
2047
|
+
// Then shutdown the external server manager
|
|
2048
|
+
await this.externalServerManager.shutdown();
|
|
2049
|
+
mcpLogger.info("[NeuroLink] All external MCP servers shut down successfully");
|
|
2050
|
+
}
|
|
2051
|
+
catch (error) {
|
|
2052
|
+
mcpLogger.error("[NeuroLink] Error shutting down external MCP servers:", error);
|
|
2053
|
+
throw error;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
/**
|
|
2057
|
+
* Convert external MCP tools to Vercel AI SDK tool format
|
|
2058
|
+
* This allows AI providers to use external tools directly
|
|
2059
|
+
*/
|
|
2060
|
+
convertExternalMCPToolsToAISDKFormat() {
|
|
2061
|
+
const externalTools = this.externalServerManager.getAllTools();
|
|
2062
|
+
const aiSDKTools = {};
|
|
2063
|
+
for (const tool of externalTools) {
|
|
2064
|
+
if (tool.isAvailable) {
|
|
2065
|
+
// Create tool definition without parameters schema to avoid Zod issues
|
|
2066
|
+
// The AI provider will handle parameters dynamically based on the tool description
|
|
2067
|
+
const toolDefinition = {
|
|
2068
|
+
description: tool.description,
|
|
2069
|
+
execute: async (args) => {
|
|
2070
|
+
try {
|
|
2071
|
+
mcpLogger.debug(`[NeuroLink] Executing external MCP tool via AI SDK: ${tool.name}`, { args });
|
|
2072
|
+
const result = await this.externalServerManager.executeTool(tool.serverId, tool.name, args, { timeout: 30000 });
|
|
2073
|
+
mcpLogger.debug(`[NeuroLink] External MCP tool execution result: ${tool.name}`, {
|
|
2074
|
+
success: !!result,
|
|
2075
|
+
hasData: !!(result &&
|
|
2076
|
+
typeof result === "object" &&
|
|
2077
|
+
"content" in result),
|
|
2078
|
+
});
|
|
2079
|
+
return result;
|
|
2080
|
+
}
|
|
2081
|
+
catch (error) {
|
|
2082
|
+
mcpLogger.error(`[NeuroLink] External MCP tool execution failed: ${tool.name}`, error);
|
|
2083
|
+
throw error;
|
|
2084
|
+
}
|
|
2085
|
+
},
|
|
2086
|
+
};
|
|
2087
|
+
// Only add parameters if we have a valid schema - otherwise omit it entirely
|
|
2088
|
+
// This prevents Zod schema parsing errors
|
|
2089
|
+
aiSDKTools[tool.name] = toolDefinition;
|
|
2090
|
+
mcpLogger.debug(`[NeuroLink] Converted external MCP tool to AI SDK format: ${tool.name} from server ${tool.serverId}`);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
mcpLogger.info(`[NeuroLink] Converted ${Object.keys(aiSDKTools).length} external MCP tools to AI SDK format`);
|
|
2094
|
+
return aiSDKTools;
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* Convert JSON Schema to AI SDK compatible format
|
|
2098
|
+
* For now, we'll skip schema validation and let the AI SDK handle parameters dynamically
|
|
2099
|
+
*/
|
|
2100
|
+
convertJSONSchemaToAISDKFormat(inputSchema) {
|
|
2101
|
+
// The simplest approach: don't provide parameters schema
|
|
2102
|
+
// This lets the AI SDK handle the tool without schema validation
|
|
2103
|
+
// Tools will still work, they just won't have strict parameter validation
|
|
2104
|
+
return undefined;
|
|
2105
|
+
}
|
|
2106
|
+
/**
|
|
2107
|
+
* Unregister external MCP tools from a specific server
|
|
2108
|
+
*/
|
|
2109
|
+
unregisterExternalMCPToolsFromRegistry(serverId) {
|
|
2110
|
+
try {
|
|
2111
|
+
const externalTools = this.externalServerManager.getServerTools(serverId);
|
|
2112
|
+
for (const tool of externalTools) {
|
|
2113
|
+
toolRegistry.removeTool(tool.name);
|
|
2114
|
+
mcpLogger.debug(`[NeuroLink] Unregistered external MCP tool from main registry: ${tool.name}`);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
catch (error) {
|
|
2118
|
+
mcpLogger.error(`[NeuroLink] Failed to unregister external MCP tools from registry for server ${serverId}:`, error);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
/**
|
|
2122
|
+
* Unregister a specific external MCP tool from the main registry
|
|
2123
|
+
*/
|
|
2124
|
+
unregisterExternalMCPToolFromRegistry(toolName) {
|
|
2125
|
+
try {
|
|
2126
|
+
toolRegistry.removeTool(toolName);
|
|
2127
|
+
mcpLogger.debug(`[NeuroLink] Unregistered external MCP tool from main registry: ${toolName}`);
|
|
2128
|
+
}
|
|
2129
|
+
catch (error) {
|
|
2130
|
+
mcpLogger.error(`[NeuroLink] Failed to unregister external MCP tool ${toolName} from registry:`, error);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
/**
|
|
2134
|
+
* Unregister all external MCP tools from the main registry
|
|
2135
|
+
*/
|
|
2136
|
+
unregisterAllExternalMCPToolsFromRegistry() {
|
|
2137
|
+
try {
|
|
2138
|
+
const externalTools = this.externalServerManager.getAllTools();
|
|
2139
|
+
for (const tool of externalTools) {
|
|
2140
|
+
toolRegistry.removeTool(tool.name);
|
|
2141
|
+
}
|
|
2142
|
+
mcpLogger.debug(`[NeuroLink] Unregistered ${externalTools.length} external MCP tools from main registry`);
|
|
2143
|
+
}
|
|
2144
|
+
catch (error) {
|
|
2145
|
+
mcpLogger.error("[NeuroLink] Failed to unregister all external MCP tools from registry:", error);
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
1642
2148
|
}
|
|
1643
2149
|
// Create default instance
|
|
1644
2150
|
export const neurolink = new NeuroLink();
|