UNPKG

@toolprint/hypertool-mcp

Version:

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

264 lines • 10.7 kB
/** * Execution step - Perform the actual setup */ import { output } from "../../utils/output.js"; import { theme } from "../../utils/theme.js"; import { join } from "path"; import { getHomeDir } from "../../utils/paths.js"; import { loadExampleConfig } from "./exampleConfigs.js"; export class ExecutionStep { name = "execution"; configManager; constructor(configManager) { this.configManager = configManager; } async run(state) { output.displaySpaceBuffer(1); output.displayHeader("🚀 Setting up Hypertool MCP..."); output.displaySpaceBuffer(1); const steps = [ { name: "Creating configuration directory", action: () => this.createConfigDirectory(state), }, state.importStrategy === "examples" ? { name: "Installing example configuration", action: () => this.installExampleConfig(state), } : { name: "Importing server configurations", action: () => this.importServerConfigs(state), }, { name: "Setting up per-app configs", action: () => this.setupPerAppConfigs(state), }, { name: "Creating toolsets", action: () => this.createToolsets(state) }, { name: "Linking applications", action: () => this.linkApplications(state), }, state.experimental ? { name: "Enabling experimental features", action: () => this.enableExperimentalFeatures(state), } : null, ].filter((step) => step !== null); // Execute each step for (let i = 0; i < steps.length; i++) { const step = steps[i]; const progress = `[${i + 1}/${steps.length}]`; output.info(`${progress} ${step.name}...`); try { if (!state.dryRun) { await step.action(); } else { output.info(theme.muted(` → Would ${step.name.toLowerCase()}`)); } output.success(`${progress} ${step.name}... ${theme.success("✓")}`); } catch (error) { output.error(`${progress} ${step.name}... ${theme.error("✗")}`); throw error; } } output.displaySpaceBuffer(1); if (state.dryRun) { output.success(theme.success("✅ Dry run complete! No changes were made.")); } else { output.success(theme.success("✅ Setup complete!")); } return state; } async createConfigDirectory(state) { if (state.dryRun) return; await this.configManager.initialize(); } async importServerConfigs(state) { if (state.dryRun) return; // This will be handled by setupPerAppConfigs } async installExampleConfig(state) { if (state.dryRun) return; if (!state.selectedExample) { throw new Error("No example configuration selected"); } const fs = (await import("fs")).promises; // Load the example configuration const exampleConfig = await loadExampleConfig(state.selectedExample.id); // Save it as the global default at ~/.toolprint/hypertool-mcp/mcp.json const globalConfigPath = join(getHomeDir(), ".toolprint", "hypertool-mcp", "mcp.json"); await fs.writeFile(globalConfigPath, JSON.stringify(exampleConfig, null, 2), "utf-8"); // Update main config to note the source const mainConfigPath = join(getHomeDir(), ".toolprint", "hypertool-mcp", "config.json"); let mainConfig = {}; try { const content = await fs.readFile(mainConfigPath, "utf-8"); mainConfig = JSON.parse(content); } catch { mainConfig = { version: "1.0.0" }; } mainConfig.globalDefault = { source: "example", exampleId: state.selectedExample.id, installedAt: new Date().toISOString(), }; await fs.writeFile(mainConfigPath, JSON.stringify(mainConfig, null, 2), "utf-8"); output.info(theme.muted(` → Installed ${state.selectedExample.name} as global default`)); } async setupPerAppConfigs(state) { if (state.dryRun) return; // Skip if using example config as global default if (state.importStrategy === "examples") { return; } // Create per-app configs from perAppSelections for (const [appId, servers] of Object.entries(state.perAppSelections)) { const selectedServers = servers.filter((server) => server.selected); if (selectedServers.length === 0) { continue; // Skip apps with no selected servers } const mcpServers = {}; for (const server of selectedServers) { // Use original server name (no conflict resolution needed for per-app configs) const finalName = server.name; // Read the original server config const app = state.detectedApps.find((a) => a.id === appId); if (app && app.hasExistingConfig) { try { const fs = (await import("fs")).promises; const content = await fs.readFile(app.configPath, "utf-8"); const config = JSON.parse(content); if (config.mcpServers && config.mcpServers[server.name]) { mcpServers[finalName] = config.mcpServers[server.name]; } } catch { // Ignore errors, server config might not exist } } } // Save app config using the file system directly if (Object.keys(mcpServers).length > 0) { await this.savePerAppConfig(appId, { mcpServers }); } } } async createToolsets(state) { if (state.dryRun) return; const fs = (await import("fs")).promises; const configPath = join(getHomeDir(), ".toolprint", "hypertool-mcp", "config.json"); // Load existing config let config = {}; try { const content = await fs.readFile(configPath, "utf-8"); config = JSON.parse(content); } catch { config = { version: "1.0.0" }; } // Initialize toolsets if not exists if (!config.toolsets) { config.toolsets = {}; } // Add new toolsets for (const toolsetDef of state.toolsets) { config.toolsets[toolsetDef.name] = { name: toolsetDef.displayName, description: toolsetDef.description, tools: toolsetDef.tools.map((tool) => ({ name: tool })), metadata: { createdAt: new Date().toISOString(), createdBy: "vibe-setup", }, }; } // Save updated config await fs.writeFile(configPath, JSON.stringify(config, null, 2)); } async linkApplications(state) { if (state.dryRun || state.installationType !== "standard") return; // Link Hypertool to each selected application await this.configManager.linkApplications(state.selectedApps); } async savePerAppConfig(appId, config) { const fs = (await import("fs")).promises; const path = await import("path"); // Get base path from environment or default location const basePath = process.env.HYPERTOOL_CONFIG_PATH || path.join(getHomeDir(), ".toolprint", "hypertool-mcp"); const configPath = path.join(basePath, "mcp", `${appId}.json`); await fs.mkdir(path.join(basePath, "mcp"), { recursive: true }); const appConfig = { ...config, _metadata: { app: appId, importedAt: new Date().toISOString(), lastModified: new Date().toISOString(), }, }; await fs.writeFile(configPath, JSON.stringify(appConfig, null, 2)); // Update main config const mainConfigPath = path.join(basePath, "config.json"); let mainConfig = {}; try { const content = await fs.readFile(mainConfigPath, "utf-8"); mainConfig = JSON.parse(content); } catch { mainConfig = { version: "1.0.0", applications: {} }; } if (!mainConfig.applications) { mainConfig.applications = {}; } mainConfig.applications[appId] = { ...mainConfig.applications[appId], mcpConfig: `mcp/${appId}.json`, lastSync: new Date().toISOString(), }; await fs.writeFile(mainConfigPath, JSON.stringify(mainConfig, null, 2)); } async enableExperimentalFeatures(state) { if (state.dryRun) return; const fs = (await import("fs")).promises; const path = await import("path"); // Update main config.json to enable all experimental features const mainConfigPath = path.join(getHomeDir(), ".toolprint", "hypertool-mcp", "config.json"); let mainConfig = {}; try { const content = await fs.readFile(mainConfigPath, "utf-8"); mainConfig = JSON.parse(content); } catch { mainConfig = { version: "1.0.0" }; } // Enable all experimental feature flags if (!mainConfig.featureFlags) { mainConfig.featureFlags = {}; } // Enable NeDB and any future experimental features mainConfig.featureFlags.nedbEnabled = true; // Add a marker that experimental mode was enabled mainConfig.experimentalMode = { enabled: true, enabledAt: new Date().toISOString(), enabledBy: "setup-wizard", }; await fs.writeFile(mainConfigPath, JSON.stringify(mainConfig, null, 2), "utf-8"); output.info(theme.muted(" → Enabled all experimental features")); output.info(theme.warning(" ⚠️ Experimental features may be unstable or change in future versions")); } } //# sourceMappingURL=execution.js.map