@juspay/neurolink 7.13.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 +6 -0
- package/README.md +89 -25
- package/dist/config/conversationMemoryConfig.js +2 -1
- package/dist/context/ContextManager.js +15 -4
- package/dist/context/config.js +5 -1
- package/dist/context/utils.js +1 -1
- 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.js +15 -4
- package/dist/lib/context/config.js +5 -1
- package/dist/lib/context/utils.js +1 -1
- 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 +111 -16
- package/dist/lib/neurolink.js +517 -50
- 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 +111 -16
- package/dist/neurolink.js +517 -50
- 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/lib/neurolink.js
CHANGED
|
@@ -29,6 +29,7 @@ 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";
|
|
32
33
|
import { ContextManager } from "./context/ContextManager.js";
|
|
33
34
|
import { defaultContextConfig } from "./context/config.js";
|
|
34
35
|
// Core types imported from core/types.js
|
|
@@ -39,6 +40,8 @@ export class NeuroLink {
|
|
|
39
40
|
// Tool registration support
|
|
40
41
|
customTools = new Map();
|
|
41
42
|
inMemoryServers = new Map();
|
|
43
|
+
// External MCP server management
|
|
44
|
+
externalServerManager;
|
|
42
45
|
// Enhanced error handling support
|
|
43
46
|
toolCircuitBreakers = new Map();
|
|
44
47
|
toolExecutionMetrics = new Map();
|
|
@@ -73,6 +76,32 @@ export class NeuroLink {
|
|
|
73
76
|
maxTurnsPerSession: memoryConfig.maxTurnsPerSession,
|
|
74
77
|
});
|
|
75
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
|
+
});
|
|
76
105
|
}
|
|
77
106
|
/**
|
|
78
107
|
* Initialize MCP registry with enhanced error handling and resource cleanup
|
|
@@ -131,7 +160,9 @@ export class NeuroLink {
|
|
|
131
160
|
* @returns The original prompt text as a string.
|
|
132
161
|
*/
|
|
133
162
|
_extractOriginalPrompt(optionsOrPrompt) {
|
|
134
|
-
return typeof optionsOrPrompt ===
|
|
163
|
+
return typeof optionsOrPrompt === "string"
|
|
164
|
+
? optionsOrPrompt
|
|
165
|
+
: optionsOrPrompt.input.text;
|
|
135
166
|
}
|
|
136
167
|
/**
|
|
137
168
|
* Enables automatic context summarization for the NeuroLink instance.
|
|
@@ -321,6 +352,7 @@ export class NeuroLink {
|
|
|
321
352
|
// Try MCP-enhanced generation first (if not explicitly disabled)
|
|
322
353
|
if (!options.disableTools) {
|
|
323
354
|
try {
|
|
355
|
+
logger.debug(`[${functionTag}] Attempting MCP generation...`);
|
|
324
356
|
const mcpResult = await this.tryMCPGeneration(options);
|
|
325
357
|
if (mcpResult && mcpResult.content) {
|
|
326
358
|
logger.debug(`[${functionTag}] MCP generation successful`);
|
|
@@ -328,6 +360,13 @@ export class NeuroLink {
|
|
|
328
360
|
await storeConversationTurn(this.conversationMemory, options, mcpResult);
|
|
329
361
|
return mcpResult;
|
|
330
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
|
+
}
|
|
331
370
|
}
|
|
332
371
|
catch (error) {
|
|
333
372
|
logger.debug(`[${functionTag}] MCP generation failed, falling back`, {
|
|
@@ -367,34 +406,7 @@ export class NeuroLink {
|
|
|
367
406
|
? await getBestProvider()
|
|
368
407
|
: options.provider;
|
|
369
408
|
// Get available tools
|
|
370
|
-
|
|
371
|
-
try {
|
|
372
|
-
// 1. Get MCP server tools (existing functionality)
|
|
373
|
-
const mcpTools = await toolRegistry.listTools();
|
|
374
|
-
const mappedMcpTools = mcpTools.map((tool) => ({
|
|
375
|
-
name: tool.name || "Unknown",
|
|
376
|
-
description: tool.description || "No description available",
|
|
377
|
-
server: tool.serverId || "Unknown", // Fix: use serverId instead of server
|
|
378
|
-
category: tool.category,
|
|
379
|
-
}));
|
|
380
|
-
// 2. ✅ NEW: Get custom tools from this NeuroLink instance
|
|
381
|
-
const customTools = Array.from(this.customTools.entries()).map(([name, tool]) => ({
|
|
382
|
-
name,
|
|
383
|
-
description: tool.description || "Custom tool",
|
|
384
|
-
server: "custom",
|
|
385
|
-
category: "user-defined",
|
|
386
|
-
}));
|
|
387
|
-
// 3. ✅ NEW: Combine all tools for AI generation
|
|
388
|
-
availableTools = [...mappedMcpTools, ...customTools];
|
|
389
|
-
logger.debug(`[${functionTag}] Available tools for AI generation:`, {
|
|
390
|
-
mcpTools: mappedMcpTools.length,
|
|
391
|
-
customTools: customTools.length,
|
|
392
|
-
total: availableTools.length,
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
catch (error) {
|
|
396
|
-
mcpLogger.warn(`[${functionTag}] Failed to get tools`, { error });
|
|
397
|
-
}
|
|
409
|
+
const availableTools = await this.getAllAvailableTools();
|
|
398
410
|
// Create tool-aware system prompt
|
|
399
411
|
const enhancedSystemPrompt = this.createToolAwareSystemPrompt(options.systemPrompt, availableTools);
|
|
400
412
|
// Get conversation messages for context
|
|
@@ -417,7 +429,7 @@ export class NeuroLink {
|
|
|
417
429
|
if (!result || !result.content || result.content.trim().length === 0) {
|
|
418
430
|
return null; // Let caller fall back to direct generation
|
|
419
431
|
}
|
|
420
|
-
// Return enhanced result
|
|
432
|
+
// Return enhanced result with external tool information
|
|
421
433
|
return {
|
|
422
434
|
content: result.content,
|
|
423
435
|
provider: providerName,
|
|
@@ -432,9 +444,14 @@ export class NeuroLink {
|
|
|
432
444
|
success: true, // Assume success if tool executed (AI providers handle failures differently)
|
|
433
445
|
serverId: teRecord.serverId || undefined,
|
|
434
446
|
};
|
|
435
|
-
}) || [],
|
|
447
|
+
}) || [],
|
|
436
448
|
enhancedWithTools: true,
|
|
437
|
-
availableTools: availableTools.
|
|
449
|
+
availableTools: availableTools.map((tool) => ({
|
|
450
|
+
name: tool.name,
|
|
451
|
+
description: tool.description,
|
|
452
|
+
server: tool.server,
|
|
453
|
+
category: tool.category,
|
|
454
|
+
})),
|
|
438
455
|
// Include analytics and evaluation from BaseProvider
|
|
439
456
|
analytics: result.analytics,
|
|
440
457
|
evaluation: result.evaluation,
|
|
@@ -537,8 +554,23 @@ export class NeuroLink {
|
|
|
537
554
|
return originalSystemPrompt || "";
|
|
538
555
|
}
|
|
539
556
|
const toolDescriptions = availableTools
|
|
540
|
-
.map((tool) =>
|
|
541
|
-
|
|
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");
|
|
542
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.`;
|
|
543
575
|
return (originalSystemPrompt || "") + toolPrompt;
|
|
544
576
|
}
|
|
@@ -912,6 +944,14 @@ export class NeuroLink {
|
|
|
912
944
|
async executeTool(toolName, params = {}, options) {
|
|
913
945
|
const functionTag = "NeuroLink.executeTool";
|
|
914
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
|
+
});
|
|
915
955
|
// Emit tool start event
|
|
916
956
|
this.emitter.emit("tool:start", {
|
|
917
957
|
toolName,
|
|
@@ -1104,7 +1144,37 @@ export class NeuroLink {
|
|
|
1104
1144
|
}
|
|
1105
1145
|
}
|
|
1106
1146
|
}
|
|
1107
|
-
//
|
|
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
|
|
1108
1178
|
try {
|
|
1109
1179
|
const context = {
|
|
1110
1180
|
sessionId: `neurolink-tool-${Date.now()}`,
|
|
@@ -1168,13 +1238,41 @@ export class NeuroLink {
|
|
|
1168
1238
|
}
|
|
1169
1239
|
}
|
|
1170
1240
|
}
|
|
1171
|
-
// 4.
|
|
1172
|
-
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());
|
|
1173
1270
|
mcpLogger.debug("Tool discovery results", {
|
|
1174
1271
|
mcpTools: mcpTools.length,
|
|
1175
1272
|
customTools: customTools.length,
|
|
1176
1273
|
inMemoryTools: inMemoryTools.length,
|
|
1177
|
-
|
|
1274
|
+
externalMCPTools: externalMCPTools.length,
|
|
1275
|
+
total: uniqueTools.length,
|
|
1178
1276
|
});
|
|
1179
1277
|
// Check memory usage after tool enumeration
|
|
1180
1278
|
const endMemory = MemoryManager.getMemoryUsageMB();
|
|
@@ -1182,11 +1280,11 @@ export class NeuroLink {
|
|
|
1182
1280
|
if (memoryDelta > 10) {
|
|
1183
1281
|
mcpLogger.debug(`🔍 Tool listing used ${memoryDelta}MB memory (large tool registry detected)`);
|
|
1184
1282
|
// Suggest periodic cleanup for large tool registries
|
|
1185
|
-
if (
|
|
1283
|
+
if (uniqueTools.length > 100) {
|
|
1186
1284
|
mcpLogger.debug("💡 Suggestion: Consider using tool categories or lazy loading for large tool sets");
|
|
1187
1285
|
}
|
|
1188
1286
|
}
|
|
1189
|
-
return
|
|
1287
|
+
return uniqueTools;
|
|
1190
1288
|
}
|
|
1191
1289
|
catch (error) {
|
|
1192
1290
|
mcpLogger.error("Failed to list available tools", { error });
|
|
@@ -1394,17 +1492,44 @@ export class NeuroLink {
|
|
|
1394
1492
|
*/
|
|
1395
1493
|
async getMCPStatus() {
|
|
1396
1494
|
try {
|
|
1397
|
-
//
|
|
1495
|
+
// Get built-in tools
|
|
1398
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
|
+
}));
|
|
1399
1520
|
return {
|
|
1400
1521
|
mcpInitialized: this.mcpInitialized,
|
|
1401
|
-
totalServers
|
|
1402
|
-
availableServers
|
|
1403
|
-
autoDiscoveredCount: 0, // No auto-discovery
|
|
1404
|
-
totalTools
|
|
1405
|
-
autoDiscoveredServers: [],
|
|
1522
|
+
totalServers,
|
|
1523
|
+
availableServers,
|
|
1524
|
+
autoDiscoveredCount: 0, // No auto-discovery for external servers
|
|
1525
|
+
totalTools,
|
|
1526
|
+
autoDiscoveredServers: [],
|
|
1406
1527
|
customToolsCount: this.customTools.size,
|
|
1407
1528
|
inMemoryServersCount: this.inMemoryServers.size,
|
|
1529
|
+
externalMCPServersCount: externalStats.totalServers,
|
|
1530
|
+
externalMCPConnectedCount: externalStats.connectedServers,
|
|
1531
|
+
externalMCPFailedCount: externalStats.failedServers,
|
|
1532
|
+
externalMCPServers,
|
|
1408
1533
|
};
|
|
1409
1534
|
}
|
|
1410
1535
|
catch (error) {
|
|
@@ -1417,6 +1542,10 @@ export class NeuroLink {
|
|
|
1417
1542
|
autoDiscoveredServers: [],
|
|
1418
1543
|
customToolsCount: this.customTools.size,
|
|
1419
1544
|
inMemoryServersCount: this.inMemoryServers.size,
|
|
1545
|
+
externalMCPServersCount: 0,
|
|
1546
|
+
externalMCPConnectedCount: 0,
|
|
1547
|
+
externalMCPFailedCount: 0,
|
|
1548
|
+
externalMCPServers: [],
|
|
1420
1549
|
error: error instanceof Error ? error.message : String(error),
|
|
1421
1550
|
};
|
|
1422
1551
|
}
|
|
@@ -1426,8 +1555,54 @@ export class NeuroLink {
|
|
|
1426
1555
|
* @returns Promise resolving to array of MCP server information
|
|
1427
1556
|
*/
|
|
1428
1557
|
async listMCPServers() {
|
|
1429
|
-
|
|
1430
|
-
|
|
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;
|
|
1431
1606
|
}
|
|
1432
1607
|
/**
|
|
1433
1608
|
* Test connectivity to a specific MCP server
|
|
@@ -1435,8 +1610,29 @@ export class NeuroLink {
|
|
|
1435
1610
|
* @returns Promise resolving to true if server is reachable
|
|
1436
1611
|
*/
|
|
1437
1612
|
async testMCPServer(serverId) {
|
|
1438
|
-
|
|
1439
|
-
|
|
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
|
+
}
|
|
1440
1636
|
}
|
|
1441
1637
|
// ==================== PROVIDER HEALTH CHECKING ====================
|
|
1442
1638
|
/**
|
|
@@ -1678,6 +1874,277 @@ export class NeuroLink {
|
|
|
1678
1874
|
}
|
|
1679
1875
|
await this.conversationMemory.clearAllSessions();
|
|
1680
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
|
+
}
|
|
1681
2148
|
}
|
|
1682
2149
|
// Create default instance
|
|
1683
2150
|
export const neurolink = new NeuroLink();
|