/**
 * External MCP Server Manager
 * Handles lifecycle management of external MCP servers including:
 * - Process spawning and management
 * - Health monitoring and automatic restart
 * - Connection management and cleanup
 * - Tool discovery and registration
 */
import { EventEmitter } from "events";
import { mcpLogger } from "../utils/logger.js";
import { MCPClientFactory } from "./mcpClientFactory.js";
import { ToolDiscoveryService } from "./toolDiscoveryService.js";
import { toolRegistry } from "./toolRegistry.js";
import { HITLUserRejectedError, HITLTimeoutError } from "../hitl/hitlErrors.js";
import { detectCategory } from "../utils/mcpDefaults.js";
import { isObject, isNonNullObject } from "../utils/typeUtils.js";
import { TelemetryService } from "../telemetry/telemetryService.js";
import { tracers } from "../telemetry/tracers.js";
import { SpanStatusCode } from "@opentelemetry/api";
/**
 * Recursively substitute environment variables in strings
 * Replaces ${VAR_NAME} with the value from process.env.VAR_NAME
 * @param value - Value to process (string, object, array, or primitive)
 * @returns Processed value with environment variables substituted
 */
function substituteEnvVariables(value) {
    if (typeof value === "string") {
        // Replace ${VAR_NAME} with process.env.VAR_NAME
        return value.replace(/\$\{([^}]+)\}/g, (match, varName) => {
            const envValue = process.env[varName.trim()];
            if (envValue === undefined) {
                mcpLogger.warn(`[ExternalServerManager] Environment variable ${varName} is not defined, using empty string`);
                return "";
            }
            return envValue;
        });
    }
    if (Array.isArray(value)) {
        return value.map((item) => substituteEnvVariables(item));
    }
    if (isNonNullObject(value)) {
        const result = {};
        for (const [key, val] of Object.entries(value)) {
            result[key] = substituteEnvVariables(val);
        }
        return result;
    }
    return value;
}
/**
 * Sensitive CLI flag patterns whose following value should be masked in logs.
 */
const SENSITIVE_ARG_PATTERNS = /^--(api-key|token|secret|password|key|figma-api-key|access-token|auth|credential)$/i;
/**
 * Redact values that follow sensitive flags in a CLI args array.
 * Handles: "--api-key sk-abc", "--api-key=sk-abc", consecutive flags.
 * E.g. ["--api-key", "sk-abc123"] → ["--api-key", "[REDACTED]"]
 *      ["--api-key=sk-abc123"]    → ["--api-key=[REDACTED]"]
 */
function redactSensitiveArgs(args) {
    if (!args || args.length === 0) {
        return args;
    }
    const redacted = [...args];
    for (let i = 0; i < redacted.length; i++) {
        // Handle --flag=value inline form
        const eqIdx = redacted[i].indexOf("=");
        if (eqIdx !== -1) {
            const flag = redacted[i].substring(0, eqIdx);
            if (SENSITIVE_ARG_PATTERNS.test(flag)) {
                redacted[i] = `${flag}=[REDACTED]`;
            }
            continue;
        }
        // Handle --flag value (two-part form)
        if (SENSITIVE_ARG_PATTERNS.test(redacted[i]) &&
            i + 1 < redacted.length &&
            !redacted[i + 1].startsWith("--")) {
            redacted[i + 1] = "[REDACTED]";
            i++; // skip the redacted value
        }
    }
    return redacted;
}
/**
 * Type guard to validate if an object can be safely used as Record<string, JsonValue>
 */
function isValidJsonRecord(value) {
    if (!isObject(value)) {
        return false;
    }
    const record = value;
    return Object.values(record).every((val) => {
        // JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }
        if (val === null ||
            typeof val === "string" ||
            typeof val === "number" ||
            typeof val === "boolean") {
            return true;
        }
        if (Array.isArray(val)) {
            return val.every((item) => isValidJsonRecord(item) ||
                typeof item === "string" ||
                typeof item === "number" ||
                typeof item === "boolean" ||
                item === null);
        }
        if (isNonNullObject(val)) {
            return isValidJsonRecord(val);
        }
        return false;
    });
}
/**
 * Safely converts unknown metadata to Record<string, JsonValue> or returns undefined
 */
function safeMetadataConversion(metadata) {
    return isValidJsonRecord(metadata) ? metadata : undefined;
}
/**
 * Type guard to validate external MCP server configuration
 * Supports both stdio transport (requires command) and HTTP transport (requires url)
 */
function isValidExternalMCPServerConfig(config) {
    if (!isNonNullObject(config)) {
        return false;
    }
    const record = config;
    // Validate blockedTools array contains only strings
    if (record.blockedTools !== undefined) {
        if (!Array.isArray(record.blockedTools)) {
            return false;
        }
        if (!record.blockedTools.every((item) => typeof item === "string")) {
            return false;
        }
    }
    // Must have either command (for stdio) or url (for HTTP/SSE transport)
    const hasCommand = typeof record.command === "string";
    const hasUrl = typeof record.url === "string";
    if (!hasCommand && !hasUrl) {
        return false;
    }
    return ((record.command === undefined || typeof record.command === "string") &&
        (record.args === undefined || Array.isArray(record.args)) &&
        (record.env === undefined || isNonNullObject(record.env)) &&
        (record.transport === undefined || typeof record.transport === "string") &&
        (record.timeout === undefined || typeof record.timeout === "number") &&
        (record.retries === undefined || typeof record.retries === "number") &&
        (record.healthCheckInterval === undefined ||
            typeof record.healthCheckInterval === "number") &&
        (record.autoRestart === undefined ||
            typeof record.autoRestart === "boolean") &&
        (record.cwd === undefined || typeof record.cwd === "string") &&
        (record.url === undefined || typeof record.url === "string") &&
        (record.headers === undefined || isNonNullObject(record.headers)) &&
        (record.httpOptions === undefined || isNonNullObject(record.httpOptions)) &&
        (record.retryConfig === undefined || isNonNullObject(record.retryConfig)) &&
        (record.rateLimiting === undefined ||
            isNonNullObject(record.rateLimiting)) &&
        (record.metadata === undefined || isNonNullObject(record.metadata)));
}
/**
 * ExternalServerManager
 * Core class for managing external MCP servers
 */
