@juspay/neurolink 7.13.0 → 7.14.1
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 +89 -25
- 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/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 +11 -30
- package/dist/core/baseProvider.js +268 -42
- package/dist/core/conversationMemoryManager.js +3 -2
- 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 -1
- package/dist/factories/providerFactory.js +5 -11
- package/dist/factories/providerRegistry.js +2 -2
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1 -1
- package/dist/lib/agent/directTools.js +1 -1
- 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 +11 -30
- package/dist/lib/core/baseProvider.js +268 -42
- package/dist/lib/core/conversationMemoryManager.js +3 -2
- package/dist/lib/core/dynamicModels.js +1 -1
- package/dist/lib/core/evaluation.js +1 -1
- package/dist/lib/core/factory.js +1 -1
- package/dist/lib/factories/providerFactory.js +5 -11
- package/dist/lib/factories/providerRegistry.js +2 -2
- package/dist/lib/index.d.ts +5 -4
- package/dist/lib/index.js +1 -1
- package/dist/lib/mcp/externalServerManager.d.ts +148 -0
- package/dist/lib/mcp/externalServerManager.js +1038 -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 +105 -0
- package/dist/lib/mcp/mcpClientFactory.js +421 -0
- package/dist/lib/mcp/toolDiscoveryService.d.ts +193 -0
- package/dist/lib/mcp/toolDiscoveryService.js +646 -0
- 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 +139 -43
- package/dist/lib/neurolink.js +604 -174
- package/dist/lib/providers/googleVertex.d.ts +7 -1
- package/dist/lib/providers/googleVertex.js +34 -7
- 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 +288 -0
- package/dist/lib/types/externalMcp.js +7 -0
- package/dist/lib/types/generateTypes.d.ts +0 -1
- package/dist/lib/types/index.d.ts +2 -2
- 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 +148 -0
- package/dist/mcp/externalServerManager.js +1038 -0
- package/dist/mcp/mcpCircuitBreaker.d.ts +184 -0
- package/dist/mcp/mcpCircuitBreaker.js +338 -0
- package/dist/mcp/mcpClientFactory.d.ts +105 -0
- package/dist/mcp/mcpClientFactory.js +421 -0
- package/dist/mcp/toolDiscoveryService.d.ts +193 -0
- package/dist/mcp/toolDiscoveryService.js +646 -0
- 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 +139 -43
- package/dist/neurolink.js +604 -174
- package/dist/providers/googleVertex.d.ts +7 -1
- package/dist/providers/googleVertex.js +34 -7
- 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 +288 -0
- package/dist/types/externalMcp.js +7 -0
- package/dist/types/generateTypes.d.ts +0 -1
- package/dist/types/index.d.ts +2 -2
- 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
package/dist/neurolink.js
CHANGED
|
@@ -21,7 +21,7 @@ import { toolRegistry } from "./mcp/toolRegistry.js";
|
|
|
21
21
|
import { logger } from "./utils/logger.js";
|
|
22
22
|
import { getBestProvider } from "./utils/providerUtils.js";
|
|
23
23
|
import { ProviderRegistry } from "./factories/providerRegistry.js";
|
|
24
|
-
import {
|
|
24
|
+
import { createCustomToolServerInfo, detectCategory, } from "./utils/mcpDefaults.js";
|
|
25
25
|
// Factory processing imports
|
|
26
26
|
import { processFactoryOptions, enhanceTextGenerationOptions, validateFactoryConfig, processStreamingFactoryOptions, createCleanStreamOptions, } from "./utils/factoryProcessing.js";
|
|
27
27
|
// Enhanced error handling imports
|
|
@@ -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
|
|
@@ -36,9 +37,9 @@ export class NeuroLink {
|
|
|
36
37
|
mcpInitialized = false;
|
|
37
38
|
emitter = new EventEmitter();
|
|
38
39
|
contextManager = null;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
autoDiscoveredServerInfos = [];
|
|
41
|
+
// External MCP server management
|
|
42
|
+
externalServerManager;
|
|
42
43
|
// Enhanced error handling support
|
|
43
44
|
toolCircuitBreakers = new Map();
|
|
44
45
|
toolExecutionMetrics = new Map();
|
|
@@ -73,6 +74,34 @@ export class NeuroLink {
|
|
|
73
74
|
maxTurnsPerSession: memoryConfig.maxTurnsPerSession,
|
|
74
75
|
});
|
|
75
76
|
}
|
|
77
|
+
// Initialize external server manager with main registry integration
|
|
78
|
+
this.externalServerManager = new ExternalServerManager({
|
|
79
|
+
maxServers: 20,
|
|
80
|
+
defaultTimeout: 15000,
|
|
81
|
+
enableAutoRestart: true,
|
|
82
|
+
enablePerformanceMonitoring: true,
|
|
83
|
+
}, {
|
|
84
|
+
enableMainRegistryIntegration: true, // Enable integration with main toolRegistry
|
|
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
|
|
@@ -98,6 +127,26 @@ export class NeuroLink {
|
|
|
98
127
|
]);
|
|
99
128
|
// Register all providers with lazy loading support
|
|
100
129
|
await ProviderRegistry.registerAllProviders();
|
|
130
|
+
// Load MCP configuration from .mcp-config.json using ExternalServerManager
|
|
131
|
+
try {
|
|
132
|
+
const configResult = await this.externalServerManager.loadMCPConfiguration();
|
|
133
|
+
mcpLogger.debug("[NeuroLink] MCP configuration loaded successfully", {
|
|
134
|
+
serversLoaded: configResult.serversLoaded,
|
|
135
|
+
errors: configResult.errors.length,
|
|
136
|
+
});
|
|
137
|
+
if (configResult.errors.length > 0) {
|
|
138
|
+
mcpLogger.warn("[NeuroLink] Some MCP servers failed to load", {
|
|
139
|
+
errors: configResult.errors,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (configError) {
|
|
144
|
+
mcpLogger.warn("[NeuroLink] MCP configuration loading failed", {
|
|
145
|
+
error: configError instanceof Error
|
|
146
|
+
? configError.message
|
|
147
|
+
: String(configError),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
101
150
|
this.mcpInitialized = true;
|
|
102
151
|
// Monitor memory usage and provide cleanup suggestions
|
|
103
152
|
const endMemory = MemoryManager.getMemoryUsageMB();
|
|
@@ -131,7 +180,9 @@ export class NeuroLink {
|
|
|
131
180
|
* @returns The original prompt text as a string.
|
|
132
181
|
*/
|
|
133
182
|
_extractOriginalPrompt(optionsOrPrompt) {
|
|
134
|
-
return typeof optionsOrPrompt ===
|
|
183
|
+
return typeof optionsOrPrompt === "string"
|
|
184
|
+
? optionsOrPrompt
|
|
185
|
+
: optionsOrPrompt.input.text;
|
|
135
186
|
}
|
|
136
187
|
/**
|
|
137
188
|
* Enables automatic context summarization for the NeuroLink instance.
|
|
@@ -321,6 +372,7 @@ export class NeuroLink {
|
|
|
321
372
|
// Try MCP-enhanced generation first (if not explicitly disabled)
|
|
322
373
|
if (!options.disableTools) {
|
|
323
374
|
try {
|
|
375
|
+
logger.debug(`[${functionTag}] Attempting MCP generation...`);
|
|
324
376
|
const mcpResult = await this.tryMCPGeneration(options);
|
|
325
377
|
if (mcpResult && mcpResult.content) {
|
|
326
378
|
logger.debug(`[${functionTag}] MCP generation successful`);
|
|
@@ -328,6 +380,13 @@ export class NeuroLink {
|
|
|
328
380
|
await storeConversationTurn(this.conversationMemory, options, mcpResult);
|
|
329
381
|
return mcpResult;
|
|
330
382
|
}
|
|
383
|
+
else {
|
|
384
|
+
logger.debug(`[${functionTag}] MCP generation returned empty result:`, {
|
|
385
|
+
hasResult: !!mcpResult,
|
|
386
|
+
hasContent: !!(mcpResult && mcpResult.content),
|
|
387
|
+
contentLength: mcpResult?.content?.length || 0,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
331
390
|
}
|
|
332
391
|
catch (error) {
|
|
333
392
|
logger.debug(`[${functionTag}] MCP generation failed, falling back`, {
|
|
@@ -367,34 +426,7 @@ export class NeuroLink {
|
|
|
367
426
|
? await getBestProvider()
|
|
368
427
|
: options.provider;
|
|
369
428
|
// 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
|
-
}
|
|
429
|
+
const availableTools = await this.getAllAvailableTools();
|
|
398
430
|
// Create tool-aware system prompt
|
|
399
431
|
const enhancedSystemPrompt = this.createToolAwareSystemPrompt(options.systemPrompt, availableTools);
|
|
400
432
|
// Get conversation messages for context
|
|
@@ -404,7 +436,7 @@ export class NeuroLink {
|
|
|
404
436
|
this);
|
|
405
437
|
// Enable tool execution for the provider using BaseProvider method
|
|
406
438
|
provider.setupToolExecutor({
|
|
407
|
-
customTools: this.
|
|
439
|
+
customTools: this.getCustomTools(),
|
|
408
440
|
executeTool: this.executeTool.bind(this),
|
|
409
441
|
}, functionTag);
|
|
410
442
|
const result = await provider.generate({
|
|
@@ -417,7 +449,7 @@ export class NeuroLink {
|
|
|
417
449
|
if (!result || !result.content || result.content.trim().length === 0) {
|
|
418
450
|
return null; // Let caller fall back to direct generation
|
|
419
451
|
}
|
|
420
|
-
// Return enhanced result
|
|
452
|
+
// Return enhanced result with external tool information
|
|
421
453
|
return {
|
|
422
454
|
content: result.content,
|
|
423
455
|
provider: providerName,
|
|
@@ -432,9 +464,14 @@ export class NeuroLink {
|
|
|
432
464
|
success: true, // Assume success if tool executed (AI providers handle failures differently)
|
|
433
465
|
serverId: teRecord.serverId || undefined,
|
|
434
466
|
};
|
|
435
|
-
}) || [],
|
|
467
|
+
}) || [],
|
|
436
468
|
enhancedWithTools: true,
|
|
437
|
-
availableTools: availableTools.
|
|
469
|
+
availableTools: availableTools.map((tool) => ({
|
|
470
|
+
name: tool.name,
|
|
471
|
+
description: tool.description,
|
|
472
|
+
server: tool.server,
|
|
473
|
+
category: tool.category,
|
|
474
|
+
})),
|
|
438
475
|
// Include analytics and evaluation from BaseProvider
|
|
439
476
|
analytics: result.analytics,
|
|
440
477
|
evaluation: result.evaluation,
|
|
@@ -485,7 +522,7 @@ export class NeuroLink {
|
|
|
485
522
|
this);
|
|
486
523
|
// Enable tool execution for direct provider generation using BaseProvider method
|
|
487
524
|
provider.setupToolExecutor({
|
|
488
|
-
customTools: this.
|
|
525
|
+
customTools: this.getCustomTools(),
|
|
489
526
|
executeTool: this.executeTool.bind(this),
|
|
490
527
|
}, functionTag);
|
|
491
528
|
const result = await provider.generate({
|
|
@@ -537,8 +574,23 @@ export class NeuroLink {
|
|
|
537
574
|
return originalSystemPrompt || "";
|
|
538
575
|
}
|
|
539
576
|
const toolDescriptions = availableTools
|
|
540
|
-
.map((tool) =>
|
|
541
|
-
|
|
577
|
+
.map((tool) => {
|
|
578
|
+
const toolWithSchema = tool;
|
|
579
|
+
const schema = (toolWithSchema.inputSchema ||
|
|
580
|
+
toolWithSchema.parameters);
|
|
581
|
+
let params = "";
|
|
582
|
+
if (schema && schema.properties) {
|
|
583
|
+
const requiredParams = new Set(schema.required || []);
|
|
584
|
+
params = Object.entries(schema.properties)
|
|
585
|
+
.map(([key, value]) => {
|
|
586
|
+
const required = requiredParams.has(key) ? " (required)" : "";
|
|
587
|
+
return ` - ${key}: ${value.type}${required}`;
|
|
588
|
+
})
|
|
589
|
+
.join("\n");
|
|
590
|
+
}
|
|
591
|
+
return `- ${tool.name}: ${tool.description} (from ${tool.server})\n${params}`;
|
|
592
|
+
})
|
|
593
|
+
.join("\n\n");
|
|
542
594
|
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
595
|
return (originalSystemPrompt || "") + toolPrompt;
|
|
544
596
|
}
|
|
@@ -572,7 +624,7 @@ export class NeuroLink {
|
|
|
572
624
|
for (const result of toolResults) {
|
|
573
625
|
if (result && typeof result === "object") {
|
|
574
626
|
enhancedPrompt += `\n\nTool Results:\n`;
|
|
575
|
-
// Handle
|
|
627
|
+
// Handle structured result generically
|
|
576
628
|
try {
|
|
577
629
|
const resultStr = typeof result === "string"
|
|
578
630
|
? result
|
|
@@ -682,7 +734,7 @@ export class NeuroLink {
|
|
|
682
734
|
const provider = await AIProviderFactory.createBestProvider(providerName, options.model, true, this);
|
|
683
735
|
// Enable tool execution for streaming using BaseProvider method
|
|
684
736
|
provider.setupToolExecutor({
|
|
685
|
-
customTools: this.
|
|
737
|
+
customTools: this.getCustomTools(),
|
|
686
738
|
executeTool: this.executeTool.bind(this),
|
|
687
739
|
}, functionTag);
|
|
688
740
|
// Create clean options for provider (remove factoryConfig)
|
|
@@ -738,7 +790,7 @@ export class NeuroLink {
|
|
|
738
790
|
this);
|
|
739
791
|
// Enable tool execution for fallback streaming using BaseProvider method
|
|
740
792
|
provider.setupToolExecutor({
|
|
741
|
-
customTools: this.
|
|
793
|
+
customTools: this.getCustomTools(),
|
|
742
794
|
executeTool: this.executeTool.bind(this),
|
|
743
795
|
}, functionTag);
|
|
744
796
|
// Create clean options for fallback provider (remove factoryConfig)
|
|
@@ -792,7 +844,7 @@ export class NeuroLink {
|
|
|
792
844
|
/**
|
|
793
845
|
* Register a custom tool that will be available to all AI providers
|
|
794
846
|
* @param name - Unique name for the tool
|
|
795
|
-
* @param tool - Tool
|
|
847
|
+
* @param tool - Tool in MCPExecutableTool format (unified MCP protocol type)
|
|
796
848
|
*/
|
|
797
849
|
registerTool(name, tool) {
|
|
798
850
|
// Emit tool registration start event
|
|
@@ -801,18 +853,10 @@ export class NeuroLink {
|
|
|
801
853
|
timestamp: Date.now(),
|
|
802
854
|
});
|
|
803
855
|
try {
|
|
804
|
-
//
|
|
805
|
-
|
|
806
|
-
//
|
|
807
|
-
|
|
808
|
-
// Convert to MCP server format for integration
|
|
809
|
-
const serverId = `custom-tool-${name}`;
|
|
810
|
-
const mcpServer = createMCPServerFromTools(serverId, { [name]: tool }, {
|
|
811
|
-
title: `Custom Tool: ${name}`,
|
|
812
|
-
category: "custom",
|
|
813
|
-
});
|
|
814
|
-
// Store as in-memory server
|
|
815
|
-
this.inMemoryServers.set(serverId, mcpServer);
|
|
856
|
+
// SMART DEFAULTS: Use utility to eliminate boilerplate creation
|
|
857
|
+
const mcpServerInfo = createCustomToolServerInfo(name, tool);
|
|
858
|
+
// Register with toolRegistry using MCPServerInfo directly
|
|
859
|
+
toolRegistry.registerServer(mcpServerInfo);
|
|
816
860
|
logger.info(`Registered custom tool: ${name}`);
|
|
817
861
|
// Emit tool registration success event
|
|
818
862
|
this.emitter.emit("tools-register:end", {
|
|
@@ -828,10 +872,10 @@ export class NeuroLink {
|
|
|
828
872
|
}
|
|
829
873
|
/**
|
|
830
874
|
* Register multiple tools at once - Supports both object and array formats
|
|
831
|
-
* @param tools - Object mapping tool names to
|
|
875
|
+
* @param tools - Object mapping tool names to MCPExecutableTool format OR Array of tools with names
|
|
832
876
|
*
|
|
833
|
-
* Object format (existing): { toolName:
|
|
834
|
-
* Array format (Lighthouse compatible): [{ name: string, tool:
|
|
877
|
+
* Object format (existing): { toolName: MCPExecutableTool, ... }
|
|
878
|
+
* Array format (Lighthouse compatible): [{ name: string, tool: MCPExecutableTool }, ...]
|
|
835
879
|
*/
|
|
836
880
|
registerTools(tools) {
|
|
837
881
|
if (Array.isArray(tools)) {
|
|
@@ -853,40 +897,57 @@ export class NeuroLink {
|
|
|
853
897
|
* @returns true if the tool was removed, false if it didn't exist
|
|
854
898
|
*/
|
|
855
899
|
unregisterTool(name) {
|
|
856
|
-
const
|
|
900
|
+
const serverId = `custom-tool-${name}`;
|
|
901
|
+
const removed = toolRegistry.unregisterServer(serverId);
|
|
857
902
|
if (removed) {
|
|
858
|
-
const serverId = `custom-tool-${name}`;
|
|
859
|
-
this.inMemoryServers.delete(serverId);
|
|
860
903
|
logger.info(`Unregistered custom tool: ${name}`);
|
|
861
904
|
}
|
|
862
905
|
return removed;
|
|
863
906
|
}
|
|
864
907
|
/**
|
|
865
908
|
* Get all registered custom tools
|
|
866
|
-
* @returns Map of tool names to
|
|
909
|
+
* @returns Map of tool names to MCPExecutableTool format
|
|
867
910
|
*/
|
|
868
911
|
getCustomTools() {
|
|
869
|
-
|
|
912
|
+
// Get tools from toolRegistry with smart category detection
|
|
913
|
+
const customTools = toolRegistry.getToolsByCategory(detectCategory({ isCustomTool: true }));
|
|
914
|
+
const toolMap = new Map();
|
|
915
|
+
for (const tool of customTools) {
|
|
916
|
+
// Return MCPServerInfo.tools format directly - no conversion needed
|
|
917
|
+
toolMap.set(tool.name, {
|
|
918
|
+
name: tool.name,
|
|
919
|
+
description: tool.description || "",
|
|
920
|
+
inputSchema: {},
|
|
921
|
+
execute: async (args, context) => {
|
|
922
|
+
// Type guard to ensure context is compatible with ExecutionContext
|
|
923
|
+
const executionContext = context && typeof context === "object" && context !== null
|
|
924
|
+
? context
|
|
925
|
+
: undefined;
|
|
926
|
+
return await toolRegistry.executeTool(tool.name, args, executionContext);
|
|
927
|
+
},
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
return toolMap;
|
|
870
931
|
}
|
|
871
932
|
/**
|
|
872
933
|
* Add an in-memory MCP server (from git diff)
|
|
873
934
|
* Allows registration of pre-instantiated server objects
|
|
874
935
|
* @param serverId - Unique identifier for the server
|
|
875
|
-
* @param
|
|
936
|
+
* @param serverInfo - Server configuration
|
|
876
937
|
*/
|
|
877
|
-
async addInMemoryMCPServer(serverId,
|
|
938
|
+
async addInMemoryMCPServer(serverId, serverInfo) {
|
|
878
939
|
try {
|
|
879
940
|
mcpLogger.debug(`[NeuroLink] Registering in-memory MCP server: ${serverId}`);
|
|
880
|
-
//
|
|
881
|
-
if (!
|
|
882
|
-
|
|
941
|
+
// Initialize tools array if not provided
|
|
942
|
+
if (!serverInfo.tools) {
|
|
943
|
+
serverInfo.tools = [];
|
|
883
944
|
}
|
|
884
|
-
//
|
|
885
|
-
|
|
945
|
+
// ZERO CONVERSIONS: Pass MCPServerInfo directly to toolRegistry
|
|
946
|
+
await toolRegistry.registerServer(serverInfo);
|
|
886
947
|
mcpLogger.info(`[NeuroLink] Successfully registered in-memory server: ${serverId}`, {
|
|
887
|
-
category:
|
|
888
|
-
provider:
|
|
889
|
-
version:
|
|
948
|
+
category: serverInfo.metadata?.category,
|
|
949
|
+
provider: serverInfo.metadata?.provider,
|
|
950
|
+
version: serverInfo.metadata?.version,
|
|
890
951
|
});
|
|
891
952
|
}
|
|
892
953
|
catch (error) {
|
|
@@ -896,10 +957,41 @@ export class NeuroLink {
|
|
|
896
957
|
}
|
|
897
958
|
/**
|
|
898
959
|
* Get all registered in-memory servers
|
|
899
|
-
* @returns Map of server IDs to
|
|
960
|
+
* @returns Map of server IDs to MCPServerInfo
|
|
900
961
|
*/
|
|
901
962
|
getInMemoryServers() {
|
|
902
|
-
|
|
963
|
+
// Get in-memory servers from toolRegistry
|
|
964
|
+
const serverInfos = toolRegistry.getBuiltInServerInfos();
|
|
965
|
+
const serverMap = new Map();
|
|
966
|
+
for (const serverInfo of serverInfos) {
|
|
967
|
+
if (detectCategory({
|
|
968
|
+
existingCategory: serverInfo.metadata?.category,
|
|
969
|
+
serverId: serverInfo.id,
|
|
970
|
+
}) === "in-memory") {
|
|
971
|
+
serverMap.set(serverInfo.id, serverInfo);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return serverMap;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Get in-memory servers as MCPServerInfo - ZERO conversion needed
|
|
978
|
+
* Now fetches from centralized tool registry instead of local duplication
|
|
979
|
+
* @returns Array of MCPServerInfo
|
|
980
|
+
*/
|
|
981
|
+
getInMemoryServerInfos() {
|
|
982
|
+
// Get in-memory servers from centralized tool registry
|
|
983
|
+
const allServers = toolRegistry.getBuiltInServerInfos();
|
|
984
|
+
return allServers.filter((server) => detectCategory({
|
|
985
|
+
existingCategory: server.metadata?.category,
|
|
986
|
+
serverId: server.id,
|
|
987
|
+
}) === "in-memory");
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Get auto-discovered servers as MCPServerInfo - ZERO conversion needed
|
|
991
|
+
* @returns Array of MCPServerInfo
|
|
992
|
+
*/
|
|
993
|
+
getAutoDiscoveredServerInfos() {
|
|
994
|
+
return this.autoDiscoveredServerInfos;
|
|
903
995
|
}
|
|
904
996
|
/**
|
|
905
997
|
* Execute a specific tool by name with robust error handling
|
|
@@ -912,6 +1004,14 @@ export class NeuroLink {
|
|
|
912
1004
|
async executeTool(toolName, params = {}, options) {
|
|
913
1005
|
const functionTag = "NeuroLink.executeTool";
|
|
914
1006
|
const executionStartTime = Date.now();
|
|
1007
|
+
// Debug: Log tool execution attempt
|
|
1008
|
+
logger.debug(`[${functionTag}] Tool execution requested:`, {
|
|
1009
|
+
toolName,
|
|
1010
|
+
params: typeof params === "object"
|
|
1011
|
+
? Object.keys(params).length + " params"
|
|
1012
|
+
: params,
|
|
1013
|
+
hasExternalManager: !!this.externalServerManager,
|
|
1014
|
+
});
|
|
915
1015
|
// Emit tool start event
|
|
916
1016
|
this.emitter.emit("tool:start", {
|
|
917
1017
|
toolName,
|
|
@@ -1010,8 +1110,8 @@ export class NeuroLink {
|
|
|
1010
1110
|
structuredError = ErrorFactory.toolTimeout(toolName, finalOptions.timeout);
|
|
1011
1111
|
}
|
|
1012
1112
|
else if (error.message.includes("not found")) {
|
|
1013
|
-
const availableTools =
|
|
1014
|
-
structuredError = ErrorFactory.toolNotFound(toolName, availableTools);
|
|
1113
|
+
const availableTools = await this.getAllAvailableTools();
|
|
1114
|
+
structuredError = ErrorFactory.toolNotFound(toolName, availableTools.map((t) => t.name));
|
|
1015
1115
|
}
|
|
1016
1116
|
else if (error.message.includes("validation") ||
|
|
1017
1117
|
error.message.includes("parameter")) {
|
|
@@ -1053,58 +1153,37 @@ export class NeuroLink {
|
|
|
1053
1153
|
*/
|
|
1054
1154
|
async executeToolInternal(toolName, params, options) {
|
|
1055
1155
|
const functionTag = "NeuroLink.executeToolInternal";
|
|
1056
|
-
//
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
}
|
|
1068
|
-
const context = {
|
|
1069
|
-
sessionId: `direct-execution-${Date.now()}`,
|
|
1070
|
-
logger,
|
|
1071
|
-
};
|
|
1156
|
+
// Check external MCP servers
|
|
1157
|
+
const externalTools = this.externalServerManager.getAllTools();
|
|
1158
|
+
const externalTool = externalTools.find((tool) => tool.name === toolName);
|
|
1159
|
+
logger.debug(`[${functionTag}] External MCP tool search:`, {
|
|
1160
|
+
toolName,
|
|
1161
|
+
externalToolsCount: externalTools.length,
|
|
1162
|
+
foundTool: !!externalTool,
|
|
1163
|
+
isAvailable: externalTool?.isAvailable,
|
|
1164
|
+
serverId: externalTool?.serverId,
|
|
1165
|
+
});
|
|
1166
|
+
if (externalTool && externalTool.isAvailable) {
|
|
1072
1167
|
try {
|
|
1073
|
-
|
|
1168
|
+
mcpLogger.debug(`[${functionTag}] Executing external MCP tool: ${toolName} from ${externalTool.serverId}`);
|
|
1169
|
+
const result = await this.externalServerManager.executeTool(externalTool.serverId, toolName, params, { timeout: options.timeout });
|
|
1170
|
+
logger.debug(`[${functionTag}] External MCP tool execution successful:`, {
|
|
1171
|
+
toolName,
|
|
1172
|
+
serverId: externalTool.serverId,
|
|
1173
|
+
resultType: typeof result,
|
|
1174
|
+
});
|
|
1074
1175
|
return result;
|
|
1075
1176
|
}
|
|
1076
1177
|
catch (error) {
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
if (server && server.tools) {
|
|
1084
|
-
const tool = server.tools instanceof Map
|
|
1085
|
-
? server.tools.get(toolName)
|
|
1086
|
-
: server.tools[toolName];
|
|
1087
|
-
if (tool && typeof tool.execute === "function") {
|
|
1088
|
-
try {
|
|
1089
|
-
const result = await tool.execute(params);
|
|
1090
|
-
// Handle MCP-style results
|
|
1091
|
-
if (result && typeof result === "object" && "success" in result) {
|
|
1092
|
-
if (result.success) {
|
|
1093
|
-
return result.data;
|
|
1094
|
-
}
|
|
1095
|
-
else {
|
|
1096
|
-
throw ErrorFactory.toolExecutionFailed(toolName, new Error(result.error || "Tool execution failed"), serverId);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
return result;
|
|
1100
|
-
}
|
|
1101
|
-
catch (error) {
|
|
1102
|
-
throw ErrorFactory.toolExecutionFailed(toolName, error instanceof Error ? error : new Error(String(error)), serverId);
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1178
|
+
logger.error(`[${functionTag}] External MCP tool execution failed:`, {
|
|
1179
|
+
toolName,
|
|
1180
|
+
serverId: externalTool.serverId,
|
|
1181
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1182
|
+
});
|
|
1183
|
+
throw ErrorFactory.toolExecutionFailed(toolName, error instanceof Error ? error : new Error(String(error)), externalTool.serverId);
|
|
1105
1184
|
}
|
|
1106
1185
|
}
|
|
1107
|
-
// If not found in custom tools
|
|
1186
|
+
// If not found in custom tools, in-memory servers, or external servers, try unified registry
|
|
1108
1187
|
try {
|
|
1109
1188
|
const context = {
|
|
1110
1189
|
sessionId: `neurolink-tool-${Date.now()}`,
|
|
@@ -1139,42 +1218,71 @@ export class NeuroLink {
|
|
|
1139
1218
|
serverId: tool.serverId === "direct" ? "neurolink-direct" : tool.serverId, // Update serverId for test compatibility
|
|
1140
1219
|
}));
|
|
1141
1220
|
// 2. Get custom tools from this NeuroLink instance
|
|
1142
|
-
const customTools =
|
|
1143
|
-
|
|
1144
|
-
|
|
1221
|
+
const customTools = toolRegistry
|
|
1222
|
+
.getToolsByCategory(detectCategory({ isCustomTool: true }))
|
|
1223
|
+
.map((tool) => ({
|
|
1224
|
+
name: tool.name,
|
|
1225
|
+
toolName: tool.name, // Add toolName property for compatibility with tests
|
|
1145
1226
|
description: tool.description || "Custom tool",
|
|
1146
|
-
serverId: `custom-tool-${name}`,
|
|
1147
|
-
category:
|
|
1148
|
-
|
|
1227
|
+
serverId: tool.serverId || `custom-tool-${tool.name}`,
|
|
1228
|
+
category: detectCategory({
|
|
1229
|
+
isCustomTool: true,
|
|
1230
|
+
serverId: tool.serverId,
|
|
1231
|
+
}),
|
|
1232
|
+
inputSchema: tool.inputSchema || {},
|
|
1149
1233
|
}));
|
|
1150
1234
|
// 3. Get tools from in-memory MCP servers
|
|
1151
|
-
const inMemoryTools =
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1235
|
+
const inMemoryTools = toolRegistry
|
|
1236
|
+
.getToolsByCategory("in-memory")
|
|
1237
|
+
.map((tool) => ({
|
|
1238
|
+
name: tool.name,
|
|
1239
|
+
toolName: tool.name, // Add toolName property for compatibility with tests
|
|
1240
|
+
description: tool.description || "In-memory MCP tool",
|
|
1241
|
+
serverId: tool.serverId || "unknown",
|
|
1242
|
+
category: tool.category || "in-memory",
|
|
1243
|
+
inputSchema: {},
|
|
1244
|
+
}));
|
|
1245
|
+
// 4. Get external MCP tools
|
|
1246
|
+
const externalMCPTools = this.externalServerManager
|
|
1247
|
+
.getAllTools()
|
|
1248
|
+
.map((tool) => ({
|
|
1249
|
+
name: tool.name,
|
|
1250
|
+
toolName: tool.name,
|
|
1251
|
+
description: tool.description,
|
|
1252
|
+
serverId: tool.serverId,
|
|
1253
|
+
category: detectCategory({
|
|
1254
|
+
existingCategory: typeof tool.metadata?.category === "string"
|
|
1255
|
+
? tool.metadata.category
|
|
1256
|
+
: undefined,
|
|
1257
|
+
isExternal: true,
|
|
1258
|
+
serverId: tool.serverId,
|
|
1259
|
+
}),
|
|
1260
|
+
inputSchema: tool.inputSchema || {},
|
|
1261
|
+
isAvailable: tool.isAvailable,
|
|
1262
|
+
stats: tool.stats,
|
|
1263
|
+
}));
|
|
1264
|
+
// 5. Combine all tools with deduplication by name
|
|
1265
|
+
const combinedTools = [
|
|
1266
|
+
...mcpTools,
|
|
1267
|
+
...customTools,
|
|
1268
|
+
...inMemoryTools,
|
|
1269
|
+
...externalMCPTools,
|
|
1270
|
+
];
|
|
1271
|
+
const uniqueTools = Array.from(combinedTools
|
|
1272
|
+
.reduce((map, tool) => {
|
|
1273
|
+
const key = tool.name;
|
|
1274
|
+
if (!map.has(key)) {
|
|
1275
|
+
map.set(key, tool);
|
|
1169
1276
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1277
|
+
return map;
|
|
1278
|
+
}, new Map())
|
|
1279
|
+
.values());
|
|
1173
1280
|
mcpLogger.debug("Tool discovery results", {
|
|
1174
1281
|
mcpTools: mcpTools.length,
|
|
1175
1282
|
customTools: customTools.length,
|
|
1176
1283
|
inMemoryTools: inMemoryTools.length,
|
|
1177
|
-
|
|
1284
|
+
externalMCPTools: externalMCPTools.length,
|
|
1285
|
+
total: uniqueTools.length,
|
|
1178
1286
|
});
|
|
1179
1287
|
// Check memory usage after tool enumeration
|
|
1180
1288
|
const endMemory = MemoryManager.getMemoryUsageMB();
|
|
@@ -1182,11 +1290,11 @@ export class NeuroLink {
|
|
|
1182
1290
|
if (memoryDelta > 10) {
|
|
1183
1291
|
mcpLogger.debug(`🔍 Tool listing used ${memoryDelta}MB memory (large tool registry detected)`);
|
|
1184
1292
|
// Suggest periodic cleanup for large tool registries
|
|
1185
|
-
if (
|
|
1293
|
+
if (uniqueTools.length > 100) {
|
|
1186
1294
|
mcpLogger.debug("💡 Suggestion: Consider using tool categories or lazy loading for large tool sets");
|
|
1187
1295
|
}
|
|
1188
1296
|
}
|
|
1189
|
-
return
|
|
1297
|
+
return uniqueTools;
|
|
1190
1298
|
}
|
|
1191
1299
|
catch (error) {
|
|
1192
1300
|
mcpLogger.error("Failed to list available tools", { error });
|
|
@@ -1394,17 +1502,39 @@ export class NeuroLink {
|
|
|
1394
1502
|
*/
|
|
1395
1503
|
async getMCPStatus() {
|
|
1396
1504
|
try {
|
|
1397
|
-
//
|
|
1505
|
+
// Initialize MCP if not already initialized (loads external servers from config)
|
|
1506
|
+
await this.initializeMCP();
|
|
1507
|
+
// Get built-in tools
|
|
1398
1508
|
const allTools = await toolRegistry.listTools();
|
|
1509
|
+
// Get external MCP server statistics
|
|
1510
|
+
const externalStats = this.externalServerManager.getStatistics();
|
|
1511
|
+
// DIRECT RETURNS - ZERO conversion
|
|
1512
|
+
const externalMCPServers = this.externalServerManager.listServers();
|
|
1513
|
+
const inMemoryServerInfos = this.getInMemoryServerInfos();
|
|
1514
|
+
const builtInServerInfos = toolRegistry.getBuiltInServerInfos();
|
|
1515
|
+
const autoDiscoveredServerInfos = this.getAutoDiscoveredServerInfos();
|
|
1516
|
+
// Calculate totals
|
|
1517
|
+
const totalServers = externalMCPServers.length +
|
|
1518
|
+
inMemoryServerInfos.length +
|
|
1519
|
+
builtInServerInfos.length +
|
|
1520
|
+
autoDiscoveredServerInfos.length;
|
|
1521
|
+
const availableServers = externalStats.connectedServers +
|
|
1522
|
+
inMemoryServerInfos.length +
|
|
1523
|
+
builtInServerInfos.length; // in-memory and built-in always available
|
|
1524
|
+
const totalTools = allTools.length + externalStats.totalTools;
|
|
1399
1525
|
return {
|
|
1400
1526
|
mcpInitialized: this.mcpInitialized,
|
|
1401
|
-
totalServers
|
|
1402
|
-
availableServers
|
|
1403
|
-
autoDiscoveredCount:
|
|
1404
|
-
totalTools
|
|
1405
|
-
autoDiscoveredServers:
|
|
1406
|
-
customToolsCount:
|
|
1407
|
-
inMemoryServersCount:
|
|
1527
|
+
totalServers,
|
|
1528
|
+
availableServers,
|
|
1529
|
+
autoDiscoveredCount: autoDiscoveredServerInfos.length,
|
|
1530
|
+
totalTools,
|
|
1531
|
+
autoDiscoveredServers: autoDiscoveredServerInfos,
|
|
1532
|
+
customToolsCount: toolRegistry.getToolsByCategory(detectCategory({ isCustomTool: true })).length,
|
|
1533
|
+
inMemoryServersCount: inMemoryServerInfos.length,
|
|
1534
|
+
externalMCPServersCount: externalMCPServers.length,
|
|
1535
|
+
externalMCPConnectedCount: externalStats.connectedServers,
|
|
1536
|
+
externalMCPFailedCount: externalStats.failedServers,
|
|
1537
|
+
externalMCPServers,
|
|
1408
1538
|
};
|
|
1409
1539
|
}
|
|
1410
1540
|
catch (error) {
|
|
@@ -1415,8 +1545,12 @@ export class NeuroLink {
|
|
|
1415
1545
|
autoDiscoveredCount: 0,
|
|
1416
1546
|
totalTools: 0,
|
|
1417
1547
|
autoDiscoveredServers: [],
|
|
1418
|
-
customToolsCount:
|
|
1419
|
-
inMemoryServersCount:
|
|
1548
|
+
customToolsCount: toolRegistry.getToolsByCategory(detectCategory({ isCustomTool: true })).length,
|
|
1549
|
+
inMemoryServersCount: 0,
|
|
1550
|
+
externalMCPServersCount: 0,
|
|
1551
|
+
externalMCPConnectedCount: 0,
|
|
1552
|
+
externalMCPFailedCount: 0,
|
|
1553
|
+
externalMCPServers: [],
|
|
1420
1554
|
error: error instanceof Error ? error.message : String(error),
|
|
1421
1555
|
};
|
|
1422
1556
|
}
|
|
@@ -1426,8 +1560,13 @@ export class NeuroLink {
|
|
|
1426
1560
|
* @returns Promise resolving to array of MCP server information
|
|
1427
1561
|
*/
|
|
1428
1562
|
async listMCPServers() {
|
|
1429
|
-
//
|
|
1430
|
-
return [
|
|
1563
|
+
// DIRECT RETURNS - ZERO conversion logic
|
|
1564
|
+
return [
|
|
1565
|
+
...this.externalServerManager.listServers(), // Direct return
|
|
1566
|
+
...this.getInMemoryServerInfos(), // Direct return
|
|
1567
|
+
...toolRegistry.getBuiltInServerInfos(), // Direct return
|
|
1568
|
+
...this.getAutoDiscoveredServerInfos(), // Direct return
|
|
1569
|
+
];
|
|
1431
1570
|
}
|
|
1432
1571
|
/**
|
|
1433
1572
|
* Test connectivity to a specific MCP server
|
|
@@ -1435,8 +1574,30 @@ export class NeuroLink {
|
|
|
1435
1574
|
* @returns Promise resolving to true if server is reachable
|
|
1436
1575
|
*/
|
|
1437
1576
|
async testMCPServer(serverId) {
|
|
1438
|
-
|
|
1439
|
-
|
|
1577
|
+
try {
|
|
1578
|
+
// Test built-in tools
|
|
1579
|
+
if (serverId === "neurolink-direct") {
|
|
1580
|
+
const tools = await toolRegistry.listTools();
|
|
1581
|
+
return tools.length > 0;
|
|
1582
|
+
}
|
|
1583
|
+
// Test in-memory servers
|
|
1584
|
+
const inMemoryServers = this.getInMemoryServers();
|
|
1585
|
+
if (inMemoryServers.has(serverId)) {
|
|
1586
|
+
const serverInfo = inMemoryServers.get(serverId);
|
|
1587
|
+
return !!(serverInfo.tools && serverInfo.tools.length > 0);
|
|
1588
|
+
}
|
|
1589
|
+
// Test external MCP servers
|
|
1590
|
+
const externalServer = this.externalServerManager.getServer(serverId);
|
|
1591
|
+
if (externalServer) {
|
|
1592
|
+
return (externalServer.status === "connected" &&
|
|
1593
|
+
externalServer.client !== null);
|
|
1594
|
+
}
|
|
1595
|
+
return false;
|
|
1596
|
+
}
|
|
1597
|
+
catch (error) {
|
|
1598
|
+
mcpLogger.error(`[NeuroLink] Error testing MCP server ${serverId}:`, error);
|
|
1599
|
+
return false;
|
|
1600
|
+
}
|
|
1440
1601
|
}
|
|
1441
1602
|
// ==================== PROVIDER HEALTH CHECKING ====================
|
|
1442
1603
|
/**
|
|
@@ -1589,14 +1750,12 @@ export class NeuroLink {
|
|
|
1589
1750
|
* Get comprehensive tool health report
|
|
1590
1751
|
* @returns Detailed health report for all tools
|
|
1591
1752
|
*/
|
|
1592
|
-
getToolHealthReport() {
|
|
1753
|
+
async getToolHealthReport() {
|
|
1593
1754
|
const tools = {};
|
|
1594
1755
|
let healthyCount = 0;
|
|
1595
|
-
// Get all tool names from
|
|
1596
|
-
const
|
|
1597
|
-
|
|
1598
|
-
...Array.from(this.inMemoryServers.values()).flatMap((server) => server.server?.tools ? Object.keys(server.server.tools) : []),
|
|
1599
|
-
]);
|
|
1756
|
+
// Get all tool names from toolRegistry
|
|
1757
|
+
const allTools = await toolRegistry.listTools();
|
|
1758
|
+
const allToolNames = new Set(allTools.map((tool) => tool.name));
|
|
1600
1759
|
for (const toolName of allToolNames) {
|
|
1601
1760
|
const metrics = this.toolExecutionMetrics.get(toolName);
|
|
1602
1761
|
const circuitBreaker = this.toolCircuitBreakers.get(toolName);
|
|
@@ -1678,6 +1837,277 @@ export class NeuroLink {
|
|
|
1678
1837
|
}
|
|
1679
1838
|
await this.conversationMemory.clearAllSessions();
|
|
1680
1839
|
}
|
|
1840
|
+
// ===== EXTERNAL MCP SERVER METHODS =====
|
|
1841
|
+
/**
|
|
1842
|
+
* Add an external MCP server
|
|
1843
|
+
* Automatically discovers and registers tools from the server
|
|
1844
|
+
* @param serverId - Unique identifier for the server
|
|
1845
|
+
* @param config - External MCP server configuration
|
|
1846
|
+
* @returns Operation result with server instance
|
|
1847
|
+
*/
|
|
1848
|
+
async addExternalMCPServer(serverId, config) {
|
|
1849
|
+
try {
|
|
1850
|
+
mcpLogger.info(`[NeuroLink] Adding external MCP server: ${serverId}`, {
|
|
1851
|
+
command: config.command,
|
|
1852
|
+
transport: config.transport,
|
|
1853
|
+
});
|
|
1854
|
+
const result = await this.externalServerManager.addServer(serverId, config);
|
|
1855
|
+
if (result.success) {
|
|
1856
|
+
mcpLogger.info(`[NeuroLink] External MCP server added successfully: ${serverId}`, {
|
|
1857
|
+
toolsDiscovered: result.metadata?.toolsDiscovered || 0,
|
|
1858
|
+
duration: result.duration,
|
|
1859
|
+
});
|
|
1860
|
+
// Emit server added event
|
|
1861
|
+
this.emitter.emit("externalMCP:serverAdded", {
|
|
1862
|
+
serverId,
|
|
1863
|
+
config,
|
|
1864
|
+
toolCount: result.metadata?.toolsDiscovered || 0,
|
|
1865
|
+
timestamp: Date.now(),
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
else {
|
|
1869
|
+
mcpLogger.error(`[NeuroLink] Failed to add external MCP server: ${serverId}`, {
|
|
1870
|
+
error: result.error,
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
return result;
|
|
1874
|
+
}
|
|
1875
|
+
catch (error) {
|
|
1876
|
+
mcpLogger.error(`[NeuroLink] Error adding external MCP server: ${serverId}`, error);
|
|
1877
|
+
throw error;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Remove an external MCP server
|
|
1882
|
+
* Stops the server and removes all its tools
|
|
1883
|
+
* @param serverId - ID of the server to remove
|
|
1884
|
+
* @returns Operation result
|
|
1885
|
+
*/
|
|
1886
|
+
async removeExternalMCPServer(serverId) {
|
|
1887
|
+
try {
|
|
1888
|
+
mcpLogger.info(`[NeuroLink] Removing external MCP server: ${serverId}`);
|
|
1889
|
+
const result = await this.externalServerManager.removeServer(serverId);
|
|
1890
|
+
if (result.success) {
|
|
1891
|
+
mcpLogger.info(`[NeuroLink] External MCP server removed successfully: ${serverId}`);
|
|
1892
|
+
// Emit server removed event
|
|
1893
|
+
this.emitter.emit("externalMCP:serverRemoved", {
|
|
1894
|
+
serverId,
|
|
1895
|
+
timestamp: Date.now(),
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
else {
|
|
1899
|
+
mcpLogger.error(`[NeuroLink] Failed to remove external MCP server: ${serverId}`, {
|
|
1900
|
+
error: result.error,
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
return result;
|
|
1904
|
+
}
|
|
1905
|
+
catch (error) {
|
|
1906
|
+
mcpLogger.error(`[NeuroLink] Error removing external MCP server: ${serverId}`, error);
|
|
1907
|
+
throw error;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
/**
|
|
1911
|
+
* List all external MCP servers
|
|
1912
|
+
* @returns Array of server health information
|
|
1913
|
+
*/
|
|
1914
|
+
listExternalMCPServers() {
|
|
1915
|
+
const serverStatuses = this.externalServerManager.getServerStatuses();
|
|
1916
|
+
const allServers = this.externalServerManager.listServers();
|
|
1917
|
+
return serverStatuses.map((health) => {
|
|
1918
|
+
const server = allServers.find((s) => s.id === health.serverId);
|
|
1919
|
+
return {
|
|
1920
|
+
serverId: health.serverId,
|
|
1921
|
+
status: health.status,
|
|
1922
|
+
toolCount: health.toolCount,
|
|
1923
|
+
uptime: health.performance.uptime,
|
|
1924
|
+
isHealthy: health.isHealthy,
|
|
1925
|
+
config: server || {},
|
|
1926
|
+
};
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Get external MCP server status
|
|
1931
|
+
* @param serverId - ID of the server
|
|
1932
|
+
* @returns Server instance or undefined if not found
|
|
1933
|
+
*/
|
|
1934
|
+
getExternalMCPServer(serverId) {
|
|
1935
|
+
return this.externalServerManager.getServer(serverId);
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Execute a tool from an external MCP server
|
|
1939
|
+
* @param serverId - ID of the server
|
|
1940
|
+
* @param toolName - Name of the tool
|
|
1941
|
+
* @param parameters - Tool parameters
|
|
1942
|
+
* @param options - Execution options
|
|
1943
|
+
* @returns Tool execution result
|
|
1944
|
+
*/
|
|
1945
|
+
async executeExternalMCPTool(serverId, toolName, parameters, options) {
|
|
1946
|
+
try {
|
|
1947
|
+
mcpLogger.debug(`[NeuroLink] Executing external MCP tool: ${toolName} on ${serverId}`);
|
|
1948
|
+
const result = await this.externalServerManager.executeTool(serverId, toolName, parameters, options);
|
|
1949
|
+
mcpLogger.debug(`[NeuroLink] External MCP tool executed successfully: ${toolName}`);
|
|
1950
|
+
return result;
|
|
1951
|
+
}
|
|
1952
|
+
catch (error) {
|
|
1953
|
+
mcpLogger.error(`[NeuroLink] External MCP tool execution failed: ${toolName}`, error);
|
|
1954
|
+
throw error;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
/**
|
|
1958
|
+
* Get all tools from external MCP servers
|
|
1959
|
+
* @returns Array of external tool information
|
|
1960
|
+
*/
|
|
1961
|
+
getExternalMCPTools() {
|
|
1962
|
+
return this.externalServerManager.getAllTools();
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Get tools from a specific external MCP server
|
|
1966
|
+
* @param serverId - ID of the server
|
|
1967
|
+
* @returns Array of tool information for the server
|
|
1968
|
+
*/
|
|
1969
|
+
getExternalMCPServerTools(serverId) {
|
|
1970
|
+
return this.externalServerManager.getServerTools(serverId);
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Test connection to an external MCP server
|
|
1974
|
+
* @param config - Server configuration to test
|
|
1975
|
+
* @returns Test result with connection status
|
|
1976
|
+
*/
|
|
1977
|
+
async testExternalMCPConnection(config) {
|
|
1978
|
+
try {
|
|
1979
|
+
const { MCPClientFactory } = await import("./mcp/mcpClientFactory.js");
|
|
1980
|
+
const testResult = await MCPClientFactory.testConnection(config, 10000);
|
|
1981
|
+
return {
|
|
1982
|
+
success: testResult.success,
|
|
1983
|
+
error: testResult.error,
|
|
1984
|
+
toolCount: testResult.capabilities ? 1 : 0, // Basic indication
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
catch (error) {
|
|
1988
|
+
return {
|
|
1989
|
+
success: false,
|
|
1990
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Get external MCP server manager statistics
|
|
1996
|
+
* @returns Statistics about external servers and tools
|
|
1997
|
+
*/
|
|
1998
|
+
getExternalMCPStatistics() {
|
|
1999
|
+
return this.externalServerManager.getStatistics();
|
|
2000
|
+
}
|
|
2001
|
+
/**
|
|
2002
|
+
* Shutdown all external MCP servers
|
|
2003
|
+
* Called automatically on process exit
|
|
2004
|
+
*/
|
|
2005
|
+
async shutdownExternalMCPServers() {
|
|
2006
|
+
try {
|
|
2007
|
+
mcpLogger.info("[NeuroLink] Shutting down all external MCP servers...");
|
|
2008
|
+
// First, unregister all external MCP tools from the main tool registry
|
|
2009
|
+
this.unregisterAllExternalMCPToolsFromRegistry();
|
|
2010
|
+
// Then shutdown the external server manager
|
|
2011
|
+
await this.externalServerManager.shutdown();
|
|
2012
|
+
mcpLogger.info("[NeuroLink] All external MCP servers shut down successfully");
|
|
2013
|
+
}
|
|
2014
|
+
catch (error) {
|
|
2015
|
+
mcpLogger.error("[NeuroLink] Error shutting down external MCP servers:", error);
|
|
2016
|
+
throw error;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Convert external MCP tools to Vercel AI SDK tool format
|
|
2021
|
+
* This allows AI providers to use external tools directly
|
|
2022
|
+
*/
|
|
2023
|
+
convertExternalMCPToolsToAISDKFormat() {
|
|
2024
|
+
const externalTools = this.externalServerManager.getAllTools();
|
|
2025
|
+
const aiSDKTools = {};
|
|
2026
|
+
for (const tool of externalTools) {
|
|
2027
|
+
if (tool.isAvailable) {
|
|
2028
|
+
// Create tool definition without parameters schema to avoid Zod issues
|
|
2029
|
+
// The AI provider will handle parameters dynamically based on the tool description
|
|
2030
|
+
const toolDefinition = {
|
|
2031
|
+
description: tool.description,
|
|
2032
|
+
execute: async (args) => {
|
|
2033
|
+
try {
|
|
2034
|
+
mcpLogger.debug(`[NeuroLink] Executing external MCP tool via AI SDK: ${tool.name}`, { args });
|
|
2035
|
+
const result = await this.externalServerManager.executeTool(tool.serverId, tool.name, args, { timeout: 30000 });
|
|
2036
|
+
mcpLogger.debug(`[NeuroLink] External MCP tool execution result: ${tool.name}`, {
|
|
2037
|
+
success: !!result,
|
|
2038
|
+
hasData: !!(result &&
|
|
2039
|
+
typeof result === "object" &&
|
|
2040
|
+
"content" in result),
|
|
2041
|
+
});
|
|
2042
|
+
return result;
|
|
2043
|
+
}
|
|
2044
|
+
catch (error) {
|
|
2045
|
+
mcpLogger.error(`[NeuroLink] External MCP tool execution failed: ${tool.name}`, error);
|
|
2046
|
+
throw error;
|
|
2047
|
+
}
|
|
2048
|
+
},
|
|
2049
|
+
};
|
|
2050
|
+
// Only add parameters if we have a valid schema - otherwise omit it entirely
|
|
2051
|
+
// This prevents Zod schema parsing errors
|
|
2052
|
+
aiSDKTools[tool.name] = toolDefinition;
|
|
2053
|
+
mcpLogger.debug(`[NeuroLink] Converted external MCP tool to AI SDK format: ${tool.name} from server ${tool.serverId}`);
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
mcpLogger.info(`[NeuroLink] Converted ${Object.keys(aiSDKTools).length} external MCP tools to AI SDK format`);
|
|
2057
|
+
return aiSDKTools;
|
|
2058
|
+
}
|
|
2059
|
+
/**
|
|
2060
|
+
* Convert JSON Schema to AI SDK compatible format
|
|
2061
|
+
* For now, we'll skip schema validation and let the AI SDK handle parameters dynamically
|
|
2062
|
+
*/
|
|
2063
|
+
convertJSONSchemaToAISDKFormat(inputSchema) {
|
|
2064
|
+
// The simplest approach: don't provide parameters schema
|
|
2065
|
+
// This lets the AI SDK handle the tool without schema validation
|
|
2066
|
+
// Tools will still work, they just won't have strict parameter validation
|
|
2067
|
+
return undefined;
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Unregister external MCP tools from a specific server
|
|
2071
|
+
*/
|
|
2072
|
+
unregisterExternalMCPToolsFromRegistry(serverId) {
|
|
2073
|
+
try {
|
|
2074
|
+
const externalTools = this.externalServerManager.getServerTools(serverId);
|
|
2075
|
+
for (const tool of externalTools) {
|
|
2076
|
+
toolRegistry.removeTool(tool.name);
|
|
2077
|
+
mcpLogger.debug(`[NeuroLink] Unregistered external MCP tool from main registry: ${tool.name}`);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
catch (error) {
|
|
2081
|
+
mcpLogger.error(`[NeuroLink] Failed to unregister external MCP tools from registry for server ${serverId}:`, error);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
/**
|
|
2085
|
+
* Unregister a specific external MCP tool from the main registry
|
|
2086
|
+
*/
|
|
2087
|
+
unregisterExternalMCPToolFromRegistry(toolName) {
|
|
2088
|
+
try {
|
|
2089
|
+
toolRegistry.removeTool(toolName);
|
|
2090
|
+
mcpLogger.debug(`[NeuroLink] Unregistered external MCP tool from main registry: ${toolName}`);
|
|
2091
|
+
}
|
|
2092
|
+
catch (error) {
|
|
2093
|
+
mcpLogger.error(`[NeuroLink] Failed to unregister external MCP tool ${toolName} from registry:`, error);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* Unregister all external MCP tools from the main registry
|
|
2098
|
+
*/
|
|
2099
|
+
unregisterAllExternalMCPToolsFromRegistry() {
|
|
2100
|
+
try {
|
|
2101
|
+
const externalTools = this.externalServerManager.getAllTools();
|
|
2102
|
+
for (const tool of externalTools) {
|
|
2103
|
+
toolRegistry.removeTool(tool.name);
|
|
2104
|
+
}
|
|
2105
|
+
mcpLogger.debug(`[NeuroLink] Unregistered ${externalTools.length} external MCP tools from main registry`);
|
|
2106
|
+
}
|
|
2107
|
+
catch (error) {
|
|
2108
|
+
mcpLogger.error("[NeuroLink] Failed to unregister all external MCP tools from registry:", error);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
1681
2111
|
}
|
|
1682
2112
|
// Create default instance
|
|
1683
2113
|
export const neurolink = new NeuroLink();
|