UNPKG

@toolprint/hypertool-mcp

Version:

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

250 lines • 8.22 kB
/** * Extension Configuration Management * Handles loading, saving, and validation of extension configurations */ import { readFile, writeFile, mkdir } from "fs/promises"; import { join, dirname } from "path"; import { homedir } from "os"; import { existsSync } from "fs"; import { isDxtEnabledViaService } from "./featureFlagService.js"; /** * Default extension configuration */ const DEFAULT_EXTENSION_CONFIG = { directory: join(homedir(), ".toolprint", "hypertool-mcp", "extensions"), autoDiscovery: true, settings: {}, }; /** * Extension configuration manager */ export class ExtensionConfigManager { configPath; config = {}; constructor(configPath) { const base = configPath || join(homedir(), ".toolprint", "hypertool-mcp", "config.json"); this.configPath = base; } /** * Load configuration from file */ async load() { // If DXT is disabled, return empty config and skip file operations if (!(await isDxtEnabledViaService())) { this.config = { extensions: { directory: "", autoDiscovery: false, settings: {} }, }; return this.config; } try { if (existsSync(this.configPath)) { const content = await readFile(this.configPath, "utf-8"); this.config = JSON.parse(content); } else { // Initialize with defaults this.config = { extensions: DEFAULT_EXTENSION_CONFIG, }; } // Ensure extensions config exists if (!this.config.extensions) { this.config.extensions = DEFAULT_EXTENSION_CONFIG; } // Ensure required fields exist if (!this.config.extensions.settings) { this.config.extensions.settings = {}; } if (this.config.extensions.autoDiscovery === undefined) { this.config.extensions.autoDiscovery = true; } if (!this.config.extensions.directory) { this.config.extensions.directory = DEFAULT_EXTENSION_CONFIG.directory; } return this.config; } catch (error) { console.error(`Failed to load extension config: ${error.message}`); return this.getDefaultConfig(); } } /** * Save configuration to file */ async save(config) { const configToSave = config || this.config; try { // Ensure directory exists await mkdir(dirname(this.configPath), { recursive: true }); // Save with pretty formatting await writeFile(this.configPath, JSON.stringify(configToSave, null, 2)); this.config = configToSave; } catch (error) { throw new Error(`Failed to save extension config: ${error.message}`); } } /** * Get current configuration */ getConfig() { return { ...this.config }; } /** * Get extension settings for a specific extension */ getExtensionSettings(extensionName) { return this.config.extensions?.settings?.[extensionName]; } /** * Set extension settings for a specific extension */ async setExtensionSettings(extensionName, settings) { if (!this.config.extensions) { this.config.extensions = DEFAULT_EXTENSION_CONFIG; } if (!this.config.extensions.settings) { this.config.extensions.settings = {}; } this.config.extensions.settings[extensionName] = settings; await this.save(); } /** * Enable an extension */ async enableExtension(extensionName) { const currentSettings = this.getExtensionSettings(extensionName); await this.setExtensionSettings(extensionName, { ...currentSettings, isEnabled: true, }); } /** * Disable an extension */ async disableExtension(extensionName) { const currentSettings = this.getExtensionSettings(extensionName); await this.setExtensionSettings(extensionName, { ...currentSettings, isEnabled: false, }); } /** * Update user config for an extension */ async updateExtensionUserConfig(extensionName, userConfig) { const currentSettings = this.getExtensionSettings(extensionName) || { isEnabled: true, }; await this.setExtensionSettings(extensionName, { ...currentSettings, userConfig, }); } /** * Remove extension settings */ async removeExtension(extensionName) { if (this.config.extensions?.settings) { delete this.config.extensions.settings[extensionName]; await this.save(); } } /** * Get all extension names */ getExtensionNames() { return Object.keys(this.config.extensions?.settings || {}); } /** * Check if auto-discovery is enabled */ isAutoDiscoveryEnabled() { return this.config.extensions?.autoDiscovery ?? true; } /** * Set auto-discovery setting */ async setAutoDiscovery(enabled) { if (!this.config.extensions) { this.config.extensions = DEFAULT_EXTENSION_CONFIG; } this.config.extensions.autoDiscovery = enabled; await this.save(); } /** * Get extensions directory */ getExtensionsDirectory() { return (this.config.extensions?.directory || DEFAULT_EXTENSION_CONFIG.directory); } /** * Set extensions directory */ async setExtensionsDirectory(directory) { if (!this.config.extensions) { this.config.extensions = DEFAULT_EXTENSION_CONFIG; } this.config.extensions.directory = directory; await this.save(); } /** * Get default configuration */ getDefaultConfig() { return { extensions: DEFAULT_EXTENSION_CONFIG, }; } /** * Validate configuration structure */ validateConfig(config) { const errors = []; if (config.extensions) { const ext = config.extensions; if (ext.directory && typeof ext.directory !== "string") { errors.push("extensions.directory must be a string"); } if (ext.autoDiscovery !== undefined && typeof ext.autoDiscovery !== "boolean") { errors.push("extensions.autoDiscovery must be a boolean"); } if (ext.settings) { if (typeof ext.settings !== "object" || Array.isArray(ext.settings)) { errors.push("extensions.settings must be an object"); } else { for (const [name, settings] of Object.entries(ext.settings)) { if (typeof settings !== "object" || Array.isArray(settings)) { errors.push(`extensions.settings.${name} must be an object`); continue; } const s = settings; if (s.isEnabled !== undefined && typeof s.isEnabled !== "boolean") { errors.push(`extensions.settings.${name}.isEnabled must be a boolean`); } if (s.userConfig !== undefined && (typeof s.userConfig !== "object" || Array.isArray(s.userConfig))) { errors.push(`extensions.settings.${name}.userConfig must be an object`); } } } } } return { isValid: errors.length === 0, errors, }; } /** * Initialize configuration file if it doesn't exist */ async initialize() { if (!existsSync(this.configPath)) { await this.save(this.getDefaultConfig()); } } } //# sourceMappingURL=extensionConfig.js.map