UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and

929 lines (928 loc) 40.3 kB
/** * NeuroLink - Unified AI Interface with Real MCP Tool Integration * * REDESIGNED FALLBACK CHAIN - NO CIRCULAR DEPENDENCIES * Enhanced AI provider system with natural MCP tool access. * Uses real MCP infrastructure for tool discovery and execution. */ // Load environment variables from .env file (critical for SDK usage) import { config as dotenvConfig } from "dotenv"; try { dotenvConfig(); // Load .env from current working directory } catch (error) { // Environment variables should be set externally in production } import { AIProviderFactory } from "./core/factory.js"; import { mcpLogger } from "./utils/logger.js"; import { SYSTEM_LIMITS } from "./core/constants.js"; import pLimit from "p-limit"; import { toolRegistry } from "./mcp/toolRegistry.js"; import { logger } from "./utils/logger.js"; import { getBestProvider } from "./utils/providerUtils.js"; import { ProviderRegistry } from "./factories/providerRegistry.js"; import { validateTool, createMCPServerFromTools, } from "./sdk/toolRegistration.js"; // Core types imported from core/types.js export class NeuroLink { mcpInitialized = false; // Tool registration support customTools = new Map(); inMemoryServers = new Map(); constructor() { // SDK always disables manual MCP config for security ProviderRegistry.setOptions({ enableManualMCP: false, }); } /** * Initialize MCP registry with enhanced error handling and resource cleanup * Uses isolated async context to prevent hanging */ async initializeMCP() { if (this.mcpInitialized) { return; } try { mcpLogger.debug("[NeuroLink] Starting isolated MCP initialization..."); // Initialize tool registry with timeout protection const initTimeout = 3000; // 3 second timeout await Promise.race([ Promise.resolve(), // toolRegistry doesn't need explicit initialization new Promise((_, reject) => { setTimeout(() => reject(new Error("MCP initialization timeout")), initTimeout); }), ]); // Register all providers with lazy loading support await ProviderRegistry.registerAllProviders(); this.mcpInitialized = true; mcpLogger.debug("[NeuroLink] MCP initialization completed successfully"); } catch (error) { mcpLogger.warn("[NeuroLink] MCP initialization failed", { error: error instanceof Error ? error.message : String(error), }); // Continue without MCP - graceful degradation } } /** * MAIN ENTRY POINT: Enhanced generate method with new function signature * Replaces both generateText and legacy methods */ async generate(optionsOrPrompt) { const startTime = Date.now(); // Convert string prompt to full options const options = typeof optionsOrPrompt === "string" ? { input: { text: optionsOrPrompt } } : optionsOrPrompt; // Validate prompt if (!options.input?.text || typeof options.input.text !== "string") { throw new Error("Input text is required and must be a non-empty string"); } // Convert to internal TextGenerationOptions for compatibility const textOptions = { prompt: options.input.text, provider: options.provider, model: options.model, temperature: options.temperature, maxTokens: options.maxTokens, systemPrompt: options.systemPrompt, disableTools: options.disableTools, // FIX: Pass disableTools flag // 🔧 FIX: Include analytics and evaluation options! enableAnalytics: options.enableAnalytics, enableEvaluation: options.enableEvaluation, context: options.context, evaluationDomain: options.evaluationDomain, toolUsageContext: options.toolUsageContext, }; // Use redesigned generation logic const textResult = await this.generateTextInternal(textOptions); // Convert back to GenerateResult const generateResult = { content: textResult.content, provider: textResult.provider, model: textResult.model, usage: textResult.usage ? { inputTokens: textResult.usage.promptTokens || 0, outputTokens: textResult.usage.completionTokens || 0, totalTokens: textResult.usage.totalTokens || 0, } : undefined, responseTime: textResult.responseTime, toolsUsed: textResult.toolsUsed, toolExecutions: textResult.toolExecutions?.map((te) => { const teRecord = te; return { name: te.toolName || teRecord.name || "", input: teRecord.input || teRecord.args || {}, output: teRecord.output || teRecord.result || (te.success ? "success" : "failed"), duration: te.executionTime || teRecord.duration || 0, }; }), enhancedWithTools: textResult.enhancedWithTools, availableTools: textResult.availableTools?.map((tool) => { const toolRecord = tool; return { name: tool.name || "", description: tool.description || "", parameters: toolRecord.parameters || toolRecord.schema || {}, }; }), analytics: textResult.analytics, evaluation: textResult.evaluation ? { ...textResult.evaluation, isOffTopic: textResult.evaluation .isOffTopic ?? false, alertSeverity: textResult.evaluation.alertSeverity ?? "none", reasoning: textResult.evaluation.reasoning ?? "No evaluation provided", evaluationModel: textResult.evaluation .evaluationModel ?? "unknown", evaluationTime: textResult.evaluation .evaluationTime ?? Date.now(), } : undefined, }; return generateResult; } /** * BACKWARD COMPATIBILITY: Legacy generateText method * Internally calls generate() and converts result format */ async generateText(options) { // Validate required parameters for backward compatibility if (!options.prompt || typeof options.prompt !== "string" || options.prompt.trim() === "") { throw new Error("GenerateText options must include prompt as a non-empty string"); } // Use internal generation method directly return await this.generateTextInternal(options); } /** * REDESIGNED INTERNAL GENERATION - NO CIRCULAR DEPENDENCIES * * This method implements a clean fallback chain: * 1. Try MCP-enhanced generation if available * 2. Fall back to direct provider generation * 3. No recursive calls - each method has a specific purpose */ async generateTextInternal(options) { const startTime = Date.now(); const functionTag = "NeuroLink.generateTextInternal"; logger.debug(`[${functionTag}] Starting generation`, { provider: options.provider || "auto", promptLength: options.prompt?.length || 0, }); try { // Try MCP-enhanced generation first (if not explicitly disabled) if (!options.disableTools) { try { const mcpResult = await this.tryMCPGeneration(options); if (mcpResult && mcpResult.content) { logger.debug(`[${functionTag}] MCP generation successful`); return mcpResult; } } catch (error) { logger.debug(`[${functionTag}] MCP generation failed, falling back`, { error: error instanceof Error ? error.message : String(error), }); } } // Fall back to direct provider generation const directResult = await this.directProviderGeneration(options); logger.debug(`[${functionTag}] Direct generation successful`); return directResult; } catch (error) { logger.error(`[${functionTag}] All generation methods failed`, { error: error instanceof Error ? error.message : String(error), }); throw error; } } /** * Try MCP-enhanced generation (no fallback recursion) */ async tryMCPGeneration(options) { const functionTag = "NeuroLink.tryMCPGeneration"; const startTime = Date.now(); // 🔧 FIX: Add proper timing try { // Initialize MCP if needed await this.initializeMCP(); if (!this.mcpInitialized) { return null; // Skip MCP if not available } // Context creation removed - was never used // Determine provider const providerName = options.provider === "auto" || !options.provider ? await getBestProvider() : options.provider; // Get available tools let availableTools = []; try { const allTools = await toolRegistry.listTools(); availableTools = allTools.map((tool) => ({ name: tool.name || "Unknown", description: tool.description || "No description available", server: tool.server || "Unknown", category: tool.category, })); } catch (error) { mcpLogger.warn(`[${functionTag}] Failed to get tools`, { error }); } // Create tool-aware system prompt const enhancedSystemPrompt = this.createToolAwareSystemPrompt(options.systemPrompt, availableTools); // Create provider and generate const provider = await AIProviderFactory.createProvider(providerName, options.model, !options.disableTools, // Pass disableTools as inverse of enableMCP this); const result = await provider.generate({ ...options, systemPrompt: enhancedSystemPrompt, }); const responseTime = Date.now() - startTime; // 🔧 FIX: Proper timing calculation // Check if result is meaningful if (!result || !result.content || result.content.trim().length === 0) { return null; // Let caller fall back to direct generation } // Return enhanced result return { content: result.content, provider: providerName, usage: result.usage, responseTime, toolsUsed: result.toolsUsed || [], enhancedWithTools: true, availableTools: availableTools.length > 0 ? availableTools : undefined, // Include analytics and evaluation from BaseProvider analytics: result.analytics, evaluation: result.evaluation, }; } catch (error) { mcpLogger.warn(`[${functionTag}] MCP generation failed`, { error: error instanceof Error ? error.message : String(error), }); return null; // Let caller fall back } } /** * Direct provider generation (no MCP, no recursion) */ async directProviderGeneration(options) { const startTime = Date.now(); const functionTag = "NeuroLink.directProviderGeneration"; // Define provider priority for fallback const providerPriority = [ "openai", "vertex", "bedrock", "anthropic", "azure", "google-ai", "huggingface", "ollama", ]; const requestedProvider = options.provider === "auto" ? undefined : options.provider; // If specific provider requested, only use that provider (no fallback) const tryProviders = requestedProvider ? [requestedProvider] : providerPriority; logger.debug(`[${functionTag}] Starting direct generation`, { requestedProvider: requestedProvider || "auto", tryProviders, allowFallback: !requestedProvider, }); let lastError = null; // Try each provider in order for (const providerName of tryProviders) { try { logger.debug(`[${functionTag}] Attempting provider: ${providerName}`); const provider = await AIProviderFactory.createProvider(providerName, options.model, !options.disableTools, // Pass disableTools as inverse of enableMCP this); const result = await provider.generate(options); const responseTime = Date.now() - startTime; if (!result) { throw new Error(`Provider ${providerName} returned null result`); } logger.debug(`[${functionTag}] Provider ${providerName} succeeded`, { responseTime, contentLength: result.content?.length || 0, }); return { content: result.content || "", provider: providerName, model: result.model, usage: result.usage, responseTime, toolsUsed: result.toolsUsed || [], enhancedWithTools: false, analytics: result.analytics, evaluation: result.evaluation, }; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); logger.warn(`[${functionTag}] Provider ${providerName} failed`, { error: lastError.message, }); // Continue to next provider } } // All providers failed const responseTime = Date.now() - startTime; logger.error(`[${functionTag}] All providers failed`, { triedProviders: tryProviders, lastError: lastError?.message, responseTime, }); throw new Error(`Failed to generate text with all providers. Last error: ${lastError?.message || "Unknown error"}`); } /** * Create tool-aware system prompt that informs AI about available tools */ createToolAwareSystemPrompt(originalSystemPrompt, availableTools) { if (availableTools.length === 0) { return originalSystemPrompt || ""; } const toolDescriptions = availableTools .map((tool) => `- ${tool.name}: ${tool.description} (from ${tool.server})`) .join("\n"); 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.`; return (originalSystemPrompt || "") + toolPrompt; } /** * BACKWARD COMPATIBILITY: Legacy streamText method * Internally calls stream() and converts result format */ async streamText(prompt, options) { // Convert legacy format to new StreamOptions const streamOptions = { input: { text: prompt }, ...options, }; // Call the new stream method const result = await this.stream(streamOptions); // Convert StreamResult to simple string async iterable async function* stringStream() { for await (const chunk of result.stream) { yield chunk.content; } } return stringStream(); } /** * PRIMARY METHOD: Stream content using AI (recommended for new code) * Future-ready for multi-modal capabilities with current text focus */ async stream(options) { const startTime = Date.now(); const functionTag = "NeuroLink.stream"; // Validate input if (!options?.input?.text || typeof options.input.text !== "string" || options.input.text.trim() === "") { throw new Error("Stream options must include input.text as a non-empty string"); } // Initialize MCP if needed await this.initializeMCP(); // Context creation removed - was never used // Determine provider to use const providerName = options.provider === "auto" || !options.provider ? await getBestProvider() : options.provider; try { mcpLogger.debug(`[${functionTag}] Starting MCP-enabled streaming`, { provider: providerName, prompt: (options.input.text?.substring(0, 100) || "No text") + "...", }); // Create provider using the same factory pattern as generate const provider = await AIProviderFactory.createBestProvider(providerName, options.model, true, this); // Call the provider's stream method directly const streamResult = await provider.stream(options); // Extract the stream from the result const stream = streamResult.stream; const responseTime = Date.now() - startTime; mcpLogger.debug(`[${functionTag}] MCP-enabled streaming completed`, { responseTime, provider: providerName, }); // Convert to StreamResult format - Include analytics and evaluation from provider return { stream, provider: providerName, model: options.model, usage: streamResult.usage, finishReason: streamResult.finishReason, toolCalls: streamResult.toolCalls, toolResults: streamResult.toolResults, analytics: streamResult.analytics, // 🔧 FIX: Pass through analytics data evaluation: streamResult.evaluation, // 🔧 FIX: Pass through evaluation data metadata: { streamId: `neurolink-${Date.now()}`, startTime, responseTime, }, }; } catch (error) { // Fall back to regular streaming if MCP fails mcpLogger.warn(`[${functionTag}] MCP streaming failed, falling back to regular`, { error: error instanceof Error ? error.message : String(error), }); // Use factory to create provider without MCP const provider = await AIProviderFactory.createBestProvider(providerName, options.model, false, // Disable MCP for fallback this); const streamResult = await provider.stream(options); const responseTime = Date.now() - startTime; return { stream: streamResult.stream, provider: providerName, model: options.model, usage: streamResult.usage, finishReason: streamResult.finishReason, toolCalls: streamResult.toolCalls, toolResults: streamResult.toolResults, analytics: streamResult.analytics, // 🔧 FIX: Pass through analytics data in fallback evaluation: streamResult.evaluation, // 🔧 FIX: Pass through evaluation data in fallback metadata: { streamId: `neurolink-${Date.now()}`, startTime, responseTime, fallback: true, }, }; } } // ======================================== // Tool Registration API // ======================================== /** * Register a custom tool that will be available to all AI providers * @param name - Unique name for the tool * @param tool - Tool configuration */ registerTool(name, tool) { try { // Validate tool configuration validateTool(name, tool); // Store the tool this.customTools.set(name, tool); // Convert to MCP server format for integration const serverId = `custom-tool-${name}`; const mcpServer = createMCPServerFromTools(serverId, { [name]: tool }, { title: `Custom Tool: ${name}`, category: "custom", }); // Store as in-memory server this.inMemoryServers.set(serverId, mcpServer); logger.info(`Registered custom tool: ${name}`); } catch (error) { logger.error(`Failed to register tool ${name}:`, error); throw error; } } /** * Register multiple tools at once * @param tools - Object mapping tool names to configurations */ registerTools(tools) { for (const [name, tool] of Object.entries(tools)) { this.registerTool(name, tool); } } /** * Unregister a custom tool * @param name - Name of the tool to remove * @returns true if the tool was removed, false if it didn't exist */ unregisterTool(name) { const removed = this.customTools.delete(name); if (removed) { const serverId = `custom-tool-${name}`; this.inMemoryServers.delete(serverId); logger.info(`Unregistered custom tool: ${name}`); } return removed; } /** * Get all registered custom tools * @returns Map of tool names to configurations */ getCustomTools() { return new Map(this.customTools); } /** * Add an in-memory MCP server (from git diff) * Allows registration of pre-instantiated server objects * @param serverId - Unique identifier for the server * @param config - Server configuration */ async addInMemoryMCPServer(serverId, config) { try { mcpLogger.debug(`[NeuroLink] Registering in-memory MCP server: ${serverId}`); // Validate server configuration if (!config.server) { throw new Error(`Server object is required for ${serverId}`); } // Store in registry this.inMemoryServers.set(serverId, config); mcpLogger.info(`[NeuroLink] Successfully registered in-memory server: ${serverId}`, { category: config.category, provider: config.metadata?.provider, version: config.metadata?.version, }); } catch (error) { mcpLogger.error(`[NeuroLink] Failed to register in-memory server ${serverId}:`, error); throw error; } } /** * Get all registered in-memory servers * @returns Map of server IDs to configurations */ getInMemoryServers() { return new Map(this.inMemoryServers); } /** * Execute a specific tool by name (from git diff) * Supports both custom tools and MCP server tools * @param toolName - Name of the tool to execute * @param params - Parameters to pass to the tool * @param options - Execution options * @returns Tool execution result */ async executeTool(toolName, params = {}, options) { const functionTag = "NeuroLink.executeTool"; try { mcpLogger.debug(`[${functionTag}] Executing tool: ${toolName}`, { toolName, params, options, }); // First check custom tools if (this.customTools.has(toolName)) { const tool = this.customTools.get(toolName); const context = { sessionId: `direct-execution-${Date.now()}`, logger, }; const startTime = Date.now(); const result = await tool.execute(params, context); const executionTime = Date.now() - startTime; mcpLogger.debug(`[${functionTag}] Custom tool executed successfully`, { toolName, executionTime, }); return result; } // Then check in-memory servers for (const [serverId, serverConfig] of this.inMemoryServers.entries()) { const server = serverConfig.server; // Check if this server has the requested tool if (server && server.tools) { const tool = server.tools instanceof Map ? server.tools.get(toolName) : server.tools[toolName]; if (tool && typeof tool.execute === "function") { mcpLogger.debug(`[${functionTag}] Found tool in in-memory server: ${serverId}`); const startTime = Date.now(); try { const result = await tool.execute(params); const executionTime = Date.now() - startTime; mcpLogger.debug(`[${functionTag}] Tool executed successfully`, { toolName, serverId, executionTime, }); // Handle MCP-style results if (result && typeof result === "object" && "success" in result) { if (result.success) { return result.data; } else { throw new Error(result.error || "Tool execution failed"); } } return result; } catch (toolError) { mcpLogger.error(`[${functionTag}] Tool execution failed`, { toolName, serverId, error: toolError instanceof Error ? toolError.message : String(toolError), }); throw toolError; } } } } // If not found in custom tools or in-memory servers, try unified registry try { // Built-in tools initialization simplified mcpLogger.debug(`[${functionTag}] MCP initialization simplified`); // Create minimal execution context for external tools const context = { sessionId: `neurolink-tool-${Date.now()}`, userId: "neurolink-user", }; const result = (await toolRegistry.executeTool(toolName, params, context)); return result; } catch (error) { mcpLogger.warn(`[${functionTag}] External tool execution failed`, { toolName, error: error instanceof Error ? error.message : String(error), }); throw error; } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); mcpLogger.error(`[${functionTag}] Tool execution failed: ${errorMessage}`, { toolName, error: errorMessage, }); throw new Error(`Failed to execute tool '${toolName}': ${errorMessage}`); } } /** * Get all available tools including custom and in-memory ones * @returns Array of available tools with metadata */ async getAllAvailableTools() { // MCP registry already includes direct tools, so just return MCP tools // This prevents duplication since direct tools are auto-registered in MCP const mcpTools = await toolRegistry.listTools(); return mcpTools; } // ============================================================================ // PROVIDER DIAGNOSTICS - SDK-First Architecture // ============================================================================ /** * Get comprehensive status of all AI providers * Primary method for provider health checking and diagnostics */ async getProviderStatus(options) { // 🔧 PERFORMANCE: Track memory and timing for provider status checks const { MemoryManager } = await import("./utils/performance.js"); const startMemory = MemoryManager.getMemoryUsageMB(); // CRITICAL FIX: Ensure providers are registered before testing if (!options?.quiet) { mcpLogger.debug("🔍 DEBUG: Initializing MCP for provider status..."); } await this.initializeMCP(); if (!options?.quiet) { mcpLogger.debug("🔍 DEBUG: MCP initialized:", this.mcpInitialized); } const { AIProviderFactory } = await import("./core/factory.js"); const { hasProviderEnvVars } = await import("./utils/providerUtils.js"); const providers = [ "openai", "bedrock", "vertex", "googleVertex", "anthropic", "azure", "google-ai", "huggingface", "ollama", "mistral", ]; // 🚀 PERFORMANCE FIX: Test providers with controlled concurrency // This reduces total time from 16s (sequential) to ~3s (parallel) while preventing resource exhaustion const limit = pLimit(SYSTEM_LIMITS.DEFAULT_CONCURRENCY_LIMIT); const providerTests = providers.map((providerName) => limit(async () => { const startTime = Date.now(); try { // Check if provider has required environment variables const hasEnvVars = await this.hasProviderEnvVars(providerName); if (!hasEnvVars && providerName !== "ollama") { return { provider: providerName, status: "not-configured", configured: false, authenticated: false, error: "Missing required environment variables", responseTime: Date.now() - startTime, }; } // Special handling for Ollama if (providerName === "ollama") { try { const response = await fetch("http://localhost:11434/api/tags", { method: "GET", signal: AbortSignal.timeout(2000), }); if (!response.ok) { throw new Error("Ollama service not responding"); } const { models } = await response.json(); const defaultOllamaModel = "llama3.2:latest"; const modelIsAvailable = models.some((m) => m.name === defaultOllamaModel); if (modelIsAvailable) { return { provider: providerName, status: "working", configured: true, authenticated: true, responseTime: Date.now() - startTime, model: defaultOllamaModel, }; } else { return { provider: providerName, status: "failed", configured: true, authenticated: false, error: `Ollama service running but model '${defaultOllamaModel}' not found`, responseTime: Date.now() - startTime, }; } } catch (error) { return { provider: providerName, status: "failed", configured: false, authenticated: false, error: error instanceof Error ? error.message : "Ollama service not running", responseTime: Date.now() - startTime, }; } } // Test other providers with actual generation call const testTimeout = 5000; const testPromise = this.testProviderConnection(providerName); const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error("Provider test timeout (5s)")), testTimeout); }); await Promise.race([testPromise, timeoutPromise]); return { provider: providerName, status: "working", configured: true, authenticated: true, responseTime: Date.now() - startTime, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { provider: providerName, status: "failed", configured: true, authenticated: false, error: errorMessage, responseTime: Date.now() - startTime, }; } })); // Wait for all provider tests to complete in parallel const results = await Promise.all(providerTests); // 🔧 PERFORMANCE: Track memory usage and suggest cleanup if needed const endMemory = MemoryManager.getMemoryUsageMB(); const memoryDelta = endMemory.heapUsed - startMemory.heapUsed; if (!options?.quiet && memoryDelta > 20) { mcpLogger.debug(`🔍 Memory usage: +${memoryDelta}MB (consider cleanup for large operations)`); } // Suggest garbage collection for large memory increases if (memoryDelta > 50) { MemoryManager.forceGC(); } return results; } /** * Test a specific AI provider's connectivity and authentication * @param providerName - Name of the provider to test * @returns Promise resolving to true if provider is working */ async testProvider(providerName) { try { await this.testProviderConnection(providerName); return true; } catch { return false; } } /** * Internal method to test provider connection with minimal generation call */ async testProviderConnection(providerName) { const { AIProviderFactory } = await import("./core/factory.js"); const provider = await AIProviderFactory.createProvider(providerName, null, false); await provider.generate({ prompt: "test", maxTokens: 1, disableTools: true, }); } /** * Check if a provider has required environment variables configured * @param providerName - Name of the provider to check * @returns True if provider has required environment variables */ async hasProviderEnvVars(providerName) { const { hasProviderEnvVars } = await import("./utils/providerUtils.js"); return hasProviderEnvVars(providerName); } /** * Get the best available AI provider based on configuration and availability * @param requestedProvider - Optional preferred provider name * @returns Promise resolving to the best provider name */ async getBestProvider(requestedProvider) { const { getBestProvider } = await import("./utils/providerUtils.js"); return getBestProvider(requestedProvider); } /** * Get list of all available AI provider names * @returns Array of supported provider names */ async getAvailableProviders() { const { getAvailableProviders } = await import("./utils/providerUtils.js"); return getAvailableProviders(); } /** * Validate if a provider name is supported * @param providerName - Provider name to validate * @returns True if provider name is valid */ async isValidProvider(providerName) { const { isValidProvider } = await import("./utils/providerUtils.js"); return isValidProvider(providerName); } // ============================================================================ // MCP DIAGNOSTICS - SDK-First Architecture // ============================================================================ /** * Get comprehensive MCP (Model Context Protocol) status information * @returns Promise resolving to MCP status details */ async getMCPStatus() { try { // Simplified MCP status - unified registry removed const allTools = await toolRegistry.listTools(); return { mcpInitialized: this.mcpInitialized, totalServers: 1, // Only tool registry now availableServers: 1, autoDiscoveredCount: 0, // No auto-discovery totalTools: allTools.length, autoDiscoveredServers: [], // No auto-discovery customToolsCount: this.customTools.size, inMemoryServersCount: this.inMemoryServers.size, }; } catch (error) { return { mcpInitialized: false, totalServers: 0, availableServers: 0, autoDiscoveredCount: 0, totalTools: 0, autoDiscoveredServers: [], customToolsCount: this.customTools.size, inMemoryServersCount: this.inMemoryServers.size, error: error instanceof Error ? error.message : String(error), }; } } /** * List all configured MCP servers with their status * @returns Promise resolving to array of MCP server information */ async listMCPServers() { // Simplified MCP servers listing - unified registry removed return []; } /** * Test connectivity to a specific MCP server * @param serverId - ID of the MCP server to test * @returns Promise resolving to true if server is reachable */ async testMCPServer(serverId) { // Simplified MCP server testing - unified registry removed return false; // No auto-discovery servers available } } // Create default instance export const neurolink = new NeuroLink(); export default neurolink;