export class ExternalServerManager extends EventEmitter {
    servers = new Map();
    config;
    isShuttingDown = false;
    toolDiscovery;
    enableMainRegistryIntegration;
    hitlManager; // Optional HITL manager for safety mechanisms
    constructor(config = {}, options = {}) {
        super();
        // Set defaults for configuration
        // Default timeout increased to 60s and made configurable via MCP_CLIENT_TIMEOUT
        // to accommodate MCP server startup latency (especially concurrent stdio servers)
        const defaultMcpTimeout = Math.max(5000, Number(process.env.MCP_CLIENT_TIMEOUT) || 60000);
        this.config = {
            maxServers: config.maxServers ?? 10,
            defaultTimeout: config.defaultTimeout ?? defaultMcpTimeout,
            defaultHealthCheckInterval: config.defaultHealthCheckInterval ?? 30000,
            enableAutoRestart: config.enableAutoRestart ?? true,
            maxRestartAttempts: config.maxRestartAttempts ?? 3,
            restartBackoffMultiplier: config.restartBackoffMultiplier ?? 2,
            enablePerformanceMonitoring: config.enablePerformanceMonitoring ?? true,
            logLevel: config.logLevel ?? "info",
        };
        // Disable main tool registry integration by default to prevent automatic tool execution
        this.enableMainRegistryIntegration =
            options.enableMainRegistryIntegration ?? false;
        // Initialize tool discovery service
        this.toolDiscovery = new ToolDiscoveryService();
        // Forward tool discovery events
        this.toolDiscovery.on("toolRegistered", (event) => {
            this.emit("toolDiscovered", {
                ...event,
                serverName: this.getServerName(event.serverId),
            });
        });
        this.toolDiscovery.on("toolUnregistered", (event) => {
            this.emit("toolRemoved", {
                ...event,
                serverName: this.getServerName(event.serverId),
            });
        });
        // Handle process cleanup
        process.on("SIGINT", () => this.shutdown());
        process.on("SIGTERM", () => this.shutdown());
        process.on("beforeExit", () => this.shutdown());
    }
    /**
     * Set HITL manager for human-in-the-loop safety mechanisms
     * @param hitlManager - HITL manager instance (optional, can be undefined to disable)
     */
    setHITLManager(hitlManager) {
        this.hitlManager = hitlManager;
        if (hitlManager && hitlManager.isEnabled()) {
            mcpLogger.info("[ExternalServerManager] HITL safety mechanisms enabled for external tool execution");
        }
        else {
            mcpLogger.debug("[ExternalServerManager] HITL safety mechanisms disabled or not configured");
        }
    }
    /**
     * Get current HITL manager
     */
    getHITLManager() {
        return this.hitlManager;
    }
    /**
     * Resolve the human-readable server name for an event payload.
     * Falls back to serverId if the instance or config.name isn't available.
     */
    getServerName(serverId) {
        const instance = this.servers.get(serverId);
        return instance?.config?.name || serverId;
    }
    /**
     * Load MCP server configurations from .mcp-config.json file with parallel loading support
     * Automatically registers servers found in the configuration
     * @param configPath Optional path to config file (defaults to .mcp-config.json in cwd)
     * @param options Loading options including parallel support
     * @returns Promise resolving to { serversLoaded, errors }
     */
    async loadMCPConfiguration(configPath, options = {}) {
        if (options.parallel) {
            return this.loadMCPConfigurationParallel(configPath);
        }
        return this.loadMCPConfigurationSequential(configPath);
    }
    /**
     * Load MCP servers in parallel for improved performance
     * @param configPath Optional path to config file (defaults to .mcp-config.json in cwd)
     * @returns Promise resolving to batch operation result
     */
    async loadMCPConfigurationParallel(configPath) {
        const fs = await import("fs");
        const path = await import("path");
        const finalConfigPath = configPath || path.join(process.cwd(), ".mcp-config.json");
        if (!fs.existsSync(finalConfigPath)) {
            mcpLogger.debug(`[ExternalServerManager] No MCP config found at ${finalConfigPath}`);
            return { serversLoaded: 0, errors: [] };
        }
        mcpLogger.debug(`[ExternalServerManager] Loading MCP configuration in PARALLEL mode from ${finalConfigPath}`);
        try {
            const configContent = fs.readFileSync(finalConfigPath, "utf8");
            const config = JSON.parse(configContent);
            if (!config.mcpServers || typeof config.mcpServers !== "object") {
                mcpLogger.debug("[ExternalServerManager] No mcpServers found in configuration");
                return { serversLoaded: 0, errors: [] };
            }
            // Create promises for all servers to start them concurrently
            const serverPromises = Object.entries(config.mcpServers).map(async ([serverId, serverConfig]) => {
                try {
                    // Validate and convert config format to MCPServerInfo
                    if (!isValidExternalMCPServerConfig(serverConfig)) {
                        throw new Error(`Invalid server config for ${serverId}: missing required properties or wrong types`);
                    }
                    const externalConfig = {
                        id: serverId,
                        name: serverId,
                        description: typeof serverConfig.description === "string"
                            ? serverConfig.description
                            : `External MCP server: ${serverId}`,
                        transport: typeof serverConfig.transport === "string"
                            ? serverConfig.transport
                            : "stdio",
                        status: "initializing",
                        tools: [],
                        command: typeof serverConfig.command === "string"
                            ? serverConfig.command
                            : undefined,
                        args: Array.isArray(serverConfig.args)
                            ? serverConfig.args
                            : [],
                        env: isNonNullObject(serverConfig.env)
                            ? substituteEnvVariables(serverConfig.env)
                            : {},
                        timeout: typeof serverConfig.timeout === "number"
                            ? serverConfig.timeout
                            : undefined,
                        retries: typeof serverConfig.retries === "number"
                            ? serverConfig.retries
                            : undefined,
                        healthCheckInterval: typeof serverConfig.healthCheckInterval === "number"
                            ? serverConfig.healthCheckInterval
                            : undefined,
                        autoRestart: typeof serverConfig.autoRestart === "boolean"
                            ? serverConfig.autoRestart
                            : undefined,
                        cwd: typeof serverConfig.cwd === "string"
                            ? serverConfig.cwd
                            : undefined,
                        url: typeof serverConfig.url === "string"
                            ? serverConfig.url
                            : undefined,
                        // HTTP transport-specific fields
                        headers: isNonNullObject(serverConfig.headers)
                            ? substituteEnvVariables(serverConfig.headers)
                            : undefined,
                        httpOptions: isNonNullObject(serverConfig.httpOptions)
                            ? serverConfig.httpOptions
                            : undefined,
                        retryConfig: isNonNullObject(serverConfig.retryConfig)
                            ? serverConfig.retryConfig
                            : undefined,
                        rateLimiting: isNonNullObject(serverConfig.rateLimiting)
                            ? serverConfig.rateLimiting
                            : undefined,
                        blockedTools: Array.isArray(serverConfig.blockedTools)
                            ? serverConfig.blockedTools
                            : undefined,
                        metadata: safeMetadataConversion(serverConfig.metadata),
                    };
                    const result = await this.addServer(serverId, externalConfig);
                    return { serverId, result };
                }
                catch (error) {
                    const errorMsg = `Failed to load MCP server ${serverId}: ${error instanceof Error ? error.message : String(error)}`;
                    mcpLogger.warn(`[ExternalServerManager] ${errorMsg}`);
                    return { serverId, error: errorMsg };
                }
            });
            // Start all servers concurrently and wait for completion
            const results = await Promise.allSettled(serverPromises);
            // Process results to count successes and collect errors
            let serversLoaded = 0;
            const errors = [];
            for (const result of results) {
                if (result.status === "fulfilled") {
                    const { serverId, result: serverResult, error } = result.value;
                    if (serverResult && serverResult.success) {
                        serversLoaded++;
                        mcpLogger.debug(`[ExternalServerManager] Successfully loaded MCP server in parallel: ${serverId}`);
                    }
                    else if (error) {
                        errors.push(error);
                    }
                    else if (serverResult && !serverResult.success) {
                        const errorMsg = `Failed to load server ${serverId}: ${serverResult.error}`;
                        errors.push(errorMsg);
                        mcpLogger.warn(`[ExternalServerManager] ${errorMsg}`);
                    }
                }
                else {
                    // Promise.allSettled rejected - this shouldn't happen with our error handling
                    const errorMsg = `Unexpected error during parallel loading: ${result.reason}`;
                    errors.push(errorMsg);
                    mcpLogger.error(`[ExternalServerManager] ${errorMsg}`);
                }
            }
            mcpLogger.info(`[ExternalServerManager] PARALLEL MCP configuration loading complete: ${serversLoaded} servers loaded, ${errors.length} errors`);
            return { serversLoaded, errors };
        }
        catch (error) {
            const errorMsg = `Failed to load MCP configuration in parallel mode: ${error instanceof Error ? error.message : String(error)}`;
            mcpLogger.error(`[ExternalServerManager] ${errorMsg}`);
            return { serversLoaded: 0, errors: [errorMsg] };
        }
    }
    /**
     * Load MCP servers sequentially (original implementation for backward compatibility)
     * @param configPath Optional path to config file (defaults to .mcp-config.json in cwd)
     * @returns Promise resolving to batch operation result
     */
    async loadMCPConfigurationSequential(configPath) {
        const fs = await import("fs");
        const path = await import("path");
        const finalConfigPath = configPath || path.join(process.cwd(), ".mcp-config.json");
        if (!fs.existsSync(finalConfigPath)) {
            mcpLogger.debug(`[ExternalServerManager] No MCP config found at ${finalConfigPath}`);
            return { serversLoaded: 0, errors: [] };
        }
        mcpLogger.debug(`[ExternalServerManager] Loading MCP configuration from ${finalConfigPath}`);
        try {
            const configContent = fs.readFileSync(finalConfigPath, "utf8");
            const config = JSON.parse(configContent);
            if (!config.mcpServers || typeof config.mcpServers !== "object") {
                mcpLogger.debug("[ExternalServerManager] No mcpServers found in configuration");
                return { serversLoaded: 0, errors: [] };
            }
            let serversLoaded = 0;
            const errors = [];
            for (const [serverId, serverConfig] of Object.entries(config.mcpServers)) {
                try {
                    // Validate and convert config format to MCPServerInfo
                    if (!isValidExternalMCPServerConfig(serverConfig)) {
                        throw new Error(`Invalid server config for ${serverId}: missing required properties or wrong types`);
                    }
                    const externalConfig = {
                        id: serverId,
                        name: serverId,
                        description: typeof serverConfig.description === "string"
                            ? serverConfig.description
                            : `External MCP server: ${serverId}`,
                        transport: typeof serverConfig.transport === "string"
                            ? serverConfig.transport
                            : "stdio",
                        status: "initializing",
                        tools: [],
                        command: typeof serverConfig.command === "string"
                            ? serverConfig.command
                            : undefined,
                        args: Array.isArray(serverConfig.args)
                            ? serverConfig.args
                            : [],
                        env: isNonNullObject(serverConfig.env)
                            ? substituteEnvVariables(serverConfig.env)
                            : {},
                        timeout: typeof serverConfig.timeout === "number"
                            ? serverConfig.timeout
                            : undefined,
                        retries: typeof serverConfig.retries === "number"
                            ? serverConfig.retries
                            : undefined,
                        healthCheckInterval: typeof serverConfig.healthCheckInterval === "number"
                            ? serverConfig.healthCheckInterval
                            : undefined,
                        autoRestart: typeof serverConfig.autoRestart === "boolean"
                            ? serverConfig.autoRestart
                            : undefined,
                        cwd: typeof serverConfig.cwd === "string"
                            ? serverConfig.cwd
                            : undefined,
                        url: typeof serverConfig.url === "string"
                            ? serverConfig.url
                            : undefined,
                        // HTTP transport-specific fields
                        headers: isNonNullObject(serverConfig.headers)
                            ? substituteEnvVariables(serverConfig.headers)
                            : undefined,
                        httpOptions: isNonNullObject(serverConfig.httpOptions)
                            ? serverConfig.httpOptions
                            : undefined,
                        retryConfig: isNonNullObject(serverConfig.retryConfig)
                            ? serverConfig.retryConfig
                            : undefined,
                        rateLimiting: isNonNullObject(serverConfig.rateLimiting)
                            ? serverConfig.rateLimiting
                            : undefined,
                        blockedTools: Array.isArray(serverConfig.blockedTools)
                            ? serverConfig.blockedTools
                            : undefined,
                        metadata: safeMetadataConversion(serverConfig.metadata),
                    };
                    const result = await this.addServer(serverId, externalConfig);
                    if (result.success) {
                        serversLoaded++;
                        mcpLogger.debug(`[ExternalServerManager] Successfully loaded MCP server: ${serverId}`);
                    }
                    else {
                        const error = `Failed to load server ${serverId}: ${result.error}`;
                        errors.push(error);
                        mcpLogger.warn(`[ExternalServerManager] ${error}`);
                    }
                }
                catch (error) {
                    const errorMsg = `Failed to load MCP server ${serverId}: ${error instanceof Error ? error.message : String(error)}`;
                    errors.push(errorMsg);
                    mcpLogger.warn(`[ExternalServerManager] ${errorMsg}`);
                    // Continue with other servers - don't let one failure break everything
                }
            }
            mcpLogger.info(`[ExternalServerManager] MCP configuration loading complete: ${serversLoaded} servers loaded, ${errors.length} errors`);
            return { serversLoaded, errors };
        }
        catch (error) {
            const errorMsg = `Failed to load MCP configuration: ${error instanceof Error ? error.message : String(error)}`;
            mcpLogger.error(`[ExternalServerManager] ${errorMsg}`);
            return { serversLoaded: 0, errors: [errorMsg] };
        }
    }
    /**
     * Validate external MCP server configuration
     */
    validateConfig(config) {
        const errors = [];
        const warnings = [];
        const suggestions = [];
        // Required fields validation
        if (!config.id || typeof config.id !== "string") {
            errors.push("Server ID is required and must be a string");
        }
        if (!["stdio", "sse", "websocket", "http"].includes(config.transport)) {
            errors.push("Transport must be one of: stdio, sse, websocket, http");
        }
        // Transport-specific validation
        if (config.transport === "stdio") {
            // stdio transport requires command
            if (!config.command || typeof config.command !== "string") {
                errors.push("Command is required and must be a string for stdio transport");
            }
            if (!Array.isArray(config.args)) {
                errors.push("Args must be an array");
            }
        }
        else if (config.transport === "sse" ||
            config.transport === "websocket" ||
            config.transport === "http") {
            // HTTP-based transports require URL
            if (!config.url || typeof config.url !== "string") {
                errors.push(`URL is required for ${config.transport} transport`);
            }
        }
        // Warnings for common issues
        if (config.timeout && config.timeout < 5000) {
            warnings.push("Timeout less than 5 seconds may cause connection issues");
        }
        if (config.retries && config.retries > 5) {
            warnings.push("High retry count may slow down error recovery");
        }
        // Suggestions for optimization
        if (!config.healthCheckInterval) {
            suggestions.push("Consider setting a health check interval for better reliability");
        }
        if (config.autoRestart === undefined) {
            suggestions.push("Consider enabling auto-restart for production use");
        }
        return {
            isValid: errors.length === 0,
            errors,
            warnings,
            suggestions,
        };
    }
    /**
     * Convert MCPServerInfo format (keeping for backward compatibility)
     * Helper function for transitioning to zero-conversion architecture
     */
    convertConfigToMCPServerInfo(serverId, config) {
        return {
            id: serverId,
            name: String(config.metadata?.title || serverId),
            description: `External MCP server (${config.transport})`,
            status: "initializing",
            transport: config.transport,
            command: config.command,
            args: config.args,
            env: config.env,
            tools: [], // Will be populated after server connection
            blockedTools: config.blockedTools,
            // Preserve top-level operational fields so startServer can read them
            timeout: config.timeout,
            retries: config.retries,
            healthCheckInterval: config.healthCheckInterval,
            autoRestart: config.autoRestart,
            cwd: config.cwd,
            url: config.url,
            metadata: {
                category: "external",
                ...(safeMetadataConversion(config.metadata) || {}),
            },
        };
    }
    async addServer(serverId, configOrServerInfo) {
        const startTime = Date.now();
        try {
            // Use MCPServerInfo directly (zero-conversion architecture)
            const serverInfo = "transport" in configOrServerInfo &&
                "command" in configOrServerInfo &&
                !("tools" in configOrServerInfo)
                ? this.convertConfigToMCPServerInfo(serverId, configOrServerInfo)
                : configOrServerInfo;
            // Check server limit
            if (this.servers.size >= this.config.maxServers) {
                return {
                    success: false,
                    error: `Maximum number of servers (${this.config.maxServers}) reached`,
                    serverId,
                    duration: Date.now() - startTime,
                };
            }
            // Validate configuration (for backward compatibility, create temporary config)
            const tempConfig = {
                id: serverId,
                name: serverInfo.name,
                description: serverInfo.description,
                transport: serverInfo.transport,
                status: serverInfo.status,
                tools: serverInfo.tools,
                command: serverInfo.command || "",
                args: serverInfo.args || [],
                env: serverInfo.env || {},
                timeout: serverInfo.timeout,
                retries: serverInfo.retries,
                healthCheckInterval: serverInfo.healthCheckInterval,
                autoRestart: serverInfo.autoRestart,
                cwd: serverInfo.cwd,
                url: serverInfo.url,
                blockedTools: serverInfo.blockedTools,
                metadata: safeMetadataConversion(serverInfo.metadata),
                // HTTP transport-specific fields
                headers: serverInfo.headers,
                httpOptions: serverInfo.httpOptions,
                retryConfig: serverInfo.retryConfig,
                rateLimiting: serverInfo.rateLimiting,
            };
            const validation = this.validateConfig(tempConfig);
            if (!validation.isValid) {
                return {
                    success: false,
                    error: `Configuration validation failed: ${validation.errors.join(", ")}`,
                    serverId,
                    duration: Date.now() - startTime,
                };
            }
            // Check for duplicate server ID
            if (this.servers.has(serverId)) {
                return {
                    success: false,
                    error: `Server with ID '${serverId}' already exists`,
                    serverId,
                    duration: Date.now() - startTime,
                };
            }
            mcpLogger.info(`[ExternalServerManager] Adding server: ${serverId}`, {
                command: serverInfo.command,
                transport: serverInfo.transport,
            });
            // Create server instance as RuntimeMCPServerInfo (transition to zero-conversion)
            const instance = {
                ...serverInfo,
                process: null,
                client: null,
                transportInstance: null,
                status: "initializing",
                reconnectAttempts: 0,
                maxReconnectAttempts: this.config.maxRestartAttempts,
                toolsMap: new Map(),
                metrics: {
                    totalConnections: 0,
                    totalDisconnections: 0,
                    totalErrors: 0,
                    totalToolCalls: 0,
                    averageResponseTime: 0,
                    lastResponseTime: 0,
                },
                config: tempConfig,
            };
            // Store the instance
            this.servers.set(serverId, instance);
            // Start the server
            await this.startServer(serverId);
            const finalInstance = this.servers.get(serverId);
            if (!finalInstance) {
                throw new Error(`Server ${serverId} not found after registration`);
            }
            // Convert RuntimeMCPServerInfo to ExternalMCPServerInstance for return
            const convertedInstance = {
                config: finalInstance.config,
                process: finalInstance.process,
                client: finalInstance.client,
                transport: finalInstance.transportInstance,
                status: finalInstance.status,
                lastError: finalInstance.lastError,
                startTime: finalInstance.startTime,
                lastHealthCheck: finalInstance.lastHealthCheck,
                reconnectAttempts: finalInstance.reconnectAttempts,
                maxReconnectAttempts: finalInstance.maxReconnectAttempts,
                tools: finalInstance.toolsMap,
                toolsArray: finalInstance.toolsArray,
                capabilities: finalInstance.capabilities,
                healthTimer: finalInstance.healthTimer,
                restartTimer: finalInstance.restartTimer,
                metrics: finalInstance.metrics,
            };
            return {
                success: true,
                data: convertedInstance,
                serverId,
                duration: Date.now() - startTime,
                metadata: {
                    timestamp: Date.now(),
                    operation: "addServer",
                    toolsDiscovered: finalInstance.tools.length,
                },
            };
        }
        catch (error) {
            mcpLogger.error(`[ExternalServerManager] Failed to add server ${serverId}:`, error);
            // Clean up if instance was created
            this.servers.delete(serverId);
            return {
                success: false,
                error: error instanceof Error ? error.message : String(error),
                serverId,
                duration: Date.now() - startTime,
            };
        }
    }
    /**
     * Remove an external MCP server
     */
    async removeServer(serverId) {
        const startTime = Date.now();
        try {
            const instance = this.servers.get(serverId);
            if (!instance) {
                return {
                    success: false,
                    error: `Server '${serverId}' not found`,
                    serverId,
                    duration: Date.now() - startTime,
                };
            }
            mcpLogger.info(`[ExternalServerManager] Removing server: ${serverId}`);
            // Capture name before deletion removes the instance
            const serverName = this.getServerName(serverId);
            // Stop the server
            await this.stopServer(serverId);
            // Remove from registry
            this.servers.delete(serverId);
            // Emit event
            this.emit("disconnected", {
                serverId,
                serverName,
                reason: "Manually removed",
                timestamp: new Date(),
            });
            return {
                success: true,
                serverId,
                duration: Date.now() - startTime,
                metadata: {
                    timestamp: Date.now(),
                    operation: "removeServer",
                },
            };
        }
        catch (error) {
            mcpLogger.error(`[ExternalServerManager] Failed to remove server ${serverId}:`, error);
            return {
                success: false,
                error: error instanceof Error ? error.message : String(error),
                serverId,
                duration: Date.now() - startTime,
            };
        }
    }
    /**
     * Start an external MCP server
     */
    async startServer(serverId) {
        const instance = this.servers.get(serverId);
        if (!instance) {
            throw new Error(`Server '${serverId}' not found`);
        }
        const config = instance.config;
        const span = tracers.mcp.startSpan("neurolink.mcp.server.start", {
            attributes: {
                "mcp.server_id": serverId,
                "mcp.transport": config.transport,
                "mcp.command_name": config.command
                    ? config.command.split(/[\\/]/).pop() || ""
                    : "",
                "mcp.command_present": Boolean(config.command),
            },
        });
        try {
            this.updateServerStatus(serverId, "connecting");
            mcpLogger.debug(`[ExternalServerManager] Starting server: ${serverId}`, {
                command: config.command,
                args: redactSensitiveArgs(config.args),
                transport: config.transport,
            });
            // Create MCP client using the factory
            const clientResult = await MCPClientFactory.createClient(config, config.timeout || this.config.defaultTimeout);
            if (!clientResult.success ||
                !clientResult.client ||
                !clientResult.transport) {
                throw new Error(`Failed to create MCP client: ${clientResult.error}`);
            }
            // Store client components
            instance.client = clientResult.client;
            instance.transportInstance = clientResult.transport;
            instance.process = clientResult.process || null;
            instance.capabilities = safeMetadataConversion(clientResult.capabilities);
            instance.startTime = new Date();
            instance.lastHealthCheck = new Date();
            instance.metrics.totalConnections++;
            // Handle process events if there's a process
            if (instance.process) {
                instance.process.on("error", (error) => {
                    mcpLogger.error(`[ExternalServerManager] Process error for ${serverId}:`, error);
                    this.handleServerError(serverId, error);
                });
                instance.process.on("exit", (code, signal) => {
                    mcpLogger.warn(`[ExternalServerManager] Process exited for ${serverId}`, {
                        code,
                        signal,
                    });
                    this.handleServerDisconnection(serverId, `Process exited with code ${code}`);
                });
                // Log stderr for debugging
                instance.process.stderr?.on("data", (data) => {
                    const message = data.toString().trim();
                    if (message) {
                        mcpLogger.debug(`[ExternalServerManager] ${serverId} stderr:`, message);
                    }
                });
            }
            this.updateServerStatus(serverId, "connected");
            // Discover tools from the server
            await this.discoverServerTools(serverId);
            // Register tools with main registry if integration is enabled
            if (this.enableMainRegistryIntegration) {
                await this.registerServerToolsWithMainRegistry(serverId);
            }
            // Start health monitoring
            this.startHealthMonitoring(serverId);
            // Emit connected event
            this.emit("connected", {
                serverId,
                serverName: this.getServerName(serverId),
                toolCount: instance.toolsMap.size,
                timestamp: new Date(),
            });
            span.setAttribute("mcp.tool_count", instance.toolsMap.size);
            span.setStatus({ code: SpanStatusCode.OK });
            mcpLogger.info(`[ExternalServerManager] Server started successfully: ${serverId}`);
        }
        catch (error) {
            mcpLogger.error(`[ExternalServerManager] Failed to start server ${serverId}:`, error);
            this.updateServerStatus(serverId, "failed");
            instance.lastError =
                error instanceof Error ? error.message : String(error);
            span.recordException(error instanceof Error ? error : new Error(String(error)));
            span.setStatus({
                code: SpanStatusCode.ERROR,
                message: error instanceof Error ? error.message : String(error),
            });
            throw error;
        }
        finally {
            span.end();
        }
    }
    /**
     * Stop an external MCP server
     */
    async stopServer(serverId) {
        const instance = this.servers.get(serverId);
        if (!instance) {
            return;
        }
        const span = tracers.mcp.startSpan("neurolink.mcp.server.stop", {
            attributes: {
                "mcp.server_id": serverId,
            },
        });
        try {
            this.updateServerStatus(serverId, "stopping");
            // Clear timers
            if (instance.healthTimer) {
                clearInterval(instance.healthTimer);
                instance.healthTimer = undefined;
            }
            if (instance.restartTimer) {
                clearTimeout(instance.restartTimer);
                instance.restartTimer = undefined;
            }
            // Unregister tools from main registry if integration is enabled
            if (this.enableMainRegistryIntegration) {
                this.unregisterServerToolsFromMainRegistry(serverId);
            }
            // Clear server tools from discovery service
            this.toolDiscovery.clearServerTools(serverId);
            // Close MCP client using factory cleanup
            if (instance.client && instance.transportInstance) {
                try {
                    await MCPClientFactory.closeClient(instance.client, instance.transportInstance, instance.process || undefined);
                }
                catch (error) {
                    mcpLogger.debug(`[ExternalServerManager] Error closing client for ${serverId}:`, error);
                }
                instance.client = null;
                instance.transportInstance = null;
                instance.process = null;
            }
            this.updateServerStatus(serverId, "stopped");
            span.setStatus({ code: SpanStatusCode.OK });
            mcpLogger.info(`[ExternalServerManager] Server stopped: ${serverId}`);
        }
        catch (error) {
            mcpLogger.error(`[ExternalServerManager] Error stopping server ${serverId}:`, error);
            this.updateServerStatus(serverId, "failed");
            span.recordException(error instanceof Error ? error : new Error(String(error)));
            span.setStatus({
                code: SpanStatusCode.ERROR,
                message: error instanceof Error ? error.message : String(error),
            });
        }
        finally {
            span.end();
        }
    }
    /**
     * Update server status and emit events
     */
    updateServerStatus(serverId, newStatus) {
        const instance = this.servers.get(serverId);
        if (!instance) {
            return;
        }
        const oldStatus = instance.status;
        // Map ExternalMCPServerStatus to MCPServerInfo status
        const mappedStatus = newStatus === "connecting" || newStatus === "restarting"
            ? "initializing"
            : newStatus === "stopping" || newStatus === "stopped"
                ? "stopping"
                : newStatus === "connected"
                    ? "connected"
                    : newStatus === "disconnected"
                        ? "disconnected"
                        : "failed";
        instance.status = mappedStatus;
        // Emit status change event
        this.emit("statusChanged", {
            serverId,
            serverName: this.getServerName(serverId),
            oldStatus,
            newStatus,
            timestamp: new Date(),
        });
        mcpLogger.debug(`[ExternalServerManager] Status changed for ${serverId}: ${oldStatus} -> ${newStatus}`);
    }
    /**
     * Handle server errors
     */
    handleServerError(serverId, error) {
        const instance = this.servers.get(serverId);
        if (!instance) {
            return;
        }
        instance.lastError = error.message;
        instance.metrics.totalErrors++;
        mcpLogger.error(`[ExternalServerManager] Server error for ${serverId}:`, error);
        // Emit failed event
        this.emit("failed", {
            serverId,
            serverName: this.getServerName(serverId),
            error: error.message,
            timestamp: new Date(),
        });
        // Attempt restart if enabled
        if (this.config.enableAutoRestart && !this.isShuttingDown) {
            this.scheduleRestart(serverId);
        }
        else {
            this.updateServerStatus(serverId, "failed");
        }
    }
    /**
     * Handle server disconnection
     */
    handleServerDisconnection(serverId, reason) {
        const instance = this.servers.get(serverId);
        if (!instance) {
            return;
        }
        instance.metrics.totalDisconnections++;
        mcpLogger.warn(`[ExternalServerManager] Server disconnected ${serverId}: ${reason}`);
        // Emit disconnected event
        this.emit("disconnected", {
            serverId,
            serverName: this.getServerName(serverId),
            reason,
            timestamp: new Date(),
        });
        // Attempt restart if enabled — prefer server-specific setting, fall back to global
        if ((instance.config.autoRestart ?? this.config.enableAutoRestart) &&
            !this.isShuttingDown) {
            this.scheduleRestart(serverId);
        }
        else {
            this.updateServerStatus(serverId, "disconnected");
        }
    }
    /**
     * Schedule server restart with exponential backoff
     */
    scheduleRestart(serverId) {
        const instance = this.servers.get(serverId);
        if (!instance) {
            return;
        }
        if (instance.reconnectAttempts >= instance.maxReconnectAttempts) {
            mcpLogger.error(`[ExternalServerManager] Max restart attempts reached for ${serverId}`);
            this.updateServerStatus(serverId, "failed");
            return;
        }
        instance.reconnectAttempts++;
        this.updateServerStatus(serverId, "restarting");
        const delay = Math.min(1000 *
            Math.pow(this.config.restartBackoffMultiplier, instance.reconnectAttempts - 1), 30000);
        mcpLogger.info(`[ExternalServerManager] Scheduling restart for ${serverId} in ${delay}ms (attempt ${instance.reconnectAttempts})`);
        if (instance.restartTimer) {
            return;
        } // already scheduled
        instance.restartTimer = setTimeout(async () => {
            const restartSpan = tracers.mcp.startSpan("neurolink.mcp.server.restart", {
                attributes: {
                    "mcp.server_id": serverId,
                    "mcp.restart_attempt": instance.reconnectAttempts,
                    "mcp.restart_delay_ms": delay,
                },
            });
            try {
                await this.stopServer(serverId);
                await this.startServer(serverId);
                // Reset restart attempts on successful restart
                instance.reconnectAttempts = 0;
                restartSpan.setStatus({ code: SpanStatusCode.OK });
            }
            catch (error) {
                mcpLogger.error(`[ExternalServerManager] Restart failed for ${serverId}:`, error);
                restartSpan.recordException(error instanceof Error ? error : new Error(String(error)));
                restartSpan.setStatus({
                    code: SpanStatusCode.ERROR,
                    message: error instanceof Error ? error.message : String(error),
                });
                this.scheduleRestart(serverId); // Try again
            }
            finally {
                restartSpan.end();
            }
        }, delay);
    }
    /**
     * Start health monitoring for a server
     */
    startHealthMonitoring(serverId) {
        const instance = this.servers.get(serverId);
        if (!instance || !this.config.enablePerformanceMonitoring) {
            return;
        }
        const interval = instance.config.healthCheckInterval ??
            this.config.defaultHealthCheckInterval;
        instance.healthTimer = setInterval(async () => {
            await this.performHealthCheck(serverId);
        }, interval);
    }
    /**
     * Perform health check on a server
     */
    async performHealthCheck(serverId) {
        const instance = this.servers.get(serverId);
        if (!instance || instance.status !== "connected") {
            return;
        }
        const startTime = Date.now();
        try {
            // For now, simple process check
            let isHealthy = true;
            const issues = [];
            if (instance.process && instance.process.killed) {
                isHealthy = false;
                issues.push("Process is killed");
            }
            const responseTime = Date.now() - startTime;
            instance.lastHealthCheck = new Date();
            const health = {
                serverId,
                isHealthy,
                status: instance.status,
                checkedAt: new Date(),
                responseTime,
                toolCount: instance.toolsMap.size,
                issues,
                performance: {
                    uptime: instance.startTime
                        ? Date.now() - instance.startTime.getTime()
                        : 0,
                    averageResponseTime: instance.metrics.averageResponseTime,
                },
            };
            // Emit health check event
            this.emit("healthCheck", {
                serverId,
                serverName: this.getServerName(serverId),
                health,
                timestamp: new Date(),
            });
            if (!isHealthy) {
                mcpLogger.warn(`[ExternalServerManager] Health check failed for ${serverId}:`, issues);
                this.handleServerError(serverId, new Error(`Health check failed: ${issues.join(", ")}`));
            }
        }
        catch (error) {
            mcpLogger.error(`[ExternalServerManager] Health check error for ${serverId}:`, error);
            this.handleServerError(serverId, error instanceof Error ? error : new Error(String(error)));
        }
    }
    /**
     * Get server instance - converted to ExternalMCPServerInstance for compatibility
     */
    getServer(serverId) {
        const runtime = this.servers.get(serverId);
        if (!runtime) {
            return undefined;
        }
        return {
            config: runtime.config,
            process: runtime.process,
            client: runtime.client,
            transport: runtime.transportInstance,
            status: runtime.status,
            lastError: runtime.lastError,
            startTime: runtime.startTime,
            lastHealthCheck: runtime.lastHealthCheck,
            reconnectAttempts: runtime.reconnectAttempts,
            maxReconnectAttempts: runtime.maxReconnectAttempts,
            tools: runtime.toolsMap,
            toolsArray: runtime.toolsArray,
            capabilities: runtime.capabilities,
            healthTimer: runtime.healthTimer,
            restartTimer: runtime.restartTimer,
            metrics: runtime.metrics,
        };
    }
    /**
     * Get all servers - converted to ExternalMCPServerInstance for compatibility
     */
    getAllServers() {
        const converted = new Map();
        for (const [serverId, runtime] of this.servers.entries()) {
            converted.set(serverId, {
                config: runtime.config,
                process: runtime.process,
                client: runtime.client,
                transport: runtime.transportInstance,
                status: runtime.status,
                lastError: runtime.lastError,
                startTime: runtime.startTime,
                lastHealthCheck: runtime.lastHealthCheck,
                reconnectAttempts: runtime.reconnectAttempts,
                maxReconnectAttempts: runtime.maxReconnectAttempts,
                tools: runtime.toolsMap,
                toolsArray: runtime.toolsArray,
                capabilities: runtime.capabilities,
                healthTimer: runtime.healthTimer,
                restartTimer: runtime.restartTimer,
                metrics: runtime.metrics,
            });
        }
        return converted;
    }
    /**
     * List servers as MCPServerInfo - ZERO conversion needed
     */
    listServers() {
        return Array.from(this.servers.values());
    }
    /**
     * Get server statuses
     */
    getServerStatuses() {
        const statuses = [];
        for (const [serverId, instance] of Array.from(this.servers.entries())) {
            const uptime = instance.startTime
                ? Date.now() - instance.startTime.getTime()
                : 0;
            statuses.push({
                serverId,
                isHealthy: instance.status === "connected",
                status: instance.status,
                checkedAt: instance.lastHealthCheck || new Date(),
                toolCount: instance.toolsMap.size,
                issues: instance.lastError ? [instance.lastError] : [],
                performance: {
                    uptime,
                    averageResponseTime: instance.metrics.averageResponseTime,
                },
            });
        }
        return statuses;
    }
    /**
     * Shutdown all servers and clean up resources
     * This method should be called during application shutdown to prevent memory leaks
     */
    async shutdown() {
        if (this.isShuttingDown) {
            return;
        }
        this.isShuttingDown = true;
        mcpLogger.info("[ExternalServerManager] Shutting down all servers...");
        const shutdownPromises = Array.from(this.servers.keys()).map((serverId) => this.stopServer(serverId).catch((error) => {
            mcpLogger.error(`[ExternalServerManager] Error shutting down ${serverId}:`, error);
        }));
        await Promise.all(shutdownPromises);
        this.servers.clear();
        // Clean up the tool discovery service to prevent memory leaks
        // from accumulated event listeners
        this.toolDiscovery.destroy();
        // Remove all event listeners from this manager
        this.removeAllListeners();
        mcpLogger.info("[ExternalServerManager] All servers shut down and resources cleaned up");
    }
    /**
     * Destroy the manager and all associated resources
     * Alias for shutdown() to match the pattern used by other components
     */
    async destroy() {
        return this.shutdown();
    }
    /**
     * Get manager statistics
     */
    getStatistics() {
        let connectedServers = 0;
        let failedServers = 0;
        let totalTools = 0;
        let totalConnections = 0;
        let totalErrors = 0;
        for (const instance of Array.from(this.servers.values())) {
            if (instance.status === "connected") {
                connectedServers++;
            }
            else if (instance.status === "failed") {
                failedServers++;
            }
            totalTools += instance.toolsMap.size;
            totalConnections += instance.metrics.totalConnections;
            totalErrors += instance.metrics.totalErrors;
        }
        return {
            totalServers: this.servers.size,
            connectedServers,
            failedServers,
            totalTools,
            totalConnections,
            totalErrors,
        };
    }
    /**
     * Discover tools from a server
     */
    async discoverServerTools(serverId) {
        const instance = this.servers.get(serverId);
        if (!instance || !instance.client) {
            throw new Error(`Server '${serverId}' not found or not connected`);
        }
        try {
            mcpLogger.debug(`[ExternalServerManager] Discovering tools for server: ${serverId}`);
            const discoveryResult = await this.toolDiscovery.discoverTools(serverId, instance.client, instance.config.timeout || this.config.defaultTimeout);
            if (discoveryResult.success) {
                instance.toolsMap.clear();
                instance.toolsArray = undefined;
                instance.tools = [];
                const blockedTools = instance.blockedTools || [];
                let blockedCount = 0;
                for (const tool of discoveryResult.tools) {
                    // Check if tool is blocked
                    if (blockedTools.includes(tool.name)) {
                        mcpLogger.info(`[ExternalServerManager] Blocking tool '${tool.name}' from server '${serverId}' (configured in blockedTools)`);
                        blockedCount++;
                        continue; // Skip blocked tools
                    }
                    instance.toolsMap.set(tool.name, tool);
                    instance.tools.push({
                        name: tool.name,
                        description: tool.description,
                        inputSchema: tool.inputSchema,
                    });
                }
                mcpLogger.info(`[ExternalServerManager] Discovered ${discoveryResult.toolCount} tools for ${serverId} (${blockedCount} blocked, ${instance.toolsMap.size} available)`);
            }
            else {
                mcpLogger.warn(`[ExternalServerManager] Tool discovery failed for ${serverId}: ${discoveryResult.error}`);
            }
        }
        catch (error) {
            mcpLogger.error(`[ExternalServerManager] Tool discovery error for ${serverId}:`, error);
        }
    }
    /**
     * Register server tools with main tool registry for unified access
     * This enables external MCP tools to be accessed via the main toolRegistry.executeTool()
     */
    async registerServerToolsWithMainRegistry(serverId) {
        const instance = this.servers.get(serverId);
        if (!instance) {
            throw new Error(`Server '${serverId}' not found`);
        }
        try {
            mcpLogger.debug(`[ExternalServerManager] Registering ${instance.toolsMap.size} tools with main registry for server: ${serverId}`);
            const registrations = [];
            for (const [toolName, tool] of instance.toolsMap.entries()) {
                const toolId = `${serverId}.${toolName}`;
                const toolInfo = {
                    name: toolName,
                    description: tool.description || toolName,
                    inputSchema: tool.inputSchema || {},
                    serverId: serverId,
                    category: detectCategory({ isExternal: true, serverId }),
                };
                try {
                    registrations.push(toolRegistry.registerTool(toolId, toolInfo, {
                        execute: async (params, _context) => {
                            // Execute tool via ExternalServerManager for proper lifecycle management
                            return await this.executeTool(serverId, toolName, params, {
                                timeout: instance.config.timeout || this.config.defaultTimeout,
                            });
                        },
                    }));
                    mcpLogger.debug(`[ExternalServerManager] Registered tool with main registry: ${toolId}`);
                }
                catch (registrationError) {
                    mcpLogger.warn(`[ExternalServerManager] Failed to register tool ${toolId} with main registry:`, registrationError);
                }
            }
            const results = await Promise.allSettled(registrations);
            const ok = results.filter((r) => r.status === "fulfilled").length;
            const failed = results.length - ok;
            mcpLogger.info(`[ExternalServerManager] Registered ${ok}/${results.length} tools with main registry for ${serverId}${failed ? ` (${failed} failed)` : ""}`);
        }
        catch (error) {
            mcpLogger.error(`[ExternalServerManager] Failed to register tools with main registry for ${serverId}:`, error);
        }
    }
    /**
     * Unregister server tools from main tool registry
     */
    unregisterServerToolsFromMainRegistry(serverId) {
        const instance = this.servers.get(serverId);
        if (!instance || !this.enableMainRegistryIntegration) {
            return;
        }
        try {
            mcpLogger.debug(`[ExternalServerManager] Unregistering tools from main registry for server: ${serverId}`);
            for (const [toolName] of instance.toolsMap.entries()) {
                const toolId = `${serverId}.${toolName}`;
                try {
                    toolRegistry.removeTool(toolId);
                    mcpLogger.debug(`[ExternalServerManager] Unregistered tool from main registry: ${toolId}`);
                }
                catch (error) {
                    mcpLogger.debug(`[ExternalServerManager] Failed to unregister tool ${toolId}:`, error);
                }
            }
            mcpLogger.debug(`[ExternalServerManager] Completed unregistering tools from main registry for ${serverId}`);
        }
        catch (error) {
            mcpLogger.error(`[ExternalServerManager] Error unregistering tools from main registry for ${serverId}:`, error);
        }
    }
    /**
     * Execute a tool on a specific server
     */
    async executeTool(serverId, toolName, parameters, options) {
        const instance = this.servers.get(serverId);
        if (!instance) {
            throw new Error(`Server '${serverId}' not found`);
        }
        if (!instance.client) {
            throw new Error(`Server '${serverId}' is not connected`);
        }
        if (instance.status !== "connected") {
            throw new Error(`Server '${serverId}' is not in connected state: ${instance.status}`);
        }
        // Check if tool is blocked
        const blockedTools = instance.blockedTools || [];
        if (blockedTools.includes(toolName)) {
            throw new Error(`Tool '${toolName}' is blocked on server '${serverId}' by configuration`);
        }
        const startTime = Date.now();
        try {
            // HITL Safety Check: Request confirmation if required
            let finalParameters = parameters;
            if (this.hitlManager && this.hitlManager.isEnabled()) {
                const requiresConfirmation = this.hitlManager.requiresConfirmation(toolName, parameters);
                if (requiresConfirmation) {
                    mcpLogger.info(`[ExternalServerManager] External tool '${toolName}' on server '${serverId}' requires HITL confirmation`);
                    try {
                        const confirmationResult = await this.hitlManager.requestConfirmation(toolName, parameters, {
                            serverId: serverId,
                            sessionId: `external-${serverId}-${Date.now()}`,
                            userId: undefined, // External tools don't have user context by default
                        });
                        if (!confirmationResult.approved) {
                            // User rejected the tool execution
                            throw new HITLUserRejectedError(`External tool execution rejected by user: ${confirmationResult.reason || "No reason provided"}`, toolName, confirmationResult.reason);
                        }
                        // User approved - use modified arguments if provided
                        if (confirmationResult.modifiedArguments !== undefined) {
                            finalParameters =
                                confirmationResult.modifiedArguments;
                            mcpLogger.info(`[ExternalServerManager] External tool '${toolName}' arguments modified by user`);
                        }
                        mcpLogger.info(`[ExternalServerManager] External tool '${toolName}' approved for execution (response time: ${confirmationResult.responseTime}ms)`);
                    }
                    catch (error) {
                        if (error instanceof HITLTimeoutError) {
                            // Timeout occurred - user didn't respond in time
                            mcpLogger.warn(`[ExternalServerManager] External tool '${toolName}' execution timed out waiting for user confirmation`);
                            throw error;
                        }
                        else if (error instanceof HITLUserRejectedError) {
                            // User explicitly rejected
                            mcpLogger.info(`[ExternalServerManager] External tool '${toolName}' execution rejected by user`);
                            throw error;
                        }
                        else {
                            // Other HITL error (configuration, system error, etc.)
                            mcpLogger.error(`[ExternalServerManager] HITL confirmation failed for external tool '${toolName}':`, error);
                            throw new Error(`HITL confirmation failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
                        }
                    }
                }
                else {
                    mcpLogger.debug(`[ExternalServerManager] External tool '${toolName}' does not require HITL confirmation`);
                }
            }
            // Execute tool through discovery service (with potentially modified parameters)
            const result = await this.toolDiscovery.executeTool(toolName, serverId, instance.client, finalParameters, {
                timeout: options?.timeout ||
                    instance.config.timeout ||
                    this.config.defaultTimeout,
            });
            const duration = Date.now() - startTime;
            // Update metrics
            instance.metrics.totalToolCalls++;
            instance.metrics.lastResponseTime = duration;
            // Update average response time
            const totalTime = instance.metrics.averageResponseTime *
                (instance.metrics.totalToolCalls - 1) +
                duration;
            instance.metrics.averageResponseTime =
                totalTime / instance.metrics.totalToolCalls;
            if (result.success) {
                mcpLogger.debug(`[ExternalServerManager] Tool executed successfully: ${toolName} on ${serverId}`, {
                    duration,
                });
                try {
                    TelemetryService.getInstance()?.recordMCPToolCall(toolName, duration, true);
                }
                catch {
                    /* telemetry should not break execution */
                }
                return result.data;
            }
            else {
                throw new Error(result.error || "Tool execution failed");
            }
        }
        catch (error) {
            instance.metrics.totalErrors++;
            try {
                const errorDuration = Date.now() - startTime;
                TelemetryService.getInstance()?.recordMCPToolCall(toolName, errorDuration, false);
            }
            catch {
                /* telemetry should not break execution */
            }
            mcpLogger.error(`[ExternalServerManager] Tool execution failed: ${toolName} on ${serverId}`, error);
            throw error;
        }
    }
    /**
     * Get all tools from all servers
     */
    getAllTools() {
        return this.toolDiscovery.getAllTools();
    }
    /**
     * Get tools for a specific server
     */
    getServerTools(serverId) {
        return this.toolDiscovery.getServerTools(serverId);
    }
    /**
     * Get tool discovery service
     */
    getToolDiscovery() {
        return this.toolDiscovery;
    }
}
