UNPKG

@toolprint/hypertool-mcp

Version:

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

261 lines • 8.74 kB
/** * Extension Manager * Orchestrates extension discovery, configuration, validation, and runtime management */ import { ExtensionDiscoveryService } from "./discovery.js"; import { ExtensionValidationService } from "./validation.js"; import { ExtensionConfigManager } from "../config/extensionConfig.js"; /** * Extension manager coordinates all extension operations */ export class ExtensionManager { discoveryService; validationService; configManager; runtimeConfigs = []; initialized = false; constructor(configPath, extensionsBaseDir) { this.discoveryService = new ExtensionDiscoveryService(extensionsBaseDir); this.validationService = new ExtensionValidationService(); this.configManager = new ExtensionConfigManager(configPath); } /** * Initialize the extension system */ async initialize() { if (this.initialized) { return; } await this.discoveryService.initialize(); await this.configManager.initialize(); await this.loadExtensions(); this.initialized = true; } /** * Load and validate all extensions */ async loadExtensions() { const config = await this.configManager.load(); this.runtimeConfigs = await this.discoveryService.loadExtensionConfigs(config); // Log validation results this.runtimeConfigs.forEach((ext) => { const summary = this.validationService.getValidationSummary(ext.name, ext.validationResult); if (ext.validationResult.isValid) { if (ext.validationResult.warnings.length > 0) { console.warn(`[WARN] ${summary}`); } else { console.log(`[INFO] ${summary}`); } } else { console.warn(`[WARN] Extension '${ext.name}' disabled: ${ext.validationResult.errors.join(", ")}`); } }); return this.runtimeConfigs; } /** * Get all runtime configurations */ getRuntimeConfigs() { return [...this.runtimeConfigs]; } /** * Get enabled runtime configurations only */ getEnabledConfigs() { return this.runtimeConfigs.filter((config) => config.enabled); } /** * Get runtime configuration for a specific extension */ getExtensionConfig(name) { return this.runtimeConfigs.find((config) => config.name === name); } /** * Enable an extension */ async enableExtension(name) { await this.configManager.enableExtension(name); await this.loadExtensions(); // Reload to update runtime configs } /** * Disable an extension */ async disableExtension(name) { await this.configManager.disableExtension(name); await this.loadExtensions(); // Reload to update runtime configs } /** * Update extension user configuration */ async updateExtensionUserConfig(name, userConfig) { // Get current extension to validate against its manifest const currentExt = this.getExtensionConfig(name); if (!currentExt) { throw new Error(`Extension '${name}' not found`); } // Validate the new configuration const tempUserSettings = { isEnabled: true, userConfig, }; const validationResult = this.validationService.validateExtensionConfig(currentExt.manifest, tempUserSettings); if (validationResult.isValid) { // Save the configuration await this.configManager.updateExtensionUserConfig(name, userConfig); await this.loadExtensions(); // Reload to update runtime configs } return validationResult; } /** * Install a new extension from DXT file */ async installExtension(dxtPath) { try { const metadata = await this.discoveryService.unpackExtension(dxtPath); // Reload extensions to include the new one await this.loadExtensions(); return { success: true, message: `Extension '${metadata.name}' installed successfully`, name: metadata.name, }; } catch (error) { return { success: false, message: `Failed to install extension: ${error.message}`, }; } } /** * Refresh all extensions (re-unpack if needed) */ async refreshExtensions() { // Force refresh by clearing metadata and reloading const dxtFiles = await this.discoveryService.discoverExtensions(); for (const dxtPath of dxtFiles) { const needsUpdate = await this.discoveryService.needsUnpacking(dxtPath); if (needsUpdate) { await this.discoveryService.unpackExtension(dxtPath); } } await this.loadExtensions(); } /** * Get validation report for an extension */ getValidationReport(name) { const ext = this.getExtensionConfig(name); if (!ext) { return undefined; } const userSettings = this.configManager.getExtensionSettings(name); return this.validationService.createValidationReport(name, ext.manifest, userSettings, ext.validationResult); } /** * Get configuration suggestions for fixing validation errors */ getConfigSuggestions(name) { const ext = this.getExtensionConfig(name); if (!ext || ext.validationResult.isValid) { return []; } return this.validationService.suggestFixes(name, ext.manifest, ext.validationResult); } /** * List all extensions with their status */ listExtensions() { return this.runtimeConfigs.map((ext) => ({ name: ext.name, enabled: ext.enabled, valid: ext.validationResult.isValid, version: ext.manifest.version, description: ext.manifest.description, errors: ext.validationResult.errors, warnings: ext.validationResult.warnings, })); } /** * Get extension directory path */ getExtensionsDirectory() { return this.configManager.getExtensionsDirectory(); } /** * Set extension directory and refresh */ async setExtensionsDirectory(directory) { await this.configManager.setExtensionsDirectory(directory); // Update discovery service and reload this.discoveryService = new ExtensionDiscoveryService(directory); await this.discoveryService.initialize(); await this.loadExtensions(); } /** * Check if auto-discovery is enabled */ isAutoDiscoveryEnabled() { return this.configManager.isAutoDiscoveryEnabled(); } /** * Enable/disable auto-discovery */ async setAutoDiscovery(enabled) { await this.configManager.setAutoDiscovery(enabled); if (enabled) { await this.loadExtensions(); // Reload if enabling } } /** * Remove an extension */ async removeExtension(name) { await this.configManager.removeExtension(name); await this.loadExtensions(); // Reload to update runtime configs } /** * Get extension metadata */ getExtensionMetadata(name) { return this.discoveryService.getExtensionMetadata(name); } /** * Get all extension metadata */ getAllMetadata() { return this.discoveryService.getAllMetadata(); } /** * Create a server configuration that can be used with the connection factory * This replaces the old DXT server type approach */ createServerConfigForExtension(name) { const ext = this.getExtensionConfig(name); if (!ext || !ext.enabled) { throw new Error(`Extension '${name}' not found or not enabled`); } // Return a stdio server config that uses the extension's server configuration return { type: "stdio", command: ext.serverConfig.command, args: ext.serverConfig.args, env: ext.serverConfig.env, cwd: ext.serverConfig.cwd, }; } /** * Get all enabled extensions as server configs for connection factory */ getEnabledExtensionsAsServerConfigs() { const configs = {}; for (const ext of this.getEnabledConfigs()) { configs[ext.name] = this.createServerConfigForExtension(ext.name); } return configs; } } //# sourceMappingURL=manager.js.map