UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

2,578 lines • 107 kB
/**
 * MCP CLI Commands for NeuroLink
 * Implements comprehensive MCP server management commands
 * Part of Phase 4.2 - MCP CLI Commands
 *
 * Enhanced in Phase 8.39.0 with:
 * - neurolink mcp servers - List all MCP servers
 * - neurolink mcp tools - List all available tools
 * - neurolink mcp discover - Enhanced tool discovery
 * - neurolink mcp create-server - Scaffold a new MCP server
 * - neurolink mcp annotate - Add/update tool annotations
 */
import { createExternalServerInfo } from "../../utils/mcpDefaults.js";
import { NeuroLink } from "../../neurolink.js";
import { logger } from "../../utils/logger.js";
import chalk from "chalk";
import ora from "ora";
import fs from "fs";
import path from "path";
import { getAnnotationSummary, validateAnnotations, mergeAnnotations, inferAnnotations, } from "../../mcp/toolAnnotations.js";
import { withTimeout, ErrorFactory } from "../../utils/errorHandling.js";
// Using MCPCommandArgs from types/cli.ts
/**
 * Response interface for MCP status information returned from the NeuroLink SDK.
 * This interface represents the raw status data that gets converted to CLI-friendly format.
 *
 * @interface MCPStatusResponse
 * @since 7.6.1
 *
 * @example
 * ```typescript
 * const status: MCPStatusResponse = {
 *   autoDiscoveredServers: [
 *     {
 *       name: "filesystem",
 *       id: "fs-server-001",
 *       status: "connected",
 *       source: "claude-desktop"
 *     }
 *   ],
 *   mcpInitialized: true,
 *   totalServers: 3,
 *   availableServers: 2
 * };
 * ```
 */
/**
 * Popular MCP servers registry
 */
const POPULAR_MCP_SERVERS = {
    filesystem: {
        command: "npx",
        args: [
            "-y",
            "@modelcontextprotocol/server-filesystem",
            "/path/to/allowed/files",
        ],
        transport: "stdio",
        description: "File system operations (read, write, create, list directories)",
    },
    github: {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-github"],
        env: { GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_PERSONAL_ACCESS_TOKEN}" },
        transport: "stdio",
        description: "GitHub repository management and file operations",
    },
    postgres: {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-postgres"],
        env: { DATABASE_URL: "${DATABASE_URL}" },
        transport: "stdio",
        description: "PostgreSQL database query and management",
    },
    sqlite: {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-sqlite", "/path/to/database.db"],
        transport: "stdio",
        description: "SQLite database operations and queries",
    },
    brave: {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-brave-search"],
        env: { BRAVE_API_KEY: "${BRAVE_API_KEY}" },
        transport: "stdio",
        description: "Brave Search API for web search capabilities",
    },
    puppeteer: {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-puppeteer"],
        transport: "stdio",
        description: "Web scraping and browser automation",
    },
    git: {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-git"],
        transport: "stdio",
        description: "Git repository operations and version control",
    },
    memory: {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-memory"],
        transport: "stdio",
        description: "Persistent memory and knowledge storage",
    },
    bitbucket: {
        command: "npx",
        args: ["-y", "@nexus2520/bitbucket-mcp-server"],
        env: {
            BITBUCKET_USERNAME: "${BITBUCKET_USERNAME}",
            BITBUCKET_TOKEN: "${BITBUCKET_TOKEN}",
            BITBUCKET_BASE_URL: "${BITBUCKET_BASE_URL}",
        },
        transport: "stdio",
        description: "Bitbucket repository management and development workflows",
    },
};
const MCP_STATUS_TIMEOUT_MS = 30_000;
/** Where `externalServerManager` looks for manually configured servers. */
const MCP_CONFIG_FILENAME = ".mcp-config.json";
/**
 * Explain an empty server list in terms of what the user's directory actually
 * looks like.
 *
 * "Found 0 MCP servers" plus two generic tips reads as an error with no cause:
 * the common case is simply that this project has no `.mcp-config.json`, and
 * saying so turns a dead end into a next step. When the file *is* present, the
 * cause is different — it parsed but declared nothing usable — so the advice
 * has to be different too, otherwise we'd send the user to create a file they
 * already have.
 */
function explainEmptyServerList() {
    const configPath = path.join(process.cwd(), MCP_CONFIG_FILENAME);
    const configExists = fs.existsSync(configPath);
    const lines = [];
    if (configExists) {
        lines.push(chalk.blue(`šŸ“„ Found ${MCP_CONFIG_FILENAME}, but it declares no usable servers.`), chalk.blue(`   Check the "mcpServers" object in ${configPath} — each entry needs a "command" (stdio) or "url" (http/sse/websocket).`));
    }
    else {
        lines.push(chalk.blue(`šŸ“„ No ${MCP_CONFIG_FILENAME} in this directory (${process.cwd()}).`), chalk.blue("   That file is where NeuroLink reads manually configured servers from."));
    }
    // Neither `install` nor `add` persists anything: both route through
    // NeuroLink.addInMemoryMCPServer(), which registers into the in-process tool
    // registry and is gone when the process exits. Nothing in the CLI writes
    // MCP_CONFIG_FILENAME. Saying otherwise sends the user looking for a file
    // that was never created — so the persistent path is spelled out as the
    // manual edit it actually is.
    lines.push("", chalk.blue("Next steps:"), chalk.blue(configExists
        ? `  • Add an entry to the "mcpServers" object in ${MCP_CONFIG_FILENAME} — editing that file is the only way to configure a server permanently.`
        : `  • Create ${MCP_CONFIG_FILENAME} here with an "mcpServers" object — each entry needs a "command" (stdio) or "url" (http/sse/websocket). Writing that file is the only way to configure a server permanently.`), chalk.blue("  • neurolink mcp install <server>   register a popular server for the current run only"), chalk.blue("  • neurolink mcp discover           discover tools from servers already reachable"), chalk.blue("  • neurolink mcp --help             all MCP subcommands"));
    return lines.join("\n");
}
/**
 * MCP CLI command factory
 */
