UNPKG

@toolprint/hypertool-mcp

Version:

HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers

164 lines • 6.35 kB
/** * Migration utilities for transitioning from global to per-app configurations */ import { promises as fs } from "fs"; import { join } from "path"; import { createChildLogger } from "../utils/logging.js"; const logger = createChildLogger({ module: "config-manager/migration" }); export class ConfigMigration { basePath; constructor(basePath) { this.basePath = basePath; } /** * Check if migration is needed */ async needsMigration() { try { // Check if global mcp.json exists const globalConfigPath = join(this.basePath, "mcp.json"); await fs.access(globalConfigPath); // Check if mcp directory exists const mcpDirPath = join(this.basePath, "mcp"); try { await fs.access(mcpDirPath); // If both exist, check if mcp dir has any configs const files = await fs.readdir(mcpDirPath); const hasAppConfigs = files.some((f) => f.endsWith(".json")); return !hasAppConfigs; // Need migration if no app configs exist } catch { // mcp directory doesn't exist, need migration return true; } } catch { // No global config, no migration needed return false; } } /** * Migrate from global mcp.json to per-app configs */ async migrate() { const result = { success: false, migratedApps: [], errors: [], }; try { // Load global config const globalConfigPath = join(this.basePath, "mcp.json"); const globalConfigContent = await fs.readFile(globalConfigPath, "utf-8"); const globalConfig = JSON.parse(globalConfigContent); // Create backup const backupPath = await this.createBackup(globalConfigPath); result.backupPath = backupPath; // Ensure mcp directory exists const mcpDir = join(this.basePath, "mcp"); await fs.mkdir(mcpDir, { recursive: true }); // Check if metadata exists to determine app ownership if (globalConfig._metadata?.sources) { // Group servers by source application const serversByApp = this.groupServersByApp(globalConfig); // Create per-app configs for (const [appId, servers] of Object.entries(serversByApp)) { try { await this.saveAppConfig(appId, servers); result.migratedApps.push(appId); logger.info(`Migrated ${Object.keys(servers).length} servers for ${appId}`); } catch (error) { const errorMsg = `Failed to migrate ${appId}: ${error}`; result.errors.push(errorMsg); logger.error(errorMsg); } } } else { // No metadata - create a default mapping await this.createDefaultMapping(globalConfig); result.migratedApps.push("default"); logger.info("Created default app configuration (no metadata found)"); } // Archive the global config await this.archiveGlobalConfig(globalConfigPath); result.success = result.errors.length === 0; logger.info(`Migration completed. Apps: ${result.migratedApps.join(", ")}`); } catch (error) { result.errors.push(`Migration failed: ${error}`); logger.error("Migration failed", error); } return result; } /** * Group servers by source application */ groupServersByApp(config) { const serversByApp = {}; for (const [serverName, serverConfig] of Object.entries(config.mcpServers)) { const source = config._metadata?.sources?.[serverName]; const appId = source?.app || "unknown"; if (!serversByApp[appId]) { serversByApp[appId] = { mcpServers: {} }; } serversByApp[appId].mcpServers[serverName] = serverConfig; } return serversByApp; } /** * Save app-specific configuration */ async saveAppConfig(appId, config) { const configPath = join(this.basePath, "mcp", `${appId}.json`); const appConfig = { ...config, _metadata: { app: appId, importedAt: new Date().toISOString(), lastModified: new Date().toISOString(), }, }; await fs.writeFile(configPath, JSON.stringify(appConfig, null, 2), "utf-8"); } /** * Create default mapping when no metadata exists */ async createDefaultMapping(config) { // Check common patterns to guess app ownership const appConfigs = {}; for (const [serverName, serverConfig] of Object.entries(config.mcpServers)) { // Try to guess based on common patterns let appId = "default"; // You could add heuristics here to guess app ownership // For now, put everything in a default config if (!appConfigs[appId]) { appConfigs[appId] = { mcpServers: {} }; } appConfigs[appId].mcpServers[serverName] = serverConfig; } // Save all app configs for (const [appId, appConfig] of Object.entries(appConfigs)) { await this.saveAppConfig(appId, appConfig); } } /** * Create backup of global config */ async createBackup(configPath) { const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); const backupPath = `${configPath}.backup-${timestamp}`; await fs.copyFile(configPath, backupPath); return backupPath; } /** * Archive the global config (rename it) */ async archiveGlobalConfig(configPath) { const archivePath = `${configPath}.migrated`; await fs.rename(configPath, archivePath); logger.info(`Archived global config to ${archivePath}`); } } //# sourceMappingURL=migration.js.map