Skip to main content
Glama

MCP Environment & Installation Manager

by devlimelabs
package-manager.js10.5 kB
import { exec as execCallback } from 'child_process'; import * as path from 'path'; import { promisify } from 'util'; import { REGISTRY_FILE } from '../config.js'; import { ensureDir, fileExists, readJsonFileOrDefault, writeJsonFile } from '../utils/fs-utils.js'; // Promisified exec const exec = promisify(execCallback); /** * Service for managing MCP package installations */ export class PackageManager { configService; registryPath; packagesDir; registry; preferredPackageManager; /** * Creates a new PackageManager instance * @param configService Configuration service */ constructor(configService) { this.configService = configService; const config = configService.getInstallationConfig(); this.packagesDir = config.packageManager.installationDir; this.registryPath = REGISTRY_FILE; this.preferredPackageManager = config.packageManager.preferredPackageManager; this.registry = { packages: {} }; } /** * Loads the package registry from disk */ async loadRegistry() { this.registry = await readJsonFileOrDefault(this.registryPath, { packages: {} }); // Ensure packages directory exists await ensureDir(this.packagesDir); } /** * Saves the package registry to disk */ async saveRegistry() { await writeJsonFile(this.registryPath, this.registry); } /** * Gets all installed packages */ getInstalledPackages() { return Object.values(this.registry.packages); } /** * Gets an installed package by name */ getInstalledPackage(packageName) { return this.registry.packages[packageName]; } /** * Installs an MCP package * @param packageName Package name to install * @param version Optional specific version to install */ async installPackage(packageName, version) { try { // Create package-specific directory const packageDir = path.join(this.packagesDir, packageName.replace('/', '-')); await ensureDir(packageDir); // Create package.json if it doesn't exist const packageJsonPath = path.join(packageDir, 'package.json'); if (!(await fileExists(packageJsonPath))) { await writeJsonFile(packageJsonPath, { name: 'mcp-package-wrapper', version: '1.0.0', private: true, type: 'module' }); } // Install the package const installCmd = this.getInstallCommand(packageName, version); await exec(installCmd, { cwd: packageDir }); // Get installed version const nodeModulesDir = path.join(packageDir, 'node_modules'); const installedVersion = await this.getInstalledVersion(nodeModulesDir, packageName); // Find bin path if it exists const binPath = await this.findPackageBin(nodeModulesDir, packageName); // Add to registry const now = new Date().toISOString(); const localPath = path.join(nodeModulesDir, packageName); const installedPackage = { name: packageName, version: installedVersion, localPath, binPath, installedAt: now, updatedAt: now, dependencies: [], // TODO: Extract dependencies usedByConfigs: [] }; this.registry.packages[packageName] = installedPackage; await this.saveRegistry(); return { success: true, packageName, version: installedVersion, localPath, binPath }; } catch (error) { return { success: false, packageName, version: version || 'latest', localPath: '', error: error instanceof Error ? error.message : String(error) }; } } /** * Updates an installed package * @param packageName Package name to update * @param version Optional target version */ async updatePackage(packageName, version) { const existing = this.getInstalledPackage(packageName); if (!existing) { return this.installPackage(packageName, version); } try { const packageDir = path.dirname(path.dirname(existing.localPath)); // Update the package const updateCmd = this.getInstallCommand(packageName, version); await exec(updateCmd, { cwd: packageDir }); // Get installed version const nodeModulesDir = path.join(packageDir, 'node_modules'); const installedVersion = await this.getInstalledVersion(nodeModulesDir, packageName); // Find bin path if it exists const binPath = await this.findPackageBin(nodeModulesDir, packageName); // Update registry const now = new Date().toISOString(); const localPath = path.join(nodeModulesDir, packageName); this.registry.packages[packageName] = { ...existing, version: installedVersion, localPath, binPath, updatedAt: now }; await this.saveRegistry(); return { success: true, packageName, version: installedVersion, localPath, binPath }; } catch (error) { return { success: false, packageName, version: version || 'latest', localPath: existing.localPath, error: error instanceof Error ? error.message : String(error) }; } } /** * Uninstalls a package * @param packageName Package name to uninstall */ async uninstallPackage(packageName) { const existing = this.getInstalledPackage(packageName); if (!existing) { return false; } try { const packageDir = path.dirname(path.dirname(existing.localPath)); // Uninstall the package const uninstallCmd = `${this.preferredPackageManager} remove ${packageName}`; await exec(uninstallCmd, { cwd: packageDir }); // Remove from registry delete this.registry.packages[packageName]; await this.saveRegistry(); return true; } catch (error) { console.error(`Failed to uninstall package ${packageName}:`, error); return false; } } /** * Adds a configuration reference to a package * @param packageName Package name * @param configRef Configuration reference */ async addConfigReference(packageName, configRef) { const existing = this.getInstalledPackage(packageName); if (!existing) { throw new Error(`Package not installed: ${packageName}`); } // Check if reference already exists const existingRef = existing.usedByConfigs.find(ref => ref.path === configRef.path && ref.serverName === configRef.serverName); if (!existingRef) { existing.usedByConfigs.push(configRef); await this.saveRegistry(); } } /** * Removes a configuration reference from a package * @param packageName Package name * @param configPath Configuration file path * @param serverName Server name */ async removeConfigReference(packageName, configPath, serverName) { const existing = this.getInstalledPackage(packageName); if (!existing) { return; } existing.usedByConfigs = existing.usedByConfigs.filter(ref => ref.path !== configPath || ref.serverName !== serverName); await this.saveRegistry(); } /** * Gets the install command for a package * @param packageName Package name * @param version Optional specific version */ getInstallCommand(packageName, version) { const versionSuffix = version ? `@${version}` : ''; return `${this.preferredPackageManager} install ${packageName}${versionSuffix}`; } /** * Gets the installed version of a package * @param nodeModulesDir Node modules directory * @param packageName Package name */ async getInstalledVersion(nodeModulesDir, packageName) { try { // Find the package.json in node_modules const packageJsonPath = path.join(nodeModulesDir, packageName, 'package.json'); const packageJson = await readJsonFileOrDefault(packageJsonPath, { version: 'unknown' }); return packageJson.version; } catch (error) { return 'unknown'; } } /** * Finds the binary path for a package * @param nodeModulesDir Node modules directory * @param packageName Package name */ async findPackageBin(nodeModulesDir, packageName) { try { // Check package.json for bin field const packageJsonPath = path.join(nodeModulesDir, packageName, 'package.json'); const packageJson = await readJsonFileOrDefault(packageJsonPath, { bin: undefined }); if (!packageJson.bin) { return undefined; } // If bin is a string, use that if (typeof packageJson.bin === 'string') { return path.join(nodeModulesDir, packageName, packageJson.bin); } // If bin is an object, use the first entry const binEntries = Object.entries(packageJson.bin); if (binEntries.length > 0) { const [, binPath] = binEntries[0]; return path.join(nodeModulesDir, packageName, binPath); } return undefined; } catch (error) { return undefined; } } } /** * Initializes the package manager * @param configService Configuration service */ export async function initializePackageManager(configService) { const packageManager = new PackageManager(configService); await packageManager.loadRegistry(); return packageManager; } //# sourceMappingURL=package-manager.js.map

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/devlimelabs/mcp-env-manager-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server