@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
JavaScript
"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