export class MCPCommandFactory {
    /**
     * Create the main MCP command with subcommands
     */
    static createMCPCommands() {
        return {
            command: "mcp <subcommand>",
            describe: "Manage Model Context Protocol (MCP) servers",
            builder: (yargs) => {
                return yargs
                    .command("list", "List configured MCP servers with status", (yargs) => this.buildListOptions(yargs), (argv) => this.executeList(argv))
                    .command("servers", "List all MCP servers with detailed status", (yargs) => this.buildServersOptions(yargs), (argv) => this.executeServers(argv))
                    .command("tools", "List all available tools across MCP servers", (yargs) => this.buildToolsOptions(yargs), (argv) => this.executeTools(argv))
                    .command("discover", "Discover tools from MCP servers with annotations", (yargs) => this.buildDiscoverToolsOptions(yargs), (argv) => this.executeDiscoverTools(argv))
                    .command("create-server <name>", "Create a new MCP server scaffold", (yargs) => this.buildCreateServerOptions(yargs), (argv) => this.executeCreateServer(argv))
                    .command("annotate", "Add or update annotations for MCP tools", (yargs) => this.buildAnnotateOptions(yargs), (argv) => this.executeAnnotate(argv))
                    .command("install <server>", "Install popular MCP servers", (yargs) => this.buildInstallOptions(yargs), (argv) => this.executeInstall(argv))
                    .command("add <name> <command>", "Add custom MCP server configuration", (yargs) => this.buildAddOptions(yargs), (argv) => this.executeAdd(argv))
                    .command("test [server]", "Test connectivity to MCP servers", (yargs) => this.buildTestOptions(yargs), (argv) => this.executeTest(argv))
                    .command("exec <server> <tool>", "Execute tools from MCP servers", (yargs) => this.buildExecOptions(yargs), (argv) => this.executeExec(argv))
                    .command("remove <server>", "Remove MCP server configuration", (yargs) => this.buildRemoveOptions(yargs), (argv) => this.executeRemove(argv))
                    .command(this.createRegistryCommand())
                    .option("format", {
                    choices: ["table", "json", "compact"],
                    default: "table",
                    description: "Output format",
                })
                    .option("output", {
                    type: "string",
                    description: "Save output to file",
                })
                    .option("quiet", {
                    type: "boolean",
                    alias: "q",
                    default: false,
                    description: "Suppress non-essential output",
                })
                    .option("debug", {
                    type: "boolean",
                    default: false,
                    description: "Enable debug output",
                })
                    .demandCommand(1, "Please specify an MCP subcommand")
                    .help();
            },
            handler: () => {
                // No-op handler as subcommands handle everything
            },
        };
    }
    /**
     * Create discover command (top-level command)
     */
    static createDiscoverCommand() {
        return {
            command: "discover",
            describe: "Auto-discover MCP servers from various sources",
            builder: (yargs) => {
                return yargs
                    .option("auto-install", {
                    type: "boolean",
                    default: false,
                    description: "Automatically install discovered servers",
                })
                    .option("source", {
                    choices: ["claude-desktop", "vscode", "all"],
                    default: "all",
                    description: "Source to discover servers from",
                })
                    .option("format", {
                    choices: ["table", "json", "compact"],
                    default: "table",
                    description: "Output format",
                })
                    .option("quiet", {
                    type: "boolean",
                    alias: "q",
                    default: false,
                    description: "Suppress non-essential output",
                })
                    .example("neurolink mcp discover", "Discover MCP servers from all sources")
                    .example("neurolink mcp discover --source claude-desktop", "Discover from Claude Desktop only")
                    .example("neurolink mcp discover --auto-install", "Discover and auto-install servers");
            },
            handler: async (argv) => await MCPCommandFactory.executeDiscover(argv),
        };
    }
    /**
     * Build options for list command
     */
    static buildListOptions(yargs) {
        return yargs
            .option("status", {
            type: "boolean",
            default: false,
            description: "Check server connection status",
        })
            .option("detailed", {
            type: "boolean",
            default: false,
            description: "Show detailed server information",
        })
            .example("neurolink mcp list", "List all configured MCP servers")
            .example("neurolink mcp list --status", "List servers with connection status")
            .example("neurolink mcp list --detailed", "Show detailed server information");
    }
    /**
     * Build options for install command
     */
    static buildInstallOptions(yargs) {
        return yargs
            .positional("server", {
            type: "string",
            description: "Server name to install from popular registry",
            choices: Object.keys(POPULAR_MCP_SERVERS),
            demandOption: true,
        })
            .option("transport", {
            choices: ["stdio", "sse", "websocket"],
            default: "stdio",
            description: "Transport type for MCP communication",
        })
            .option("args", {
            type: "array",
            description: "Additional arguments for the server command",
        })
            .option("env", {
            type: "string",
            description: "Environment variables as JSON string",
        })
            .example("neurolink mcp install filesystem", "Install filesystem MCP server")
            .example("neurolink mcp install github", "Install GitHub MCP server")
            .example("neurolink mcp install postgres", "Install PostgreSQL MCP server");
    }
    /**
     * Build options for add command
     */
    static buildAddOptions(yargs) {
        return yargs
            .positional("name", {
            type: "string",
            description: "Name for the custom MCP server",
            demandOption: true,
        })
            .positional("command", {
            type: "string",
            description: "Command to execute the MCP server",
            demandOption: true,
        })
            .option("transport", {
            choices: ["stdio", "sse", "websocket"],
            default: "stdio",
            description: "Transport type for MCP communication",
        })
            .option("args", {
            type: "array",
            description: "Arguments for the server command",
        })
            .option("env", {
            type: "string",
            description: "Environment variables as JSON string",
        })
            .example("neurolink mcp add my-server node", "Add custom Node.js MCP server")
            .example("neurolink mcp add api-server python", "Add custom Python MCP server");
    }
    /**
     * Build options for test command
     */
    static buildTestOptions(yargs) {
        return yargs
            .positional("server", {
            type: "string",
            description: "Server name to test (optional - tests all if not specified)",
        })
            .option("timeout", {
            type: "number",
            default: 10000,
            description: "Test timeout in milliseconds",
        })
            .example("neurolink mcp test", "Test all configured servers")
            .example("neurolink mcp test filesystem", "Test specific server")
            .example("neurolink mcp test --timeout 5000", "Test with custom timeout");
    }
    /**
     * Build options for exec command
     */
    static buildExecOptions(yargs) {
        return yargs
            .positional("server", {
            type: "string",
            description: "MCP server name",
            demandOption: true,
        })
            .positional("tool", {
            type: "string",
            description: "Tool name to execute",
            demandOption: true,
        })
            .option("params", {
            type: "string",
            description: "Tool parameters as JSON string",
        })
            .example("neurolink mcp exec filesystem read_file", "Execute read_file tool")
            .example("neurolink mcp exec github list_repos", "Execute GitHub list_repos tool");
    }
    /**
     * Build options for remove command
     */
    static buildRemoveOptions(yargs) {
        return yargs
            .positional("server", {
            type: "string",
            description: "Server name to remove",
            demandOption: true,
        })
            .option("force", {
            type: "boolean",
            default: false,
            description: "Force removal without confirmation",
        })
            .example("neurolink mcp remove filesystem", "Remove filesystem server")
            .example("neurolink mcp remove old-server --force", "Force remove without confirmation");
    }
    /**
     * Execute list command
     */
    static async executeList(argv) {
        try {
            const spinner = argv.quiet ? null : ora("Loading MCP servers...").start();
            // Get configured servers from NeuroLink
            const sdk = new NeuroLink();
            const mcpStatus = await sdk.getMCPStatus();
            const allServers = await sdk.listMCPServers();
            if (spinner) {
                spinner.succeed(`Found ${allServers.length} MCP servers`);
            }
            if (allServers.length === 0) {
                logger.always(chalk.yellow("No MCP servers configured."));
                logger.always(explainEmptyServerList());
                return;
            }
            // Format and display results
            if (argv.format === "json") {
                logger.always(JSON.stringify(mcpStatus, null, 2));
            }
            else if (argv.format === "compact") {
                const allServers = await sdk.listMCPServers();
                allServers.forEach((server) => {
                    const status = server.status === "connected" ? chalk.green("āœ“") : chalk.red("āœ—");
                    logger.always(`${status} ${server.name} - ${server.description || "No description"}`);
                });
            }
            else {
                // Table format
                logger.always(chalk.bold("\nšŸ”§ MCP Servers:\n"));
                const allServers = await sdk.listMCPServers();
                for (const server of allServers) {
                    const status = server.status === "connected"
                        ? chalk.green("CONNECTED")
                        : chalk.red("DISCONNECTED");
                    logger.always(`${chalk.cyan(server.name)} ${status}`);
                    logger.always(`  Command: ${server.command || "Unknown"}`);
                    logger.always(`  Tools: ${server.tools?.length || 0} available`);
                    if (argv.detailed && server.tools) {
                        server.tools.forEach((tool) => {
                            logger.always(`    • ${tool.name}: ${tool.description}`);
                        });
                    }
                    if (server.error) {
                        logger.always(`  ${chalk.red("Error:")} ${server.error}`);
                    }
                    logger.always();
                }
            }
        }
        catch (_error) {
            logger.error(chalk.red(`āŒ List command failed: ${_error.message}`));
            process.exit(1);
        }
    }
    /**
     * Execute install command
     */
    static async executeInstall(argv) {
        try {
            const serverName = argv.server;
            if (!serverName) {
                logger.error(chalk.red("āŒ Server name is required"));
                process.exit(1);
            }
            const serverConfig = POPULAR_MCP_SERVERS[serverName];
            if (!serverConfig) {
                logger.error(chalk.red(`āŒ Unknown server: ${serverName}`));
                logger.always(chalk.blue("Available servers:"));
                Object.keys(POPULAR_MCP_SERVERS).forEach((name) => {
                    logger.always(`  • ${name}: ${POPULAR_MCP_SERVERS[name].description}`);
                });
                process.exit(1);
            }
            const spinner = argv.quiet
                ? null
                : ora(`Installing ${serverName} MCP server...`).start();
            // Parse environment variables if provided
            let env = serverConfig.env;
            if (argv.env) {
                try {
                    const parsedEnv = JSON.parse(argv.env);
                    env = { ...env, ...parsedEnv };
                }
                catch (error) {
                    if (spinner) {
                        spinner.fail();
                    }
                    logger.error(chalk.red("āŒ Invalid JSON in env parameter"));
                    logger.error(chalk.red(`Error details: ${error instanceof Error ? error.message : String(error)}`));
                    process.exit(1);
                }
            }
            const serverInfo = createExternalServerInfo({
                ...serverConfig,
                id: serverName,
                name: serverName,
                env,
            });
            // Add server to NeuroLink - direct usage, zero transformations!
            const sdk = new NeuroLink();
            await sdk.addInMemoryMCPServer(serverName, serverInfo);
            if (spinner) {
                spinner.succeed(chalk.green(`āœ… Successfully installed ${serverName} MCP server`));
            }
            // Display configuration info
            logger.always(chalk.bold("\nšŸ“‹ Server Configuration:"));
            logger.always(`Name: ${serverInfo.name}`);
            logger.always(`Command: ${serverInfo.command}`);
            if (serverInfo.args?.length) {
                logger.always(`Args: ${serverInfo.args.join(" ")}`);
            }
            if (serverInfo.env) {
                logger.always(`Environment: ${Object.keys(serverInfo.env).length} variables`);
            }
            logger.always(`Transport: ${serverInfo.transport}`);
            logger.always(`Description: ${serverInfo.description}`);
            // Test connection
            logger.always(chalk.blue("\nšŸ” Testing connection..."));
            try {
                const rawStatus = await sdk.getMCPStatus();
                const status = rawStatus;
                const installedServer = status.externalMCPServers?.find((s) => s.name === serverName);
                if (installedServer?.status === "connected") {
                    logger.always(chalk.green("āœ… Server connected successfully"));
                    if (installedServer.tools?.length) {
                        logger.always(`šŸ› ļø  Available tools: ${installedServer.tools.length}`);
                    }
                }
                else {
                    logger.always(chalk.yellow("āš ļø  Server installed but not connected"));
                    if (installedServer?.error) {
                        logger.always(chalk.red(`Error: ${installedServer.error}`));
                    }
                }
            }
            catch (testError) {
                logger.always(chalk.yellow("āš ļø  Could not test connection"));
                logger.debug(`Test connection _error: ${testError instanceof Error ? testError.message : String(testError)}`);
            }
        }
        catch (_error) {
            logger.error(chalk.red(`āŒ Install command failed: ${_error.message}`));
            process.exit(1);
        }
    }
    /**
     * Execute add command
     */
    static async executeAdd(argv) {
        try {
            const name = argv.name;
            if (!name) {
                logger.error(chalk.red("āŒ Server name is required"));
                process.exit(1);
            }
            const command = argv.command;
            if (!command) {
                logger.error(chalk.red("āŒ Command is required"));
                process.exit(1);
            }
            const spinner = argv.quiet
                ? null
                : ora(`Adding custom MCP server: ${name}...`).start();
            // Parse environment variables if provided
            let env;
            if (argv.env) {
                try {
                    env = JSON.parse(argv.env);
                }
                catch (error) {
                    if (spinner) {
                        spinner.fail();
                    }
                    logger.error(chalk.red("āŒ Invalid JSON in env parameter"));
                    logger.error(chalk.red(`Error details: ${error instanceof Error ? error.message : String(error)}`));
                    process.exit(1);
                }
            }
            const serverInfo = createExternalServerInfo({
                id: name,
                name,
                command,
                args: argv.args,
                env,
                transport: argv.transport || "stdio",
                description: command,
            });
            // Add server to NeuroLink using MCPServerInfo directly
            const sdk = new NeuroLink();
            await sdk.addInMemoryMCPServer(name, serverInfo);
            if (spinner) {
                spinner.succeed(chalk.green(`āœ… Successfully added ${name} MCP server`));
            }
            // Display configuration
            logger.always(chalk.bold("\nšŸ“‹ Server Configuration:"));
            logger.always(`Name: ${serverInfo.name}`);
            logger.always(`Command: ${serverInfo.command}`);
            if (serverInfo.args?.length) {
                logger.always(`Args: ${serverInfo.args.join(" ")}`);
            }
            if (serverInfo.env) {
                logger.always(`Environment: ${Object.keys(serverInfo.env).length} variables`);
            }
            logger.always(`Transport: ${serverInfo.transport}`);
        }
        catch (_error) {
            logger.error(chalk.red(`āŒ Add command failed: ${_error.message}`));
            process.exit(1);
        }
    }
    /**
     * Execute test command
     */
    static async executeTest(argv) {
        try {
            const targetServer = argv.server;
            const spinner = argv.quiet
                ? null
                : ora("Testing MCP server connections...").start();
            const sdk = new NeuroLink();
            await this.getMCPStatusWithTimeout(sdk, Number(argv.timeout) || MCP_STATUS_TIMEOUT_MS);
            let serversToTest = await sdk.listMCPServers();
            if (targetServer) {
                serversToTest = serversToTest.filter((s) => s.name === targetServer);
                if (serversToTest.length === 0) {
                    if (spinner) {
                        spinner.fail();
                    }
                    logger.error(chalk.red(`āŒ Server not found: ${targetServer}`));
                    process.exit(1);
                }
            }
            if (spinner) {
                spinner.succeed(`Testing ${serversToTest.length} servers`);
            }
            // Display test results
            logger.always(chalk.bold("\n🧪 MCP Server Test Results:\n"));
            for (const server of serversToTest) {
                const status = server.status === "connected"
                    ? chalk.green("āœ… CONNECTED")
                    : chalk.red("āŒ DISCONNECTED");
                logger.always(`${server.name}: ${status}`);
                if (server.status === "connected") {
                    logger.always(`  Tools: ${server.tools?.length || 0} available`);
                    if (server.tools?.length) {
                        server.tools.slice(0, 3).forEach((tool) => {
                            logger.always(`    • ${tool.name}`);
                        });
                        if (server.tools.length > 3) {
                            logger.always(`    ... and ${server.tools.length - 3} more`);
                        }
                    }
                }
                else {
                    if (server.error) {
                        logger.always(`  ${chalk.red("Error:")} ${server.error}`);
                    }
                    logger.always(chalk.yellow("  šŸ’” Try: neurolink mcp remove && neurolink mcp install"));
                }
                logger.always();
            }
            // Summary
            const connected = serversToTest.filter((s) => s.status === "connected").length;
            const total = serversToTest.length;
            if (connected === total) {
                logger.always(chalk.green(`šŸŽ‰ All ${total} servers connected successfully`));
            }
            else {
                logger.always(chalk.yellow(`āš ļø  ${connected}/${total} servers connected`));
            }
        }
        catch (_error) {
            logger.error(chalk.red(`āŒ Test command failed: ${_error.message}`));
            process.exit(1);
        }
    }
    /**
     * Execute exec command
     */
    static async executeExec(argv) {
        try {
            const serverName = argv.server;
            if (!serverName) {
                logger.error(chalk.red("āŒ Server name is required"));
                process.exit(1);
            }
            const toolName = argv.tool;
            if (!toolName) {
                logger.error(chalk.red("āŒ Tool name is required"));
                process.exit(1);
            }
            const spinner = argv.quiet
                ? null
                : ora(`Executing ${toolName} on ${serverName}...`).start();
            // Parse parameters if provided
            let params = {};
            if (argv.params) {
                try {
                    params = JSON.parse(argv.params);
                }
                catch (error) {
                    if (spinner) {
                        spinner.fail();
                    }
                    logger.error(chalk.red("āŒ Invalid JSON in params parameter"));
                    logger.error(chalk.red(`Error details: ${error instanceof Error ? error.message : String(error)}`));
                    process.exit(1);
                }
            }
            const sdk = new NeuroLink();
            let exitCode = 0;
            try {
                await this.getMCPStatusWithTimeout(sdk);
                // Check if server exists and is connected
                const allServers = await sdk.listMCPServers();
                const server = allServers.find((s) => s.name === serverName);
                if (!server) {
                    if (spinner) {
                        spinner.fail();
                    }
                    logger.error(chalk.red(`āŒ Server not found: ${serverName}`));
                    exitCode = 1;
                }
                else if (server.status !== "connected") {
                    if (spinner) {
                        spinner.fail();
                    }
                    logger.error(chalk.red(`āŒ Server not connected: ${serverName}`));
                    logger.always(chalk.yellow("šŸ’” Try: neurolink mcp test " + serverName));
                    exitCode = 1;
                }
                else {
                    // Check if tool exists
                    const tool = server.tools?.find((t) => t.name === toolName);
                    if (!tool) {
                        if (spinner) {
                            spinner.fail();
                        }
                        logger.error(chalk.red(`āŒ Tool not found: ${toolName}`));
                        if (server.tools?.length) {
                            logger.always(chalk.blue("Available tools:"));
                            server.tools.forEach((t) => {
                                logger.always(`  • ${t.name}: ${t.description}`);
                            });
                        }
                        exitCode = 1;
                    }
                    else {
                        exitCode = await this.runExecTool(toolName, serverName, params, argv, spinner);
                    }
                }
            }
            finally {
                // Always shut down MCP connections to prevent lingering child processes
                await sdk.shutdown().catch(() => undefined);
            }
            // Flush stdout/stderr before exit so buffered output (spinner lines,
            // result text) is not truncated by process.exit(). process.exit() is
            // still required because MCP stdio connections and health-check timers
            // keep the Node.js event loop alive even after sdk.shutdown().
            await new Promise((resolve) => {
                process.stdout.write("", () => {
                    process.stderr.write("", () => resolve());
                });
            });
            process.exit(exitCode);
        }
        catch (_error) {
            logger.error(chalk.red(`āŒ Exec command failed: ${_error.message}`));
            process.exit(1);
        }
    }
    /**
     * Run the actual MCP tool execution and format/write the output.
     * Extracted to keep executeExec nesting within ESLint max-depth limit.
     * Returns 0 on success, 1 on failure.
     */
    static async runExecTool(toolName, serverName, params, argv, spinner) {
        const WARN_BYTES = 50 * 1024; // 50 KB
        try {
            const { toolRegistry } = await import("../../mcp/toolRegistry.js");
            const executionResult = await withTimeout(toolRegistry.executeTool(toolName, params, {
                sessionId: `cli-${Date.now()}`,
                userId: process.env.USER || "cli-user",
                config: { domainType: "cli-execution", customData: { serverName } },
            }), MCP_STATUS_TIMEOUT_MS, ErrorFactory.toolTimeout(toolName, MCP_STATUS_TIMEOUT_MS));
            const result = {
                tool: toolName,
                server: serverName,
                params,
                result: executionResult,
                success: true,
                timestamp: new Date().toISOString(),
            };
            if (spinner) {
                spinner.succeed(chalk.green("āœ… Tool executed successfully"));
            }
            const resultJson = JSON.stringify(result, null, 2);
            const resultBytes = Buffer.byteLength(resultJson, "utf-8");
            const outputFile = argv.output;
            if (outputFile) {
                await fs.promises.writeFile(outputFile, resultJson, "utf-8");
                logger.always(chalk.green(`āœ… Result saved to: ${chalk.cyan(outputFile)}`));
                logger.always(`   Size: ${resultBytes >= 1024 ? `${(resultBytes / 1024).toFixed(1)} KB` : `${resultBytes} B`}`);
            }
            else if (argv.format === "json") {
                if (resultBytes > WARN_BYTES) {
                    logger.warn(chalk.yellow(`⚠  Large result (${(resultBytes / 1024).toFixed(1)} KB). ` +
                        `Use --output <file> to save to disk instead.`));
                }
                logger.always(resultJson);
            }
            else {
                if (resultBytes > WARN_BYTES) {
                    logger.warn(chalk.yellow(`⚠  Large result (${(resultBytes / 1024).toFixed(1)} KB). ` +
                        `Use --output <file> to save to disk instead.`));
                }
                logger.always(chalk.green("šŸ”§ Tool Execution Results:"));
                logger.always(`   Tool: ${chalk.cyan(toolName)}`);
                logger.always(`   Server: ${chalk.cyan(serverName)}`);
                logger.always(`   Result: ${JSON.stringify(executionResult, null, 2)}`);
                logger.always(`   Timestamp: ${result.timestamp}`);
            }
            return 0;
        }
        catch (toolError) {
            const errorMessage = toolError instanceof Error ? toolError.message : String(toolError);
            if (spinner) {
                spinner.fail(chalk.red("āŒ Tool execution failed"));
            }
            const result = {
                tool: toolName,
                server: serverName,
                params,
                _error: errorMessage,
                success: false,
                timestamp: new Date().toISOString(),
            };
            if (argv.format === "json") {
                logger.always(JSON.stringify(result, null, 2));
            }
            else {
                logger.error(chalk.red("šŸ”§ Tool Execution Failed:"));
                logger.error(`   Tool: ${chalk.cyan(toolName)}`);
                logger.error(`   Server: ${chalk.cyan(serverName)}`);
                logger.error(`   Error: ${chalk.red(errorMessage)}`);
            }
            return 1;
        }
    }
    /**
     * Execute remove command
     */
    static async executeRemove(argv) {
        try {
            const serverName = argv.server;
            if (!serverName) {
                logger.error(chalk.red("āŒ Server name is required"));
                process.exit(1);
            }
            const sdk = new NeuroLink();
            await this.getMCPStatusWithTimeout(sdk);
            const allServers = await sdk.listMCPServers();
            const server = allServers.find((s) => s.name === serverName);
            if (!server) {
                logger.error(chalk.red(`āŒ Server not found: ${serverName}`));
                process.exit(1);
            }
            // Confirmation unless forced
            if (!argv.force) {
                logger.always(chalk.yellow(`āš ļø  This will remove the MCP server: ${serverName}`));
                logger.always("Use --force flag to confirm removal");
                process.exit(1);
            }
            const spinner = argv.quiet
                ? null
                : ora(`Removing MCP server: ${serverName}...`).start();
            // Remove server using the NeuroLink MCP tool registry
            try {
                const { toolRegistry } = await import("../../mcp/toolRegistry.js");
                const removed = toolRegistry.unregisterServer(serverName);
                if (!removed) {
                    throw new Error(`Failed to remove server ${serverName} from registry`);
                }
                if (spinner) {
                    spinner.succeed(chalk.green(`āœ… Server ${serverName} removed successfully`));
                }
                logger.always(chalk.green(`šŸ—‘ļø  Successfully removed MCP server: ${serverName}`));
                logger.always(chalk.gray("   All associated tools have been unregistered"));
            }
            catch (removalError) {
                const errorMessage = removalError instanceof Error
                    ? removalError.message
                    : String(removalError);
                if (spinner) {
                    spinner.fail(chalk.red(`āŒ Failed to remove server ${serverName}`));
                }
                logger.error(chalk.red(`āŒ Server removal failed: ${errorMessage}`));
                process.exit(1);
            }
        }
        catch (_error) {
            logger.error(chalk.red(`āŒ Remove command failed: ${_error.message}`));
            process.exit(1);
        }
    }
    /**
     * Execute discover command
     */
    static async executeDiscover(argv) {
        try {
            const spinner = argv.quiet
                ? null
                : ora("Discovering MCP servers...").start();
            const discovered = [];
            // Discover from Claude Desktop
            if (argv.source === "claude-desktop" || argv.source === "all") {
                const claudeServers = await this.discoverFromClaudeDesktop();
                discovered.push(...claudeServers);
            }
            // Discover from VS Code
            if (argv.source === "vscode" || argv.source === "all") {
                const vscodeServers = await this.discoverFromVSCode();
                discovered.push(...vscodeServers);
            }
            if (spinner) {
                spinner.succeed(`Discovered ${discovered.length} MCP servers`);
            }
            if (discovered.length === 0) {
                logger.always(chalk.yellow("No MCP servers discovered."));
                logger.always(chalk.blue("šŸ’” Try installing popular servers: neurolink mcp install filesystem"));
                return;
            }
            // Display discovered servers
            if (argv.format === "json") {
                logger.always(JSON.stringify(discovered, null, 2));
            }
            else {
                logger.always(chalk.bold("\nšŸ” Discovered MCP Servers:\n"));
                discovered.forEach((server) => {
                    logger.always(`${chalk.cyan(server.name)}`);
                    logger.always(`  Command: ${server.command}`);
                    logger.always(`  Source: ${server.description || "Unknown"}`);
                    logger.always(`  Status: ${server.status}`);
                    logger.always();
                });
            }
            // Auto-install if requested
            if (argv.autoInstall && discovered.length > 0) {
                logger.always(chalk.blue("šŸš€ Auto-installing discovered servers..."));
                const sdk = new NeuroLink();
                for (const server of discovered) {
                    try {
                        // Use discovered MCPServerInfo directly - zero conversions!
                        await sdk.addInMemoryMCPServer(server.name, server);
                        logger.always(chalk.green(`āœ… Installed ${server.name}`));
                    }
                    catch (_error) {
                        logger.always(chalk.red(`āŒ Failed to install ${server.name}: ${_error.message}`));
                    }
                }
            }
        }
        catch (_error) {
            logger.error(chalk.red(`āŒ Discover command failed: ${_error.message}`));
            process.exit(1);
        }
    }
    /**
     * Discover servers from Claude Desktop configuration
     */
    static async discoverFromClaudeDesktop() {
        const servers = [];
        try {
            // Common Claude Desktop config paths
            const possiblePaths = [
                path.join(process.env.HOME || "", "Library", "Application Support", "Claude", "claude_desktop_config.json"),
                path.join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json"),
                path.join(process.env.HOME || "", ".config", "claude", "claude_desktop_config.json"),
            ];
            for (const configPath of possiblePaths) {
                if (fs.existsSync(configPath)) {
                    const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
                    if (config.mcpServers) {
                        Object.entries(config.mcpServers).forEach(([name, serverConfig]) => {
                            const typedConfig = serverConfig;
                            // SMART DEFAULTS: Use utility to eliminate manual MCPServerInfo creation
                            servers.push(createExternalServerInfo({
                                ...typedConfig,
                                id: name,
                                name,
                                description: "Discovered from Claude Desktop",
                            }));
                        });
                    }
                    break; // Found config file, stop searching
                }
            }
        }
        catch (error) {
            // Log discovery errors for debugging but don't fail
            logger.debug(`Claude Desktop discovery error: ${error instanceof Error ? error.message : String(error)}`);
        }
        return servers;
    }
    /**
     * Discover servers from VS Code configuration
     */
    static async discoverFromVSCode() {
        const servers = [];
        try {
            // VS Code settings paths
            const possiblePaths = [
                path.join(process.env.HOME || "", "Library", "Application Support", "Code", "User", "settings.json"),
                path.join(process.env.APPDATA || "", "Code", "User", "settings.json"),
                path.join(process.env.HOME || "", ".config", "Code", "User", "settings.json"),
            ];
            for (const settingsPath of possiblePaths) {
                if (fs.existsSync(settingsPath)) {
                    const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
                    // Look for MCP-related extensions or configurations
                    if (settings["mcp.servers"]) {
                        Object.entries(settings["mcp.servers"]).forEach(([name, serverConfig]) => {
                            const config = serverConfig;
                            servers.push(createExternalServerInfo({
                                ...config,
                                id: name,
                                name,
                                description: "Discovered from VS Code",
                            }));
                        });
                    }
                    break;
                }
            }
        }
        catch (error) {
            // Log discovery errors for debugging but don't fail
            logger.debug(`VS Code discovery error: ${error instanceof Error ? error.message : String(error)}`);
        }
        return servers;
    }
    // ========================================
    // NEW MCP ENHANCEMENT COMMANDS (Phase 8.39.0)
    // ========================================
    /**
     * Build options for servers command
     */
    static buildServersOptions(yargs) {
        return yargs
            .option("status", {
            choices: ["all", "connected", "disconnected", "failed"],
            default: "all",
            description: "Filter servers by status",
        })
            .option("category", {
            type: "string",
            description: "Filter servers by category",
        })
            .option("detailed", {
            type: "boolean",
            default: false,
            description: "Show detailed server information including metrics",
        })
            .example("neurolink mcp servers", "List all MCP servers")
            .example("neurolink mcp servers --status connected", "List only connected servers")
            .example("neurolink mcp servers --detailed", "Show detailed metrics for each server");
    }
    /**
     * Build options for tools command
     */
    static buildToolsOptions(yargs) {
        return yargs
            .option("server", {
            type: "string",
            description: "Filter tools by server ID",
        })
            .option("category", {
            type: "string",
            description: "Filter tools by category",
        })
            .option("tag", {
            type: "array",
            description: "Filter tools by tags",
        })
            .option("safety", {
            choices: ["safe", "moderate", "dangerous"],
            description: "Filter tools by safety level",
        })
            .option("annotations", {
            type: "boolean",
            default: false,
            description: "Show tool annotations",
        })
            .option("search", {
            type: "string",
            description: "Search tools by name or description",
        })
            .example("neurolink mcp tools", "List all available tools")
            .example("neurolink mcp tools --server filesystem", "List tools from specific server")
            .example("neurolink mcp tools --safety safe", "List only read-only safe tools")
            .example("neurolink mcp tools --annotations", "Show tools with their annotations");
    }
    /**
     * Build options for discover tools command
     */
    static buildDiscoverToolsOptions(yargs) {
        return yargs
            .option("server", {
            type: "string",
            description: "Specific server to discover tools from",
        })
            .option("infer-annotations", {
            type: "boolean",
            default: true,
            description: "Automatically infer tool annotations",
        })
            .option("verbose", {
            type: "boolean",
            alias: "v",
            default: false,
            description: "Show verbose discovery output",
        })
            .example("neurolink mcp discover", "Discover tools from all servers")
            .example("neurolink mcp discover --server github", "Discover tools from specific server");
    }
    /**
     * Build options for create-server command
     */
    static buildCreateServerOptions(yargs) {
        return yargs
            .positional("name", {
            type: "string",
            description: "Name for the new MCP server",
            demandOption: true,
        })
            .option("template", {
            choices: ["basic", "typescript", "python"],
            default: "typescript",
            description: "Server template to use",
        })
            .option("output", {
            type: "string",
            alias: "o",
            description: "Output directory (defaults to current directory)",
        })
            .option("tools", {
            type: "array",
            description: "Initial tools to include in the server",
        })
            .option("description", {
            type: "string",
            alias: "d",
            description: "Server description",
        })
            .example("neurolink mcp create-server my-server", "Create a new TypeScript MCP server")
            .example("neurolink mcp create-server my-server --template python", "Create a Python MCP server")
            .example("neurolink mcp create-server my-server --tools readFile writeFile", "Create server with initial tools");
    }
    /**
     * Execute servers command
     */
    static async executeServers(argv) {
        try {
            const spinner = argv.quiet ? null : ora("Loading MCP servers...").start();
            const sdk = new NeuroLink();
            let servers = await sdk.listMCPServers();
            // Filter by status
            if (argv.status && argv.status !== "all") {
                servers = servers.filter((s) => s.status === argv.status);
            }
            // Filter by category
            if (argv.category) {
                servers = servers.filter((s) => s.metadata?.category === argv.category);
            }
            if (spinner) {
                spinner.succeed(`Found ${servers.length} MCP servers`);
            }
            if (servers.length === 0) {
                logger.always(chalk.yellow("No MCP servers match the criteria."));
                return;
            }
            // Output formatting
            if (argv.format === "json") {
                logger.always(JSON.stringify(servers, null, 2));
                return;
            }
            logger.always(chalk.bold("\nšŸ“” MCP Servers:\n"));
            for (const server of servers) {
                const statusIcon = server.status === "connected"
                    ? chalk.green("ā—")
                    : server.status === "failed"
                        ? chalk.red("ā—")
                        : chalk.yellow("ā—");
                const statusText = server.status === "connected"
                    ? chalk.green(server.status.toUpperCase())
                    : server.status === "failed"
                        ? chalk.red(server.status.toUpperCase())
                        : chalk.yellow(server.status.toUpperCase());
                logger.always(`${statusIcon} ${chalk.cyan.bold(server.name)} [${statusText}]`);
                logger.always(`   ID: ${server.id}`);
                logger.always(`   Transport: ${server.transport}`);
                logger.always(`   Description: ${server.description || "No description"}`);
                logger.always(`   Tools: ${server.tools?.length || 0} available`);
                if (argv.detailed) {
                    if (server.command) {
                        logger.always(`   Command: ${server.command}`);
                    }
                    if (server.url) {
                        logger.always(`   URL: ${server.url}`);
                    }
                    if (server.metadata) {
                        logger.always(`   Category: ${server.metadata.category || "uncategorized"}`);
                        if (server.metadata.version) {
                            logger.always(`   Version: ${server.metadata.version}`);
                        }
                        if (server.metadata.uptime) {
                            logger.always(`   Uptime: ${Math.round(server.metadata.uptime / 1000)}s`);
                        }
                    }
                    if (server.error) {
                        logger.always(`   ${chalk.red("Error:")} ${server.error}`);
                    }
                }
                logger.always();
            }
            // Summary
            const connected = servers.filter((s) => s.status === "connected").length;
            const totalTools = servers.reduce((sum, s) => sum + (s.tools?.length || 0), 0);
            logger.always(chalk.gray("---"));
            logger.always(`${chalk.bold("Summary:")} ${connected}/${servers.length} servers connected, ${totalTools} total tools`);
        }
        catch (error) {
            logger.error(chalk.red(`Servers command failed: ${error instanceof Error ? error.message : String(error)}`));
            process.exit(1);
        }
    }
    /**
     * Execute tools command
     */
    static async executeTools(argv) {
        try {
            const spinner = argv.quiet ? null : ora("Loading MCP tools...").start();
            const sdk = new NeuroLink();
            const servers = await sdk.listMCPServers();
            let tools = [];
            const { inferAnnotations } = await import("../../mcp/toolAnnotations.js");
            for (const server of servers) {
                if (server.status !== "connected") {
                    continue;
                }
                for (const tool of server.tools || []) {
                    const annotations = tool.annotations ??
                        inferAnnotations({
                            name: tool.name,
                            description: tool.description,
                        });
                    tools.push({
                        name: tool.name,
                        description: tool.description,
                        serverId: server.id,
                        serverName: server.name,
                        inputSchema: tool.inputSchema,
                        category: this.inferToolCategory(tool.name, tool.description),
                        annotations,
                    });
                }
            }
            // Apply filters
            if (argv.server) {
                tools = tools.filter((t) => t.serverId === argv.server || t.serverName === argv.server);
            }
            if (argv.category) {
                tools = tools.filter((t) => t.category === argv.category);
            }
            if (argv.search) {
                const searchTerm = String(argv.search).toLowerCase();
                tools = tools.filter((t) => t.name.toLowerCase().includes(searchTerm) ||
                    t.description.toLowerCase().includes(searchTerm));
            }
            if (argv.safety) {
                tools = tools.filter((t) => {
                    switch (argv.safety) {
                        case "safe":
                            return t.annotations?.readOnlyHint === true;
                        case "dangerous":
                            return t.annotations?.destructiveHint === true;
                        case "moderate":
                            return (!t.annotations?.readOnlyHint && !t.annotations?.destructiveHint);
                        default:
                            return true;
                    }
                });
            }
            if (spinner) {
                spinner.succeed(`Found ${tools.length} tools`);
            }
            if (tools.length === 0) {
                logger.always(chalk.yellow("No tools match the criteria."));
                return;
            }
            // Output formatting
            if (argv.format === "json") {
                logger.always(JSON.stringify(tools, null, 2));
                return;
            }
            logger.always(chalk.bold("\nšŸ”§ MCP Tools:\n"));
            // Group tools by server for better readability
            const toolsByServer = new Map();
            for (const tool of tools) {
                const key = tool.serverName;
                if (!toolsByServer.has(key)) {
                    toolsByServer.set(key, []);
                }
                const list = toolsByServer.get(key);
                if (list) {
                    list.push(tool);
                }
            }
            for (const [serverName, serverTools] of toolsByServer) {
                logger.always(chalk.cyan.bold(`${serverName}`) +
                    chalk.gray(` (${serverTools.length} tools)`));
                for (const tool of serverTools) {
                    // Safety indicator
                    let safetyIcon = "ā—‹";
                    if (tool.annotations?.destructiveHint) {
                        safetyIcon = chalk.red("⚠");
                    }
                    else if (tool.annotations?.readOnlyHint) {
                        safetyIcon = chalk.green("ā—");
                    }
                    else if (tool.annotations?.idempotentHint) {
                        safetyIcon = chalk.blue("◐");
                    }
                    logger.always(`  ${safetyIcon} ${chalk.white.bold(tool.name)}`);
                    logger.always(`    ${chalk.gray(tool.description)}`);
                    if (argv.annotations && tool.annotations) {
                        const annotationStr = getAnnotationSummary(tool.annotations);
                        if (annotationStr !== "[no annotations]") {
                            logger.always(`    ${chalk.dim(annotationStr)}`);
                        }
                    }
                }
                logger.always();
            }
            // Summary
            const safeCount = tools.filter((t) => t.annotations?.readOnlyHint).length;
            const dangerousCount = tools.filter((t) => t.annotations?.destructiveHint).length;
            logger.always(chalk.gray("---"));
            logger.always(`${chalk.bold("Summary:")} ${tools.length} tools (${chalk.green(`${safeCount} safe`)}, ${chalk.red(`${dangerousCount} destructive`)})`);
        }
        catch (error) {
            logger.error(chalk.red(`Tools command failed: ${error instanceof Error ? error.message : String(error)}`));
            process.exit(1);
        }
    }
    /**
     * Execute discover tools command
     */
    static async executeDiscoverTools(argv) {
        try {
            const spinner = argv.quiet
                ? null
                : ora("Discovering MCP tools...").start();
            const sdk = new NeuroLink();
            const servers = await sdk.listMCPServers();
            // Filter to specific server if requested
            let targetServers = servers.filter((s) => s.status === "connected");
            if (argv.server) {
                targetServers = targetServers.filter((s) => s.id === argv.server || s.name === argv.server);
                if (targetServers.length === 0) {
                    if (spinner) {
                        spinner.fail();
                    }
                    logger.error(chalk.red(`Server not found or not connected: ${argv.server}`));
                    process.exit(1);
                }
            }
            if (spinner) {
                spinner.text = `Discovering tools from ${targetServers.length} servers...`;
            }
            const results = [];
            for (const server of targetServers) {
                const { inferAnnotations } = await import("../../mcp/toolAnnotations.js");
                const toolsWithAnnotations = (server.tools || []).map((tool) => {
                    const existing = tool
                        .annotations;
                    const annotations = existing ??
                        (argv.inferAnnotations !== false
                            ? inferAnnotations({
                                name: tool.name,
                                description: tool.description,
                            })
                            : {});
                    return {
                        name: tool.name,
                        description: tool.description,
                        annotations,
                    };
                });
                results.push({
                    serverId: server.id,
                    serverName: server.name,
                    toolCount: toolsWithAnnotations.length,
                    tools: toolsWithAnnotations,
                });
                if (argv.verbose) {
                    logger.always(chalk.dim(`Discovered ${toolsWithAnnotations.length} tools from ${server.name}`));
                }
            }
            if (spinner) {
                const totalTools = results.reduce((sum, r) => sum + r.toolCount, 0);
                spinner.succeed(`Discovered ${totalTools} tools from ${results.length} servers`);
            }
            // Output formatting
            if (argv.format === "json") {
                logger.always(JSON.stringify(results, null, 2));
                return;
            }
            logger.always(chalk.bold("\nšŸ” Tool Discovery Results:\n"));
            for (const result of results) {
                logger.always(chalk.cyan.bold(result.serverName) +
                    chalk.gray(` [${result.serverId}]`));
                logger.always(`  Tools discovered: ${result.toolCount}`);
                if (argv.verbose && result.tools.length > 0) {
                    for (const tool of result.tools) {
                        const annotationStr = getAnnotationSummary(tool.annotations);
                        logger.always(`    • ${tool.name}${annotationStr !== "[no annotations]"
                            ? ` ${chalk.dim(annotationStr)}`
                            : ""}`);
                    }
                }
                logger.always();
            }
            // Summary by annotations
            const allTools = results.flatMap((r) => r.tools);
            const safeCount = allTools.filter((t) => t.annotations.readOnlyHint).length;
            const destructiveCount = allTools.filter((t) => t.annotations.destructiveHint).length;
            const idempotentCount = allTools.filter((t) => t.annotations.idempotentHint).length;
            logger.always(chalk.gray("---"));
            logger.always(chalk.bold("Annotation Summary:"));
            logger.always(`  ${chalk.green("Read-only (safe):")} ${safeCount}`);
            logger.always(`  ${chalk.red("Destructive:")} ${destructiveCount}`);
            logger.always(`  ${chalk.blue("Idempotent:")} ${idempotentCount}`);
        }
        catch (error) {
            logger.error(chalk.red(`Discover command failed: ${error instanceof Error ? error.message : String(error)}`));
            process.exit(1);
        }
    }
    /**
     * Execute create-server command
     */
    static async executeCreateServer(argv) {
        try {
            const serverName = argv.name;
            if (!serverName) {
                logger.error(chalk.red("Server name is required"));
                process.exit(1);
            }
            const template = argv.template || "typescript";
            const outputDir = argv.output || process.cwd();
            const description = argv.description || `Custom MCP server: ${serverName}`;
            const initialTools = argv.tools || [];
            const spinner = argv.quiet
                ? null
                : ora(`Creating MCP server: ${serverName}...`).start();
            // Create output directory
            const serverDir = path.join(outputDir, serverName);
            if (fs.existsSync(serverDir)) {
                if (spinner) {
                    spinner.fail();
                }
                logger.error(chalk.red(`Directory already exists: ${serverDir}`));
                process.exit(1);
            }
            fs.mkdirSync(serverDir, { recursive: true });
            // Generate server files based on template
            if (template === "typescript") {
                await this.generateTypeScriptServer(serverDir, serverName, description, initialTools);
            }
            else if (template === "python") {
                await this.generatePythonServer(serverDir, serverName, description, initialTools);
            }
            else {
                await this.generateBasicServer(serverDir, serverName, description, initialTools);
            }
            if (spinner) {
                spinner.succeed(chalk.green(`Successfully created MCP server: ${serverName}`));
            }
            logger.always(chalk.bold("\nšŸ“ Server created at:") + ` ${serverDir}`);
            logger.always();
            logger.always(chalk.bold("Next steps:"));
            logger.always(`  1. cd ${serverDir}`);
            if (template === "typescript") {
                logger.always("  2. npm install");
                logger.always("  3. npm run build");
                logger.always("  4. npm start");
            }
            else if (template === "python") {
                logger.always("  2. pip install -r requirements.txt");
                logger.always("  3. python server.py");
            }
            else {
                logger.always("  2. node server.js");
            }
            logger.always();
            logger.always(chalk.blue("šŸ’” Add to NeuroLink: neurolink mcp add " +
                serverName +
                " node " +
                path.join(serverDir, "dist", "index.js")));
        }
        catch (error) {
            logger.error(chalk.red(`Create server command failed: ${error instanceof Error ? error.message : String(error)}`));
            process.exit(1);
        }
    }
    /**
     * Generate TypeScript MCP server
     */
    static async generateTypeScriptServer(serverDir, serverName, description, tools) {
        // package.json
        const packageJson = {
            name: serverName,
            version: "1.0.0",
            description,
            type: "module",
            main: "dist/index.js",
            scripts: {
                build: "tsc",
                start: "node dist/index.js",
                dev: "tsc --watch",
            },
            dependencies: {
                "@modelcontextprotocol/sdk": "^1.0.0",
            },
            devDependencies: {
                "@types/node": "^20.0.0",
                typescript: "^5.0.0",
            },
        };
        fs.writeFileSync(path.join(serverDir, "package.json"), JSON.stringify(packageJson, null, 2));
        // tsconfig.json
        const tsConfig = {
            compilerOptions: {
                target: "ES2022",
                module: "NodeNext",
                moduleResolution: "NodeNext",
                outDir: "./dist",
                rootDir: "./src",
                strict: true,
                esModuleInterop: true,
                skipLibCheck: true,
                declaration: true,
            },
            include: ["src/**/*"],
        };
        fs.writeFileSync(path.join(serverDir, "tsconfig.json"), JSON.stringify(tsConfig, null, 2));
        // Create src directory
        fs.mkdirSync(path.join(serverDir, "src"), { recursive: true });
        // Generate tool definitions
        const toolDefs = tools.length > 0
            ? `
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
${tools
                .map((toolName) => `        { name: "${toolName}", description: "Add a description for ${toolName}", inputSchema: { type: "object", properties: {}, required: [] } },`)
                .join("\n")}
      ],
    }));

    server.setRequestHandler(CallToolRequestSchema, async (request) => {
      switch (request.params.name) {
${tools
                .map((toolName) => `        case "${toolName}":
          // Implement ${toolName} tool logic here
          return {
            content: [{ type: "text", text: "${toolName} executed successfully" }],
          };`)
                .join("\n")}
        default:
          throw new Error(\`Unknown tool: \${request.params.name}\`);
      }
    });
`
            : `
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: "hello",
          description: "A simple hello tool",
          inputSchema: {
            type: "object",
            properties: {
              name: { type: "string", description: "Name to greet" },
            },
            required: ["name"],
          },
        },
      ],
    }));

    server.setRequestHandler(CallToolRequestSchema, async (request) => {
      if (request.params.name === "hello") {
        const args = request.params.arguments as { name?: string };
        return {
          content: [{
            type: "text",
            text: \`Hello, \${args.name || "World"}!\`,
          }],
        };
      }
      throw new Error(\`Unknown tool: \${request.params.name}\`);
    });
`;
        // Main server file
        const mainFile = `/**
 * ${serverName} - MCP Server
 * ${description}
 *
 * Generated by NeuroLink CLI
 */

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
  {
    name: "${serverName}",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  },
);

${toolDefs}

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("${serverName} MCP server running on stdio");
}

main().catch(console.error);
`;
        fs.writeFileSync(path.join(serverDir, "src", "index.ts"), mainFile);
        // README.md
        const readme = `# ${serverName}

${description}

## Installation

\`\`\`bash
npm install
npm run build
\`\`\`

## Usage

Add to your NeuroLink configuration:

\`\`\`bash
neurolink mcp add ${serverName} node ${path.join(serverDir, "dist", "index.js")}
\`\`\`

## Development

\`\`\`bash
npm run dev  # Watch mode
npm run build  # Build for production
\`\`\`

## Tools

${tools.length > 0 ? tools.map((t) => `- **${t}**: add a description`).join("\n") : "- **hello**: A simple hello tool"}
`;
        fs.writeFileSync(path.join(serverDir, "README.md"), readme);
    }
    /**
     * Generate Python MCP server
     */
    static async generatePythonServer(serverDir, serverName, description, tools) {
        // requirements.txt
        const requirements = `mcp>=1.0.0
`;
        fs.writeFileSync(path.join(serverDir, "requirements.txt"), requirements);
        // Generate tool definitions - Python MCP SDK expects a single @server.call_tool() handler
        // that dispatches on the tool name
        const toolDefs = tools.length > 0
            ? (() => {
                const branches = tools
                    .map((toolName, index) => {
                    const _safeName = toolName.replace(/-/g, "_");
                    const keyword = index === 0 ? "if" : "elif";
                    return `    ${keyword} name == "${toolName}":
        # Implement ${toolName} tool logic here
        return [TextContent(type="text", text="${toolName} executed successfully")]`;
                })
                    .join("\n");
                return `
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    """Handle tool calls."""
${branches}
    else:
        raise ValueError(f"Unknown tool: {name}")
`;
            })()
            : `
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    """Handle tool calls."""
    if name == "hello":
        name_arg = arguments.get("name", "World")
        return [TextContent(type="text", text=f"Hello, {name_arg}!")]
    else:
        raise ValueError(f"Unknown tool: {name}")
`;
        const toolListDefs = tools.length > 0
            ? tools
                .map((toolName) => `
        Tool(
            name="${toolName}",
            description="Add a description for ${toolName}",
            inputSchema={
                "type": "object",
                "properties": {},
                "required": [],
            },
        ),`)
                .join("\n")
            : `
        Tool(
            name="hello",
            description="A simple hello tool",
            inputSchema={
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "Name to greet"},
                },
                "required": ["name"],
            },
        ),`;
        // Main server file
        const mainFile = `"""
${serverName} - MCP Server
${description}

Generated by NeuroLink CLI
"""

import asyncio
from mcp.server.models import InitializationOptions
from mcp.server import NotificationOptions, Server
from mcp.types import (
    Resource,
    Tool,
    TextContent,
)

server = Server("${serverName}")

@server.list_tools()
async def list_tools() -> list[Tool]:
    """List available tools."""
    return [${toolListDefs}
    ]

${toolDefs}

async def main():
    from mcp.server.stdio import stdio_server
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="${serverName}",
                server_version="1.0.0",
                capabilities=server.get_capabilities(
                    notification_options=NotificationOptions(),
                    experimental_capabilities={},
                ),
            ),
        )

if __name__ == "__main__":
    asyncio.run(main())
`;
        fs.writeFileSync(path.join(serverDir, "server.py"), mainFile);
        // README.md
        const readme = `# ${serverName}

${description}

## Installation

\`\`\`bash
pip install -r requirements.txt
\`\`\`

## Usage

Add to your NeuroLink configuration:

\`\`\`bash
neurolink mcp add ${serverName} python ${path.join(serverDir, "server.py")}
\`\`\`

## Tools

${tools.length > 0 ? tools.map((t) => `- **${t}**: add a description`).join("\n") : "- **hello**: A simple hello tool"}
`;
        fs.writeFileSync(path.join(serverDir, "README.md"), readme);
    }
    /**
     * Generate basic JavaScript MCP server
     */
    static async generateBasicServer(serverDir, serverName, description, tools) {
        // package.json
        const packageJson = {
            name: serverName,
            version: "1.0.0",
            description,
            type: "module",
            main: "server.js",
            scripts: {
                start: "node server.js",
            },
            dependencies: {
                "@modelcontextprotocol/sdk": "^1.0.0",
            },
        };
        fs.writeFileSync(path.join(serverDir, "package.json"), JSON.stringify(packageJson, null, 2));
        // Generate tool handler
        const toolHandler = tools.length > 0
            ? `const handlers = {
  ${tools.map((t) => `"${t}": async (args) => ({ type: "text", text: "${t} executed" })`).join(",\n  ")}
};

if (handlers[request.params.name]) {
  return { content: [await handlers[request.params.name](request.params.arguments)] };
}`
            : `if (request.params.name === "hello") {
  const args = request.params.arguments || {};
  return {
    content: [{
      type: "text",
      text: \`Hello, \${args.name || "World"}!\`,
    }],
  };
}`;
        const toolList = tools.length > 0
            ? tools.map((t) => `{ name: "${t}", description: "Add a description", inputSchema: { type: "object", properties: {} } }`)
            : [
                `{ name: "hello", description: "A simple hello tool", inputSchema: { type: "object", properties: { name: { type: "string" } }, required: ["name"] } }`,
            ];
        // Main server file
        const mainFile = `/**
 * ${serverName} - MCP Server
 * ${description}
 *
 * Generated by NeuroLink CLI
 */

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new Server(
  { name: "${serverName}", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

server.setRequestHandler({ method: "tools/list" }, async () => ({
  tools: [
    ${toolList.join(",\n    ")}
  ],
}));

server.setRequestHandler({ method: "tools/call" }, async (request) => {
  ${toolHandler}
  throw new Error(\`Unknown tool: \${request.params.name}\`);
});

const transport = new StdioServerTransport();
server.connect(transport).then(() => {
  console.error("${serverName} MCP server running on stdio");
}).catch(console.error);
`;
        fs.writeFileSync(path.join(serverDir, "server.js"), mainFile);
        // README.md
        const readme = `# ${serverName}

${description}

## Installation

\`\`\`bash
npm install
\`\`\`

## Usage

\`\`\`bash
neurolink mcp add ${serverName} node ${path.join(serverDir, "server.js")}
\`\`\`

## Tools

${tools.length > 0 ? tools.map((t) => `- **${t}**: add a description`).join("\n") : "- **hello**: A simple hello tool"}
`;
        fs.writeFileSync(path.join(serverDir, "README.md"), readme);
    }
    /**
     * Infer tool category from name and description
     */
    static inferToolCategory(name, description) {
        const combined = (name + " " + description).toLowerCase();
        if (combined.includes("git") || combined.includes("commit")) {
            return "version-control";
        }
        if (combined.includes("file") ||
            combined.includes("read") ||
            combined.includes("write") ||
            combined.includes("directory")) {
            return "file-system";
        }
        if (combined.includes("api") ||
            combined.includes("http") ||
            combined.includes("request") ||
            combined.includes("fetch")) {
            return "api";
        }
        if (combined.includes("database") ||
            combined.includes("query") ||
            combined.includes("sql")) {
            return "database";
        }
        if (combined.includes("search") ||
            combined.includes("find") ||
            combined.includes("grep")) {
            return "search";
        }
        if (combined.includes("auth") ||
            combined.includes("login") ||
            combined.includes("token")) {
            return "authentication";
        }
        return "general";
    }
    // ========================================
    // REGISTRY COMMANDS (Phase 8.39.0)
    // ========================================
    /**
     * Create the registry subcommand
     */
    static createRegistryCommand() {
        return {
            command: "registry <action>",
            describe: "Browse and search the MCP server registry",
            builder: (yargs) => {
                return yargs
                    .command("search [query]", "Search for MCP servers in the registry", (yargs) => yargs
                    .positional("query", {
                    type: "string",
                    description: "Search query",
                })
                    .option("category", {
                    type: "string",
                    alias: "c",
                    description: "Filter by category",
                })
                    .option("tag", {
                    type: "array",
                    alias: "t",
                    description: "Filter by tags",
                })
                    .option("verified", {
                    type: "boolean",
                    default: false,
                    description: "Show only verified servers",
                })
                    .option("limit", {
                    type: "number",
                    default: 10,
                    description: "Maximum results to show",
                }), (argv) => this.executeRegistrySearch(argv))
                    .command("list", "List all available servers in the registry", (yargs) => yargs
                    .option("category", {
                    type: "string",
                    description: "Filter by category",
                })
                    .option("limit", {
                    type: "number",
                    default: 25,
                    description: "Maximum results",
                }), (argv) => this.executeRegistryList(argv))
                    .command("info <server>", "Get detailed information about a server", (yargs) => yargs.positional("server", {
                    type: "string",
                    description: "Server ID to get info for",
                    demandOption: true,
                }), (argv) => this.executeRegistryInfo(argv))
                    .command("categories", "List all server categories", () => { }, () => this.executeRegistryCategories())
                    .command("popular", "Show popular MCP servers", (yargs) => yargs.option("limit", {
                    type: "number",
                    default: 10,
                    description: "Number of servers to show",
                }), (argv) => this.executeRegistryPopular(argv))
                    .demandCommand(1, "Please specify a registry action")
                    .help();
            },
            handler: () => { },
        };
    }
    /**
     * Execute registry search
     */
    static async executeRegistrySearch(argv) {
        try {
            const { globalMCPRegistryClient } = await import("../../mcp/mcpRegistryClient.js");
            const spinner = argv.quiet ? null : ora("Searching registry...").start();
            const results = await withTimeout(globalMCPRegistryClient.search({
                query: argv.query,
                categories: argv.category ? [argv.category] : undefined,
                tags: argv.tag,
                verifiedOnly: argv.verified,
                limit: argv.limit ?? 10,
            }), 30_000, ErrorFactory.toolTimeout("registrySearch", 30_000));
            if (spinner) {
                spinner.succeed(`Found ${results.totalCount} servers`);
            }
            if (results.entries.length === 0) {
                logger.always(chalk.yellow("No servers found matching your criteria."));
                return;
            }
            if (argv.format === "json") {
                logger.always(JSON.stringify(results.entries, null, 2));
                return;
            }
            logger.always(chalk.bold("\nšŸ“¦ MCP Server Registry Results:\n"));
            for (const entry of results.entries) {
                const verified = entry.verified ? chalk.green(" āœ“") : "";
                logger.always(`${chalk.cyan.bold(entry.name)}${verified} (${entry.id})`);
                logger.always(`  ${chalk.gray(entry.description)}`);
                logger.always(`  Version: ${entry.version} | Categories: ${entry.categories?.join(", ") ?? "none"}`);
                if (entry.tags?.length) {
                    logger.always(`  Tags: ${entry.tags.join(", ")}`);
                }
                logger.always();
            }
            if (results.hasMore) {
                logger.always(chalk.dim(`Showing ${results.entries.length} of ${results.totalCount} results. Use --limit to see more.`));
            }
        }
        catch (error) {
            logger.error(chalk.red(`Registry search failed: ${error instanceof Error ? error.message : String(error)}`));
            process.exit(1);
        }
    }
    /**
     * Execute registry list
     */
    static async executeRegistryList(argv) {
        try {
            const { globalMCPRegistryClient } = await import("../../mcp/mcpRegistryClient.js");
            const spinner = argv.quiet ? null : ora("Loading registry...").start();
            const results = await withTimeout(globalMCPRegistryClient.search({
                categories: argv.category ? [argv.category] : undefined,
                limit: argv.limit ?? 25,
                sortBy: "name",
                sortDirection: "asc",
            }), 30_000, ErrorFactory.toolTimeout("registrySearch", 30_000));
            if (spinner) {
                spinner.succeed(`Found ${results.totalCount} servers`);
            }
            if (argv.format === "json") {
                logger.always(JSON.stringify(results.entries, null, 2));
                return;
            }
            logger.always(chalk.bold("\nšŸ“¦ Available MCP Servers:\n"));
            // Group by category
            const byCategory = new Map();
            for (const entry of results.entries) {
                const category = entry.categories?.[0] ?? "uncategorized";
                if (!byCategory.has(category)) {
                    byCategory.set(category, []);
                }
                const catList = byCategory.get(category);
                if (catList) {
                    catList.push(entry);
                }
            }
            for (const [category, entries] of byCategory) {
                logger.always(chalk.yellow.bold(`\n${category.toUpperCase()}`));
                for (const entry of entries) {
                    const verified = entry.verified ? chalk.green("āœ“") : " ";
                    logger.always(`  ${verified} ${chalk.cyan(entry.id.padEnd(20))} ${entry.description.slice(0, 50)}${entry.description.length > 50 ? "..." : ""}`);
                }
            }
        }
        catch (error) {
            logger.error(chalk.red(`Registry list failed: ${error instanceof Error ? error.message : String(error)}`));
            process.exit(1);
        }
    }
    /**
     * Execute registry info
     */
    static async executeRegistryInfo(argv) {
        try {
            const { globalMCPRegistryClient } = await import("../../mcp/mcpRegistryClient.js");
            const serverId = argv.server;
            const entry = await withTimeout(globalMCPRegistryClient.getEntry(serverId), 30_000, ErrorFactory.toolTimeout("registryGetEntry", 30_000));
            if (!entry) {
                logger.error(chalk.red(`Server not found: ${serverId}`));
                process.exit(1);
            }
            if (argv.format === "json") {
                logger.always(JSON.stringify(entry, null, 2));
                return;
            }
            logger.always(chalk.bold(`\nšŸ“¦ ${entry.name}`));
            logger.always(chalk.gray(`ID: ${entry.id}`));
            if (entry.verified) {
                logger.always(chalk.green("āœ“ Verified"));
            }
            logger.always();
            logger.always(entry.description);
            logger.always();
            logger.always(chalk.bold("Details:"));
            logger.always(`  Version: ${entry.version}`);
            if (entry.author) {
                logger.always(`  Author: ${entry.author}`);
            }
            if (entry.license) {
                logger.always(`  License: ${entry.license}`);
            }
            if (entry.homepage) {
                logger.always(`  Homepage: ${entry.homepage}`);
            }
            if (entry.repository) {
                logger.always(`  Repository: ${entry.repository}`);
            }
            if (entry.categories?.length) {
                logger.always(`\n${chalk.bold("Categories:")} ${entry.categories.join(", ")}`);
            }
            if (entry.tags?.length) {
                logger.always(`${chalk.bold("Tags:")} ${entry.tags.join(", ")}`);
            }
            if (entry.transports?.length) {
                logger.always(`${chalk.bold("Transports:")} ${entry.transports.join(", ")}`);
            }
            if (entry.tools?.length) {
                logger.always(`\n${chalk.bold("Tools:")} (${entry.tools.length})`);
                for (const tool of entry.tools.slice(0, 10)) {
                    logger.always(`  • ${tool}`);
                }
                if (entry.tools.length > 10) {
                    logger.always(`  ... and ${entry.tools.length - 10} more`);
                }
            }
            if (entry.requiredEnvVars?.length) {
                logger.always(`\n${chalk.bold("Required Environment Variables:")}`);
                for (const envVar of entry.requiredEnvVars) {
                    const isSet = process.env[envVar] ? chalk.green("āœ“") : chalk.red("āœ—");
                    logger.always(`  ${isSet} ${envVar}`);
                }
            }
            // Installation command
            logger.always(`\n${chalk.bold("Installation:")}`);
            if (entry.npmPackage) {
                logger.always(chalk.cyan(`  neurolink mcp install ${entry.id}`));
                logger.always(chalk.dim(`  or: npx -y ${entry.npmPackage}`));
            }
            else if (entry.command) {
                logger.always(chalk.cyan(`  ${entry.command} ${entry.args?.join(" ") ?? ""}`));
            }
        }
        catch (error) {
            logger.error(chalk.red(`Registry info failed: ${error instanceof Error ? error.message : String(error)}`));
            process.exit(1);
        }
    }
    /**
     * Execute registry categories
     */
    static async executeRegistryCategories() {
        try {
            const { globalMCPRegistryClient } = await import("../../mcp/mcpRegistryClient.js");
            const categories = await withTimeout(globalMCPRegistryClient.getCategories(), 30_000, ErrorFactory.toolTimeout("registryGetCategories", 30_000));
            logger.always(chalk.bold("\nšŸ“ Available Categories:\n"));
            for (const category of categories) {
                const entries = await withTimeout(globalMCPRegistryClient.getByCategory(category), 30_000, ErrorFactory.toolTimeout("registryGetByCategory", 30_000));
                logger.always(`  ${chalk.cyan(category.padEnd(20))} (${entries.length} servers)`);
            }
        }
        catch (error) {
            logger.error(chalk.red(`Registry categories failed: ${error instanceof Error ? error.message : String(error)}`));
            process.exit(1);
        }
    }
    /**
     * Execute registry popular
     */
    static async executeRegistryPopular(argv) {
        try {
            const { globalMCPRegistryClient } = await import("../../mcp/mcpRegistryClient.js");
            const spinner = argv.quiet
                ? null
                : ora("Loading popular servers...").start();
            const entries = await withTimeout(globalMCPRegistryClient.getPopularServers(argv.limit ?? 10), 30_000, ErrorFactory.toolTimeout("registryGetPopular", 30_000));
            if (spinner) {
                spinner.succeed(`Top ${entries.length} popular servers`);
            }
            if (argv.format === "json") {
                logger.always(JSON.stringify(entries, null, 2));
                return;
            }
            logger.always(chalk.bold("\n🌟 Popular MCP Servers:\n"));
            for (let i = 0; i < entries.length; i++) {
                const entry = entries[i];
                const rank = chalk.yellow(`#${i + 1}`);
                const verified = entry.verified ? chalk.green(" āœ“") : "";
                logger.always(`${rank} ${chalk.cyan.bold(entry.name)}${verified}`);
                logger.always(`   ${chalk.gray(entry.description)}`);
                logger.always(`   ${chalk.dim(`Install: neurolink mcp install ${entry.id}`)}`);
                logger.always();
            }
        }
        catch (error) {
            logger.error(chalk.red(`Registry popular failed: ${error instanceof Error ? error.message : String(error)}`));
            process.exit(1);
        }
    }
    /**
     * Build options for annotate command
     */
    static buildAnnotateOptions(yargs) {
        return yargs
            .option("tool", {
            type: "string",
            alias: "t",
            description: "Tool name to annotate",
        })
            .option("server", {
            type: "string",
            alias: "s",
            description: "Server ID containing the tool",
        })
            .option("annotations", {
            type: "string",
            alias: "a",
            description: "Annotations as JSON string",
        })
            .option("read-only", {
            type: "boolean",
            description: "Set readOnlyHint annotation",
        })
            .option("destructive", {
            type: "boolean",
            description: "Set destructiveHint annotation",
        })
            .option("idempotent", {
            type: "boolean",
            description: "Set idempotentHint annotation",
        })
            .option("requires-confirmation", {
            type: "boolean",
            description: "Set requiresConfirmation annotation",
        })
            .option("tags", {
            type: "array",
            description: "Tags for the tool",
        })
            .option("estimated-duration", {
            type: "number",
            description: "Estimated execution duration in ms",
        })
            .option("rate-limit", {
            type: "number",
            description: "Rate limit hint (calls per minute)",
        })
            .option("cost", {
            type: "number",
            description: "Cost hint (arbitrary units)",
        })
            .option("complexity", {
            choices: ["simple", "medium", "complex"],
            description: "Complexity level",
        })
            .option("security-level", {
            choices: ["public", "internal", "restricted"],
            description: "Security classification",
        })
            .option("audit", {
            type: "boolean",
            description: "Whether tool execution should be audited",
        })
            .option("infer", {
            type: "boolean",
            description: "Infer annotations from tool name/description",
        })
            .option("list", {
            type: "boolean",
            description: "List all tools with their current annotations",
        })
            .option("validate", {
            type: "boolean",
            description: "Validate annotations without applying",
        })
            .example("neurolink mcp annotate --tool readFile --read-only", "Mark readFile as read-only")
            .example("neurolink mcp annotate --tool deleteFile --destructive --requires-confirmation", "Mark deleteFile as destructive requiring confirmation")
            .example('neurolink mcp annotate --tool myTool --annotations \'{"readOnlyHint": true, "tags": ["data"]}\'', "Set custom annotations via JSON")
            .example("neurolink mcp annotate --list", "List all tools with annotations")
            .example("neurolink mcp annotate --tool myTool --infer", "Infer annotations from tool name/description");
    }
    /**
     * Execute annotate command
     */
    static async executeAnnotate(argv) {
        try {
            const sdk = new NeuroLink();
            const servers = await sdk.listMCPServers();
            if (argv.list) {
                await this.listToolAnnotations(servers, argv);
                return;
            }
            const toolName = argv.tool;
            if (!toolName) {
                logger.error(chalk.red("Tool name is required. Use --tool <name> or --list to see all tools."));
                process.exit(1);
            }
            // `--infer` standalone path: when the user passes `--tool <name> --infer`
            // and no server scope, emit the heuristic annotations inferred purely
            // from the tool name/description. This makes `--infer` useful in any
            // environment (no MCP servers required) and matches user intent —
            // "show me what NeuroLink would infer for a tool with this name".
            const serverId = argv.server;
            if (argv.infer &&
                !serverId &&
                servers.filter((s) => s.status === "connected").length === 0) {
                const inferred = inferAnnotations({
                    name: toolName,
                    description: "",
                });
                const errors = validateAnnotations(inferred);
                if (errors.length > 0) {
                    logger.error(chalk.red("Inferred annotations validation errors:"));
                    for (const error of errors) {
                        logger.error(chalk.red(`  - ${error}`));
                    }
                    process.exit(1);
                }
                if (argv.format === "json") {
                    logger.always(JSON.stringify({ tool: toolName, inferred: true, annotations: inferred }, null, 2));
                }
                else {
                    logger.always(chalk.bold(`\nInferred annotations for ${chalk.cyan(toolName)}:`));
                    logger.always(JSON.stringify(inferred, null, 2));
                }
                return;
            }
            const foundTool = this.findToolForAnnotation(servers, toolName, serverId);
            if (foundTool === "ambiguous") {
                logger.error(chalk.red(`Tool '${toolName}' exists on multiple servers. Use --server <id> to specify which server to annotate.`));
                logger.always(chalk.yellow("Use 'neurolink mcp annotate --list' to see available tools and their server IDs."));
                process.exit(1);
            }
            if (!foundTool) {
                logger.error(chalk.red(`Tool '${toolName}' not found.${serverId ? ` Server: ${serverId}` : ""}`));
                logger.always(chalk.yellow("Use 'neurolink mcp annotate --list' to see available tools."));
                process.exit(1);
            }
            const annotations = this.buildAnnotationsFromArgs(argv, foundTool);
            const errors = validateAnnotations(annotations);
            if (errors.length > 0) {
                logger.error(chalk.red("Annotation validation errors:"));
                for (const error of errors) {
                    logger.error(chalk.red(`  - ${error}`));
                }
                process.exit(1);
            }
            // Validate only mode
            if (argv.validate) {
                logger.always(chalk.green("Annotations are valid:"));
                logger.always(JSON.stringify(annotations, null, 2));
                return;
            }
            this.printAnnotationUpdate(foundTool, annotations);
        }
        catch (error) {
            logger.error(chalk.red(`Annotate command failed: ${error instanceof Error ? error.message : String(error)}`));
            process.exit(1);
        }
    }
    static async getMCPStatusWithTimeout(sdk, timeoutMs = MCP_STATUS_TIMEOUT_MS) {
        return withTimeout(sdk.getMCPStatus(), timeoutMs, ErrorFactory.toolTimeout("mcpStatus", timeoutMs));
    }
    static async listToolAnnotations(servers, argv) {
        const spinner = argv.quiet
            ? null
            : ora("Loading tool annotations...").start();
        const allTools = this.collectToolAnnotations(servers);
        if (spinner) {
            spinner.succeed(`Found ${allTools.length} tools`);
        }
        if (argv.format === "json") {
            logger.always(JSON.stringify(allTools, null, 2));
            return;
        }
        logger.always(chalk.bold("\n Tool Annotations:\n"));
        const byServer = allTools.reduce((acc, tool) => {
            if (!acc[tool.serverId]) {
                acc[tool.serverId] = {
                    serverName: tool.serverName,
                    tools: [],
                };
            }
            acc[tool.serverId].tools.push(tool);
            return acc;
        }, {});
        for (const [serverId, { serverName, tools }] of Object.entries(byServer)) {
            logger.always(chalk.cyan.bold(`\n${serverName} (${serverId}):`));
            for (const tool of tools) {
                logger.always(`  ${chalk.yellow(tool.toolName)} ${chalk.gray(getAnnotationSummary(tool.annotations))}`);
                if (argv.detailed) {
                    logger.always(`    ${chalk.gray(tool.description)}`);
                }
            }
        }
        logger.always();
    }
    static collectToolAnnotations(servers) {
        const allTools = [];
        for (const server of servers) {
            if (server.status !== "connected") {
                continue;
            }
            for (const tool of server.tools || []) {
                const existing = tool
                    .annotations;
                allTools.push({
                    serverName: server.name,
                    serverId: server.id,
                    toolName: tool.name,
                    description: tool.description,
                    annotations: existing ??
                        inferAnnotations({
                            name: tool.name,
                            description: tool.description,
                        }),
                });
            }
        }
        return allTools;
    }
    static findToolForAnnotation(servers, toolName, serverId) {
        const matches = [];
        for (const server of servers) {
            if (serverId && server.id !== serverId) {
                continue;
            }
            for (const tool of server.tools || []) {
                if (tool.name === toolName) {
                    matches.push({
                        name: tool.name,
                        description: tool.description,
                        serverId: server.id,
                        serverName: server.name,
                    });
                }
            }
        }
        if (matches.length === 0) {
            return null;
        }
        if (matches.length > 1) {
            return "ambiguous";
        }
        return matches[0] ?? null;
    }
    static buildAnnotationsFromArgs(argv, foundTool) {
        let annotations = {};
        if (argv.annotations) {
            try {
                annotations = {
                    ...annotations,
                    ...JSON.parse(argv.annotations),
                };
            }
            catch {
                logger.error(chalk.red("Invalid JSON in --annotations"));
                process.exit(1);
            }
        }
        if (argv["read-only"] !== undefined) {
            annotations.readOnlyHint = argv["read-only"];
        }
        if (argv.destructive !== undefined) {
            annotations.destructiveHint = argv.destructive;
        }
        if (argv.idempotent !== undefined) {
            annotations.idempotentHint = argv.idempotent;
        }
        if (argv["requires-confirmation"] !== undefined) {
            annotations.requiresConfirmation = argv["requires-confirmation"];
        }
        if (argv.tags) {
            annotations.tags = argv.tags;
        }
        if (argv["estimated-duration"] !== undefined) {
            annotations.estimatedDuration = argv["estimated-duration"];
        }
        if (argv["rate-limit"] !== undefined) {
            annotations.rateLimitHint = argv["rate-limit"];
        }
        if (argv.cost !== undefined) {
            annotations.costHint = argv.cost;
        }
        if (argv.complexity) {
            annotations.complexity = argv.complexity;
        }
        if (argv["security-level"]) {
            annotations.securityLevel = argv["security-level"];
        }
        if (argv.audit !== undefined) {
            annotations.auditRequired = argv.audit;
        }
        if (argv.infer) {
            annotations = mergeAnnotations(inferAnnotations({
                name: foundTool.name,
                description: foundTool.description,
            }), annotations);
        }
        return annotations;
    }
    static printAnnotationUpdate(foundTool, annotations) {
        logger.always(chalk.bold("\n Tool Annotation Update:\n"));
        logger.always(`  Server: ${chalk.cyan(foundTool.serverName)} (${foundTool.serverId})`);
        logger.always(`  Tool: ${chalk.yellow(foundTool.name)}`);
        logger.always(`  Description: ${chalk.gray(foundTool.description)}`);
        logger.always();
        logger.always(chalk.bold("  Annotations:"));
        if (annotations.readOnlyHint !== undefined) {
            logger.always(`    readOnlyHint: ${annotations.readOnlyHint ? chalk.green("true") : chalk.red("false")}`);
        }
        if (annotations.destructiveHint !== undefined) {
            logger.always(`    destructiveHint: ${annotations.destructiveHint ? chalk.red("true") : chalk.green("false")}`);
        }
        if (annotations.idempotentHint !== undefined) {
            logger.always(`    idempotentHint: ${annotations.idempotentHint ? chalk.green("true") : chalk.gray("false")}`);
        }
        if (annotations.requiresConfirmation !== undefined) {
            logger.always(`    requiresConfirmation: ${annotations.requiresConfirmation ? chalk.yellow("true") : chalk.gray("false")}`);
        }
        if (annotations.tags?.length) {
            logger.always(`    tags: ${chalk.blue(annotations.tags.join(", "))}`);
        }
        if (annotations.estimatedDuration !== undefined) {
            logger.always(`    estimatedDuration: ${annotations.estimatedDuration}ms`);
        }
        if (annotations.rateLimitHint !== undefined) {
            logger.always(`    rateLimitHint: ${annotations.rateLimitHint} calls/min`);
        }
        if (annotations.costHint !== undefined) {
            logger.always(`    costHint: ${annotations.costHint}`);
        }
        if (annotations.complexity) {
            logger.always(`    complexity: ${chalk.cyan(annotations.complexity)}`);
        }
        if (annotations.securityLevel) {
            const secColor = annotations.securityLevel === "restricted"
                ? chalk.red
                : annotations.securityLevel === "internal"
                    ? chalk.yellow
                    : chalk.green;
            logger.always(`    securityLevel: ${secColor(annotations.securityLevel)}`);
        }
        if (annotations.auditRequired !== undefined) {
            logger.always(`    auditRequired: ${annotations.auditRequired ? chalk.yellow("true") : chalk.gray("false")}`);
        }
        logger.always();
        logger.always(chalk.gray("  Summary: ") + getAnnotationSummary(annotations));
        logger.always();
        logger.always(chalk.blue("Note: Annotations displayed above. To persist annotations, add them to your MCP server configuration."));
        logger.always(chalk.gray("Example: Add to your server's tool definition or use environment-specific annotation overrides."));
    }
}
//# sourceMappingURL=mcp.js.map