UNPKG

@aurracloud/mcp-cli

Version:

A command-line tool to install, manage, and setup MCP (Model Context Protocol) servers with Docker support

865 lines • 88.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.installCommand = installCommand; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const readline = __importStar(require("readline")); const logger_1 = require("../utils/logger"); const child_process_1 = require("child_process"); const util_1 = require("util"); const client_ops_1 = require("../client-ops"); const smithery_1 = require("../smithery"); const npm_1 = require("../npm"); const python_1 = require("../python"); const git_1 = require("../git"); const docker_1 = require("../docker"); const lib_1 = require("../lib"); const cloud_sse_1 = require("../utils/cloud-sse"); const smithery_2 = require("../smithery"); const AURRA_REGISTRY = "https://registry.npmjs.org"; const execPromise = (0, util_1.promisify)(child_process_1.exec); /** * Parses command line arguments from --args flag * Expected format: key=value or --key=value or -k=value */ function parseArgsFromFlag(args) { const envVars = {}; const cmdArgs = []; for (const arg of args) { if (arg.includes('=')) { const [key, ...valueParts] = arg.split('='); const value = valueParts.join('='); // Handle values that contain '=' // If it starts with -- or -, treat as command argument if (key.startsWith('-')) { cmdArgs.push(key, value); } else { // Otherwise treat as environment variable envVars[key] = value; } } else { // Arguments without = are treated as command flags cmdArgs.push(arg); } } return { envVars, cmdArgs }; } /** * Extract GitHub repository information from npm package.json */ function extractGitHubRepoFromPackageInfo(packageInfo) { if (!packageInfo.repository) { return null; } let repoUrl; // Handle repository object format if (typeof packageInfo.repository === 'object' && packageInfo.repository.url) { repoUrl = packageInfo.repository.url; } else if (typeof packageInfo.repository === 'string') { repoUrl = packageInfo.repository; } else { return null; } // Clean up common prefixes and suffixes repoUrl = repoUrl .replace(/^git\+/, '') .replace(/^git:\/\//, 'https://') .replace(/\.git$/, ''); // Check if it's a GitHub URL if (repoUrl.includes('github.com')) { return repoUrl; } return null; } /** * Prompts user for input with optional validation */ async function promptUser(question, isPassword = false) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { rl.question(question, (answer) => { rl.close(); resolve(answer.trim()); }); }); } /** * Collects environment variables from user */ async function collectEnvironmentVariables(config) { const envVars = (0, lib_1.extractEnvironmentVariables)(config); const collectedVars = {}; console.log('\nšŸ“‹ Environment Variables Setup'); console.log('====================================='); // Collect required environment variables if (envVars.required.length > 0) { console.log('\nšŸ”“ Required Environment Variables:'); for (const envName of envVars.required) { const envConfig = config.environment.required[envName]; const isSensitive = envVars.sensitive.includes(envName); console.log(`\n${envName}:`); console.log(` Description: ${envConfig.description}`); if (envConfig.pattern) { console.log(` Pattern: ${envConfig.pattern}`); } if (envConfig.format) { console.log(` Format: ${envConfig.format}`); } let value = ''; while (!value) { value = await promptUser(` Enter ${envName}: `, isSensitive); if (!value) { console.log(' āŒ This environment variable is required. Please provide a value.'); } } collectedVars[envName] = value; } } // Collect optional environment variables if (envVars.optional.length > 0) { console.log('\n🟔 Optional Environment Variables:'); console.log('(Press Enter to skip or use default value)'); for (const envName of envVars.optional) { const envConfig = config.environment.optional[envName]; const isSensitive = envVars.sensitive.includes(envName); console.log(`\n${envName}:`); console.log(` Description: ${envConfig.description}`); if (envConfig.default !== undefined) { console.log(` Default: ${envConfig.default}`); } const value = await promptUser(` Enter ${envName} (optional): `, isSensitive); if (value) { collectedVars[envName] = value; } else if (envConfig.default !== undefined) { collectedVars[envName] = String(envConfig.default); } } } return collectedVars; } /** * Collects command line arguments from user */ async function collectCommandLineArguments(config) { const args = (0, lib_1.extractCommandLineArguments)(config); const collectedArgs = []; if (args.required.length === 0 && args.optional.length === 0) { return collectedArgs; } console.log('\nāš™ļø Command Line Arguments Setup'); console.log('====================================='); // Collect required arguments if (args.required.length > 0) { console.log('\nšŸ”“ Required Arguments:'); for (const arg of args.required) { console.log(`\n${arg.name}:`); console.log(` Description: ${arg.description}`); if (arg.aliases.length > 0) { console.log(` Aliases: ${arg.aliases.join(', ')}`); } let value = ''; while (!value) { value = await promptUser(` Enter value for ${arg.name}: `); if (!value) { console.log(' āŒ This argument is required. Please provide a value.'); } } // Use the first alias if available, otherwise use the name const argFlag = arg.aliases.length > 0 ? arg.aliases[0] : `--${arg.name}`; collectedArgs.push(argFlag, value); } } // Collect optional arguments if (args.optional.length > 0) { console.log('\n🟔 Optional Arguments:'); console.log('(Press Enter to skip or use default value)'); for (const arg of args.optional) { console.log(`\n${arg.name}:`); console.log(` Description: ${arg.description}`); if (arg.default !== undefined) { console.log(` Default: ${arg.default}`); } if (arg.aliases.length > 0) { console.log(` Aliases: ${arg.aliases.join(', ')}`); } const value = await promptUser(` Enter value for ${arg.name} (optional): `); if (value) { const argFlag = arg.aliases.length > 0 ? arg.aliases[0] : `--${arg.name}`; collectedArgs.push(argFlag, value); } } } return collectedArgs; } /** * Checks if a package has MCP configuration */ async function checkMcpConfiguration(packageName, jsonMode = false) { logger_1.logger.verbose(`Starting MCP configuration check for: ${packageName}`); // First, check if it's a cloud SSE tool slug try { logger_1.logger.info(`Checking if ${packageName} is a cloud SSE tool...`); logger_1.logger.verbose(`Attempting to find cloud tool with slug: ${packageName}`); const cloudTool = await (0, cloud_sse_1.isCloudToolSlug)(packageName); if (cloudTool) { logger_1.logger.info(`Found cloud SSE tool: ${cloudTool.name}`); logger_1.logger.verbose(`Cloud tool details: ${JSON.stringify(cloudTool, null, 2)}`); try { // Validate authentication const apiKey = (0, cloud_sse_1.validateCloudAuth)(); const sseUrl = (0, cloud_sse_1.generateCloudToolSSEUrl)(packageName, apiKey); // Test access to the SSE endpoint logger_1.logger.verbose(`Testing access to cloud SSE endpoint: ${sseUrl}`); // const accessible = await testCloudToolAccess(packageName); // if (!accessible) { // logger.warn(`Cloud tool ${packageName} found but SSE endpoint is not accessible`); // if (jsonMode) { // const result = { // success: false, // error: 'Cloud tool SSE endpoint is not accessible', // cloudTool: cloudTool.name, // suggestion: 'Please check your API key and try again' // }; // console.log(JSON.stringify(result, null, 2)); // process.exit(1); // } else { // throw new Error(`Cloud tool SSE endpoint is not accessible. Please check your API key.`); // } // } logger_1.logger.info(`Cloud SSE tool ${cloudTool.name} is accessible and ready for installation`); return { hasMcpConfig: false, // Cloud SSE tools don't use traditional MCP config source: 'cloud-sse', cloudTool, sseUrl, packageVersion: '1.0.0' // Cloud tools don't have traditional versions }; } catch (authError) { logger_1.logger.warn(`Authentication failed for cloud tool ${packageName}: ${authError}`); if (jsonMode) { const result = { success: false, error: 'Authentication required for cloud tools', cloudTool: cloudTool.name, suggestion: 'Please run "mcp login" to authenticate with aurracloud.com' }; console.log(JSON.stringify(result, null, 2)); process.exit(1); } else { throw new Error(`Authentication required for cloud tools. Please run "mcp login" first.`); } } } } catch (error) { logger_1.logger.debug(`Cloud SSE tool check failed for ${packageName}: ${error}`); // Continue to other checks if cloud tool check fails } // Second, try to check if it's a Git repository if (packageName.includes('github.com')) { try { logger_1.logger.info(`Checking if ${packageName} is a Git repository with MCP configuration...`); logger_1.logger.verbose(`Detected GitHub URL, attempting to clone and analyze repository`); const gitResult = await (0, git_1.checkGitRepoExistence)(packageName); if (gitResult.exists && gitResult.clonePath) { logger_1.logger.verbose(`Repository cloned successfully to: ${gitResult.clonePath}`); // If a specific path was provided and a server was found there if (gitResult.selectedServer) { logger_1.logger.debug(`Selected server found: ${JSON.stringify(gitResult.selectedServer, null, 2)}`); // Check for MCP configuration in Node.js packages if (gitResult.selectedServer.packageInfo?.mcpServer) { logger_1.logger.info(`Found MCP server configuration in specified path: ${gitResult.targetPath}`); logger_1.logger.verbose(`MCP configuration detected in Node.js package`); const result = (0, lib_1.parseMcpServerConfig)(gitResult.selectedServer.packageInfo); return { hasMcpConfig: true, config: result.config || undefined, validation: result.validation, source: 'git', clonePath: gitResult.clonePath, packageVersion: gitResult.selectedServer.packageInfo.version, targetPath: gitResult.targetPath, selectedServer: gitResult.selectedServer }; } // For Python packages, we don't have MCP config in the same format yet // but we still return the server info for installation else if (gitResult.selectedServer.pythonPackageInfo) { logger_1.logger.info(`Found Python package in specified path: ${gitResult.targetPath}`); logger_1.logger.verbose(`Python package detected, no MCP configuration format available yet`); return { hasMcpConfig: false, source: 'git', clonePath: gitResult.clonePath, packageVersion: gitResult.selectedServer.pythonPackageInfo.version, targetPath: gitResult.targetPath, selectedServer: gitResult.selectedServer }; } } // If multiple MCP servers were found, let user choose if (gitResult.mcpServers && gitResult.mcpServers.length > 0) { logger_1.logger.verbose(`Found ${gitResult.mcpServers.length} MCP server(s) in repository`); if (gitResult.mcpServers.length === 1) { // Only one server found, use it const server = gitResult.mcpServers[0]; logger_1.logger.info(`Found MCP server configuration: ${server.name} at ${server.path}`); logger_1.logger.verbose(`Single server auto-selected: ${server.name} (${server.runtime})`); // Check for Node.js packages with MCP configuration if (server.packageInfo?.mcpServer) { const result = (0, lib_1.parseMcpServerConfig)(server.packageInfo); return { hasMcpConfig: true, config: result.config || undefined, validation: result.validation, source: 'git', clonePath: gitResult.clonePath, packageVersion: server.packageInfo.version, targetPath: gitResult.targetPath, selectedServer: server }; } else { // Python packages or Node packages without MCP config logger_1.logger.verbose(`Server found but no MCP configuration available`); // Try to find smithery.yaml for this server if (gitResult.clonePath && server.path) { const serverPath = path.join(gitResult.clonePath, server.path); logger_1.logger.verbose(`Checking for smithery.yaml in server path: ${serverPath}`); const smitheryResult = await (0, smithery_2.findAndLoadSmitheryConfig)(serverPath); if (smitheryResult.found && smitheryResult.envResult.environmentVariables) { logger_1.logger.info(`Found smithery.yaml configuration for ${server.name}`); logger_1.logger.verbose(`Smithery config found with ${Object.keys(smitheryResult.envResult.environmentVariables).length} environment variables`); // Convert smithery config to MCP format const envVarNames = Object.keys(smitheryResult.envResult.environmentVariables); const mcpEnvConfig = (0, smithery_2.convertSmitheryToMcpConfig)(smitheryResult.envResult, envVarNames); // Create a synthetic MCP config from smithery.yaml const syntheticMcpConfig = { name: server.name, version: server.packageInfo?.version || server.pythonPackageInfo?.version || '1.0.0', description: server.packageInfo?.description || server.pythonPackageInfo?.description || `MCP server: ${server.name}`, connection: { type: 'stdio', command: smitheryResult.envResult.command || 'node', args: smitheryResult.envResult.args || ['dist/index.js'], timeout: 30000, headers: {} }, environment: mcpEnvConfig, arguments: { required: {}, optional: {} }, files: { required: {}, optional: {} }, capabilities: { tools: false, resources: false, prompts: false }, metadata: { keywords: [], requires_extras: [] } }; logger_1.logger.info(`Generated MCP configuration from smithery.yaml for ${server.name}`); logger_1.logger.debug(`Synthetic MCP config: ${JSON.stringify(syntheticMcpConfig, null, 2)}`); return { hasMcpConfig: true, config: syntheticMcpConfig, validation: { valid: true, errors: [], warnings: [] }, source: 'git', clonePath: gitResult.clonePath, packageVersion: server.packageInfo?.version || server.pythonPackageInfo?.version, targetPath: gitResult.targetPath, selectedServer: server }; } } return { hasMcpConfig: false, source: 'git', clonePath: gitResult.clonePath, packageVersion: server.packageInfo?.version || server.pythonPackageInfo?.version, targetPath: gitResult.targetPath, selectedServer: server }; } } else { // Multiple servers found, let user choose logger_1.logger.verbose(`Multiple servers found, prompting user for selection`); if (jsonMode) { // In JSON mode, return error with available servers for user to specify const availableServers = gitResult.mcpServers.map((server, index) => ({ index: index + 1, name: server.name, path: server.path, runtime: server.runtime, description: server.packageInfo?.description || server.pythonPackageInfo?.description || 'No description' })); const result = { success: false, error: 'Multiple MCP servers found. Please specify the exact path.', availableServers, suggestion: 'Use the full GitHub URL with the specific server path (e.g., https://github.com/owner/repo/tree/main/path/to/server)' }; console.log(JSON.stringify(result, null, 2)); (0, git_1.cleanupClonedRepo)(gitResult.clonePath); process.exit(1); } console.log('\nšŸŽÆ Multiple MCP servers found in this repository:'); console.log('====================================='); gitResult.mcpServers.forEach((server, index) => { console.log(`${index + 1}. ${server.name}`); console.log(` Path: ${server.path}`); console.log(` Runtime: ${server.runtime}`); console.log(` Description: ${server.packageInfo?.description || server.pythonPackageInfo?.description || 'No description'}`); console.log(''); }); const choice = await promptUser(`Please select a server (1-${gitResult.mcpServers.length}): `); const selectedIndex = parseInt(choice) - 1; if (selectedIndex >= 0 && selectedIndex < gitResult.mcpServers.length) { const selectedServer = gitResult.mcpServers[selectedIndex]; logger_1.logger.info(`Selected MCP server: ${selectedServer.name} at ${selectedServer.path}`); logger_1.logger.debug(`User selected server ${selectedIndex + 1}: ${selectedServer.name}`); // Check for Node.js packages with MCP configuration if (selectedServer.packageInfo?.mcpServer) { const result = (0, lib_1.parseMcpServerConfig)(selectedServer.packageInfo); return { hasMcpConfig: true, config: result.config || undefined, validation: result.validation, source: 'git', clonePath: gitResult.clonePath, packageVersion: selectedServer.packageInfo.version, targetPath: gitResult.targetPath, selectedServer: selectedServer }; } else { // Python packages or Node packages without MCP config // Try to find smithery.yaml for this selected server if (gitResult.clonePath && selectedServer.path) { const serverPath = path.join(gitResult.clonePath, selectedServer.path); logger_1.logger.verbose(`Checking for smithery.yaml in selected server path: ${serverPath}`); const smitheryResult = await (0, smithery_2.findAndLoadSmitheryConfig)(serverPath); if (smitheryResult.found && smitheryResult.envResult.environmentVariables) { logger_1.logger.info(`Found smithery.yaml configuration for selected server ${selectedServer.name}`); logger_1.logger.verbose(`Smithery config found with ${Object.keys(smitheryResult.envResult.environmentVariables).length} environment variables`); // Convert smithery config to MCP format const envVarNames = Object.keys(smitheryResult.envResult.environmentVariables); const mcpEnvConfig = (0, smithery_2.convertSmitheryToMcpConfig)(smitheryResult.envResult, envVarNames); // Create a synthetic MCP config from smithery.yaml const syntheticMcpConfig = { name: selectedServer.name, version: selectedServer.packageInfo?.version || selectedServer.pythonPackageInfo?.version || '1.0.0', description: selectedServer.packageInfo?.description || selectedServer.pythonPackageInfo?.description || `MCP server: ${selectedServer.name}`, connection: { type: 'stdio', command: smitheryResult.envResult.command || 'node', args: smitheryResult.envResult.args || ['dist/index.js'], timeout: 30000, headers: {} }, environment: mcpEnvConfig, arguments: { required: {}, optional: {} }, files: { required: {}, optional: {} }, capabilities: { tools: false, resources: false, prompts: false }, metadata: { keywords: [], requires_extras: [] } }; logger_1.logger.info(`Generated MCP configuration from smithery.yaml for selected server ${selectedServer.name}`); logger_1.logger.debug(`Synthetic MCP config: ${JSON.stringify(syntheticMcpConfig, null, 2)}`); return { hasMcpConfig: true, config: syntheticMcpConfig, validation: { valid: true, errors: [], warnings: [] }, source: 'git', clonePath: gitResult.clonePath, packageVersion: selectedServer.packageInfo?.version || selectedServer.pythonPackageInfo?.version, targetPath: gitResult.targetPath, selectedServer: selectedServer }; } } return { hasMcpConfig: false, source: 'git', clonePath: gitResult.clonePath, packageVersion: selectedServer.packageInfo?.version || selectedServer.pythonPackageInfo?.version, targetPath: gitResult.targetPath, selectedServer: selectedServer }; } } else { logger_1.logger.error('Invalid selection. Installation aborted.'); (0, git_1.cleanupClonedRepo)(gitResult.clonePath); process.exit(1); } } } // Check if the root package.json has MCP configuration (backward compatibility) if (gitResult.packageInfo && gitResult.packageInfo.mcpServer) { logger_1.logger.info('Found MCP server configuration in root package.json'); logger_1.logger.verbose('Using root package.json MCP configuration (backward compatibility)'); const result = (0, lib_1.parseMcpServerConfig)(gitResult.packageInfo); return { hasMcpConfig: true, config: result.config || undefined, validation: result.validation, source: 'git', clonePath: gitResult.clonePath, packageVersion: gitResult.packageInfo.version, targetPath: gitResult.targetPath, selectedServer: gitResult.packageInfo }; } logger_1.logger.verbose('No MCP configuration found in Git repository'); // Try to find smithery.yaml in the root directory as a last resort if (gitResult.clonePath) { logger_1.logger.verbose(`Checking for smithery.yaml in repository root: ${gitResult.clonePath}`); const smitheryResult = await (0, smithery_2.findAndLoadSmitheryConfig)(gitResult.clonePath); if (smitheryResult.found && smitheryResult.envResult.environmentVariables) { logger_1.logger.info(`Found smithery.yaml configuration in repository root`); logger_1.logger.verbose(`Smithery config found with ${Object.keys(smitheryResult.envResult.environmentVariables).length} environment variables`); // Convert smithery config to MCP format const envVarNames = Object.keys(smitheryResult.envResult.environmentVariables); const mcpEnvConfig = (0, smithery_2.convertSmitheryToMcpConfig)(smitheryResult.envResult, envVarNames); // Create a synthetic MCP config from smithery.yaml const syntheticMcpConfig = { name: gitResult.packageInfo?.name || packageName, version: gitResult.packageInfo?.version || '1.0.0', description: gitResult.packageInfo?.description || `MCP server: ${packageName}`, connection: { type: 'stdio', command: smitheryResult.envResult.command || 'node', args: smitheryResult.envResult.args || ['dist/index.js'], timeout: 30000, headers: {} }, environment: mcpEnvConfig, arguments: { required: {}, optional: {} }, files: { required: {}, optional: {} }, capabilities: { tools: false, resources: false, prompts: false }, metadata: { keywords: [], requires_extras: [] } }; logger_1.logger.info(`Generated MCP configuration from smithery.yaml in repository root`); logger_1.logger.debug(`Synthetic MCP config: ${JSON.stringify(syntheticMcpConfig, null, 2)}`); return { hasMcpConfig: true, config: syntheticMcpConfig, validation: { valid: true, errors: [], warnings: [] }, source: 'git', clonePath: gitResult.clonePath, packageVersion: gitResult.packageInfo?.version, targetPath: gitResult.targetPath, selectedServer: gitResult.packageInfo }; } } return { hasMcpConfig: false, source: 'git', clonePath: gitResult.clonePath, packageVersion: gitResult.packageInfo?.version, targetPath: gitResult.targetPath, selectedServer: gitResult.packageInfo }; } } catch (error) { logger_1.logger.warn(`Failed to check Git repository: ${error}`); logger_1.logger.debug(`Git repository check error: ${error instanceof Error ? error.stack : error}`); } } // If not a Git repo or Git check failed, try npm try { logger_1.logger.info(`Checking npm registry for ${packageName} MCP configuration...`); logger_1.logger.verbose('Querying npm registry API for package information'); const npmResult = await (0, npm_1.checkPackageExistence)(packageName); if (npmResult.exists && npmResult.packageInfo) { logger_1.logger.verbose(`Package found on npm: ${npmResult.packageInfo.name}@${npmResult.packageInfo.version}`); const packageJson = npmResult.packageInfo; // Check for MCP configuration in npm package let mcpConfigResult = { hasMcpConfig: false, source: 'npm', packageVersion: packageJson.version }; if (packageJson.mcpServer) { logger_1.logger.info('Found MCP server configuration in npm package'); logger_1.logger.verbose('MCP configuration detected in npm package.json'); const result = (0, lib_1.parseMcpServerConfig)(packageJson); mcpConfigResult = { hasMcpConfig: true, config: result.config || undefined, validation: result.validation, source: 'npm', packageVersion: packageJson.version }; } // Check for GitHub repository in npm package.json for Docker building const githubRepo = extractGitHubRepoFromPackageInfo(npmResult.packageInfo); if (githubRepo) { logger_1.logger.info('Found GitHub repository in npm package.json, checking for Dockerfile...'); logger_1.logger.verbose(`GitHub repository URL: ${githubRepo}`); try { const gitResult = await (0, git_1.checkGitRepoExistence)(githubRepo); if (gitResult.exists && gitResult.clonePath) { logger_1.logger.info('Successfully cloned GitHub repository from npm package.json'); logger_1.logger.verbose(`Repository cloned for Docker analysis: ${gitResult.clonePath}`); // If no MCP config was found in npm package, check for smithery.yaml in the cloned repo if (!mcpConfigResult.hasMcpConfig) { logger_1.logger.verbose(`No MCP config in npm package, checking for smithery.yaml in cloned repository: ${gitResult.clonePath}`); const smitheryResult = await (0, smithery_2.findAndLoadSmitheryConfig)(gitResult.clonePath); if (smitheryResult.found && smitheryResult.envResult.environmentVariables) { logger_1.logger.info(`Found smithery.yaml configuration in npm package's GitHub repository`); logger_1.logger.verbose(`Smithery config found with ${Object.keys(smitheryResult.envResult.environmentVariables).length} environment variables`); // Convert smithery config to MCP format const envVarNames = Object.keys(smitheryResult.envResult.environmentVariables); const mcpEnvConfig = (0, smithery_2.convertSmitheryToMcpConfig)(smitheryResult.envResult, envVarNames); // Create a synthetic MCP config from smithery.yaml const syntheticMcpConfig = { name: packageJson.name, version: packageJson.version, description: packageJson.description || `MCP server: ${packageJson.name}`, connection: { type: 'stdio', command: smitheryResult.envResult.command || 'node', args: smitheryResult.envResult.args || ['dist/index.js'], timeout: 30000, headers: {} }, environment: mcpEnvConfig, arguments: { required: {}, optional: {} }, files: { required: {}, optional: {} }, capabilities: { tools: false, resources: false, prompts: false }, metadata: { keywords: packageJson.keywords || [], requires_extras: [] } }; logger_1.logger.info(`Generated MCP configuration from smithery.yaml for npm package ${packageJson.name}`); logger_1.logger.debug(`Synthetic MCP config: ${JSON.stringify(syntheticMcpConfig, null, 2)}`); // Update the result to indicate we found MCP config from smithery.yaml mcpConfigResult = { hasMcpConfig: true, config: syntheticMcpConfig, validation: { valid: true, errors: [], warnings: [] }, source: 'npm', packageVersion: packageJson.version, clonePath: gitResult.clonePath }; } else { // Merge the git information with npm configuration (original behavior) mcpConfigResult.clonePath = gitResult.clonePath; // Use git package version if npm version is not available if (!mcpConfigResult.packageVersion && gitResult.packageInfo?.version) { mcpConfigResult.packageVersion = gitResult.packageInfo.version; } } } else { // Merge the git information with npm configuration (original behavior) mcpConfigResult.clonePath = gitResult.clonePath; // Use git package version if npm version is not available if (!mcpConfigResult.packageVersion && gitResult.packageInfo?.version) { mcpConfigResult.packageVersion = gitResult.packageInfo.version; } } } } catch (error) { logger_1.logger.warn(`Failed to clone GitHub repository from npm package.json: ${error}`); logger_1.logger.debug(`GitHub clone error: ${error instanceof Error ? error.stack : error}`); } } else { logger_1.logger.verbose('No GitHub repository found in npm package.json'); } return mcpConfigResult; } else { logger_1.logger.verbose('Package not found on npm registry'); } } catch (error) { logger_1.logger.warn(`Failed to check npm package: ${error}`); logger_1.logger.debug(`npm check error: ${error instanceof Error ? error.stack : error}`); } // If not found on npm, try PyPI for Python packages try { logger_1.logger.info(`Checking PyPI for ${packageName} MCP configuration...`); logger_1.logger.verbose('Querying PyPI API for Python package information'); const pythonResult = await (0, python_1.checkPythonPackageExistence)(packageName); if (pythonResult.exists && pythonResult.packageInfo) { logger_1.logger.verbose(`Python package found on PyPI: ${pythonResult.packageInfo.name}@${pythonResult.packageInfo.version}`); // For Python packages, we don't have MCP configuration in PyPI metadata // but we can still return basic package information return { hasMcpConfig: false, source: 'none', // No MCP config found, but package exists packageVersion: pythonResult.packageInfo.version }; } else { logger_1.logger.verbose('Package not found on PyPI'); } } catch (error) { logger_1.logger.warn(`Failed to check Python package: ${error}`); logger_1.logger.debug(`PyPI check error: ${error instanceof Error ? error.stack : error}`); } logger_1.logger.verbose('No MCP configuration found in any registry'); return { hasMcpConfig: false, source: 'none' }; } /** * Command to install and setup an MCP server */ async function installCommand(options) { // Package is now a required positional argument const packageName = options.package; const useDocker = !options.direct; const jsonMode = options.json || false; logger_1.logger.info(`Installing MCP server ${packageName} for ${options.client} environment`); logger_1.logger.verbose(`Installation mode: ${useDocker ? 'Docker (sandboxed)' : 'Direct (host)'}`); logger_1.logger.debug(`Install options: ${JSON.stringify(options)}`); // Parse args if provided let providedEnvVars = {}; let providedCmdArgs = []; if (options.args && options.args.length > 0) { const parsed = parseArgsFromFlag(options.args); providedEnvVars = parsed.envVars; providedCmdArgs = parsed.cmdArgs; logger_1.logger.verbose(`Parsed args - env vars: ${Object.keys(providedEnvVars).length}, cmd args: ${providedCmdArgs.length}`); logger_1.logger.debug(`Provided environment variables: ${JSON.stringify(providedEnvVars)}`); logger_1.logger.debug(`Provided command arguments: ${JSON.stringify(providedCmdArgs)}`); } try { // Check for MCP configuration first logger_1.logger.verbose('Starting MCP configuration detection phase'); const mcpResult = await checkMcpConfiguration(packageName, jsonMode); if (mcpResult.hasMcpConfig && mcpResult.config) { if (!jsonMode) { console.log('\nšŸŽ‰ MCP Server Configuration Detected!'); console.log('====================================='); } logger_1.logger.verbose('Processing detected MCP configuration'); const summary = (0, lib_1.createConfigSummary)(mcpResult.config); if (!jsonMode) { console.log(`Name: ${summary.name}`); console.log(`Version: ${summary.version}`); console.log(`Description: ${summary.description}`); console.log(`Connection Type: ${summary.connectionType}`); console.log(`Capabilities: ${summary.capabilities.join(', ') || 'none'}`); if (summary.category) { console.log(`Category: ${summary.category}`); } } logger_1.logger.debug(`Full MCP config summary: ${JSON.stringify(summary, null, 2)}`); if (mcpResult.validation && !mcpResult.validation.valid) { if (!jsonMode) { console.log('\nāš ļø Configuration Validation Issues:'); console.log((0, lib_1.formatValidationResults)(mcpResult.validation)); } logger_1.logger.verbose('MCP configuration has validation issues'); } let envVars = {}; let cmdArgs = []; if (summary.requiresSetup) { if (jsonMode) { // In JSON mode, use provided args or fail if required args are missing envVars = providedEnvVars; cmdArgs = providedCmdArgs; // Validate that all required environment variables are provided const requiredEnvVars = (0, lib_1.extractEnvironmentVariables)(mcpResult.config); const missingEnvVars = requiredEnvVars.required.filter(envName => !(envName in envVars)); if (missingEnvVars.length > 0) { const result = { success: false, error: 'Missing required environment variables', missingEnvVars, requiredEnvVars: requiredEnvVars.required, optionalEnvVars: requiredEnvVars.optional }; console.log(JSON.stringify(result, null, 2)); process.exit(1); } // Validate that all required command arguments are provided const requiredCmdArgs = (0, lib_1.extractCommandLineArguments)(mcpResult.config); // For simplicity, we'll assume all required args are provided if any args are given // A more sophisticated implementation could validate specific argument names logger_1.logger.verbose('Using provided configuration arguments in JSON mode'); } else { console.log('\nšŸ“‹ This server requires additional configuration.'); logger_1.logger.verbose('Server requires user configuration input'); // Collect environment variables logger_1.logger.verbose('Collecting environment variables from user'); envVars = await collectEnvironmentVariables(mcpResult.config); logger_1.logger.debug(`Collected environment variables: ${Object.keys(envVars)}`); // Collect command line arguments logger_1.logger.verbose('Collecting command line arguments from user'); cmdArgs = await collectCommandLineArguments(mcpResult.config); logger_1.logger.debug(`Collected command arguments: ${cmdArgs}`); console.log('\nāœ… Configuration collected successfully!'); } } else { if (!jsonMode) { console.log('\nāœ… No additional configuration required.'); } logger_1.logger.verbose('MCP server requires no additional setup'); // Still use provided args if available envVars = providedEnvVars; cmdArgs = providedCmdArgs; } // Clean up cloned repository if it exists if (mcpResult.clonePath) { logger_1.logger.verbose(`Cleaning up cloned repository: ${mcpResult.clonePath}`); (0, git_1.cleanupClonedRepo)(mcpResult.clonePath); } // Continue with installation using collected configuration await proceedWithInstallation(packageName, options, mcpResult.config, envVars, cmdArgs, mcpResult); return; } else { logger_1.logger.info('No MCP server configuration found. Proceeding with standard installation.'); logger_1.logger.verbose(`MCP check result: source=${mcpResult.source}, hasMcpConfig=${mcpResult.hasMcpConfig}`); } // Standard installation flow logger_1.logger.verbose('Proceeding with standard installation flow'); await proceedWithInstallation(packageName, options, undefined, providedEnvVars, providedCmdArgs, mcpRe