Skip to main content
Glama

hypertool-mcp

manager.tsโ€ข9.04 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"; import { ExtensionRuntimeConfig, HypertoolConfig, ExtensionUserConfig, ValidationResult, DxtManifest, } from "../config/dxt-config.js"; /** * Extension manager coordinates all extension operations */ export class ExtensionManager { private discoveryService: ExtensionDiscoveryService; private readonly validationService: ExtensionValidationService; private readonly configManager: ExtensionConfigManager; private runtimeConfigs: ExtensionRuntimeConfig[] = []; private initialized = false; constructor(configPath?: string, extensionsBaseDir?: string) { this.discoveryService = new ExtensionDiscoveryService(extensionsBaseDir); this.validationService = new ExtensionValidationService(); this.configManager = new ExtensionConfigManager(configPath); } /** * Initialize the extension system */ async initialize(): Promise<void> { 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(): Promise<ExtensionRuntimeConfig[]> { 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(): ExtensionRuntimeConfig[] { return [...this.runtimeConfigs]; } /** * Get enabled runtime configurations only */ getEnabledConfigs(): ExtensionRuntimeConfig[] { return this.runtimeConfigs.filter((config) => config.enabled); } /** * Get runtime configuration for a specific extension */ getExtensionConfig(name: string): ExtensionRuntimeConfig | undefined { return this.runtimeConfigs.find((config) => config.name === name); } /** * Enable an extension */ async enableExtension(name: string): Promise<void> { await this.configManager.enableExtension(name); await this.loadExtensions(); // Reload to update runtime configs } /** * Disable an extension */ async disableExtension(name: string): Promise<void> { await this.configManager.disableExtension(name); await this.loadExtensions(); // Reload to update runtime configs } /** * Update extension user configuration */ async updateExtensionUserConfig( name: string, userConfig: Record<string, any> ): Promise<ValidationResult> { // 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: ExtensionUserConfig = { 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: string ): Promise<{ success: boolean; message: string; name?: string }> { 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 as Error).message}`, }; } } /** * Refresh all extensions (re-unpack if needed) */ async refreshExtensions(): Promise<void> { // 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: string): string | undefined { 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: string): string[] { 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(): Array<{ name: string; enabled: boolean; valid: boolean; version: string; description?: string; errors: string[]; warnings: string[]; }> { 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(): string { return this.configManager.getExtensionsDirectory(); } /** * Set extension directory and refresh */ async setExtensionsDirectory(directory: string): Promise<void> { 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(): boolean { return this.configManager.isAutoDiscoveryEnabled(); } /** * Enable/disable auto-discovery */ async setAutoDiscovery(enabled: boolean): Promise<void> { await this.configManager.setAutoDiscovery(enabled); if (enabled) { await this.loadExtensions(); // Reload if enabling } } /** * Remove an extension */ async removeExtension(name: string): Promise<void> { await this.configManager.removeExtension(name); await this.loadExtensions(); // Reload to update runtime configs } /** * Get extension metadata */ getExtensionMetadata(name: string) { 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: string): any { 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" as const, 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(): Record<string, any> { const configs: Record<string, any> = {}; for (const ext of this.getEnabledConfigs()) { configs[ext.name] = this.createServerConfigForExtension(ext.name); } return configs; } }

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