Skip to main content
Glama

hypertool-mcp

migration.tsโ€ข6.12 kB
/** * Migration utilities for transitioning from global to per-app configurations */ import { promises as fs } from "fs"; import { join } from "path"; import { MCPConfig, AppMCPConfig } from "./types/index.js"; import { createChildLogger } from "../utils/logging.js"; const logger = createChildLogger({ module: "config-manager/migration" }); export interface MigrationResult { success: boolean; migratedApps: string[]; errors: string[]; backupPath?: string; } export class ConfigMigration { private basePath: string; constructor(basePath: string) { this.basePath = basePath; } /** * Check if migration is needed */ async needsMigration(): Promise<boolean> { 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(): Promise<MigrationResult> { const result: MigrationResult = { 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: MCPConfig = 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 */ private groupServersByApp(config: MCPConfig): Record<string, MCPConfig> { const serversByApp: Record<string, MCPConfig> = {}; 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 */ private async saveAppConfig(appId: string, config: MCPConfig): Promise<void> { const configPath = join(this.basePath, "mcp", `${appId}.json`); const appConfig: AppMCPConfig = { ...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 */ private async createDefaultMapping(config: MCPConfig): Promise<void> { // Check common patterns to guess app ownership const appConfigs: Record<string, MCPConfig> = {}; 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 */ private async createBackup(configPath: string): Promise<string> { 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) */ private async archiveGlobalConfig(configPath: string): Promise<void> { const archivePath = `${configPath}.migrated`; await fs.rename(configPath, archivePath); logger.info(`Archived global config to ${archivePath}`); } }

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/toolprint/hypertool-mcp'

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