UNPKG

@toolprint/hypertool-mcp

Version:

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

189 lines • 6.56 kB
/** * Centralized feature flag service for consistent flag resolution * Priority: Environment variable > config.json > default (false) */ import { getFeatureFlags } from "./preferenceStore.js"; import { getAllFlagDefinitions, getFlagDefaultValue, } from "./flagRegistry.js"; // Remove logger to avoid circular dependency with logging system // We'll use console for critical errors only const logger = { debug: (message, ...args) => { if (process.env.DEBUG) console.debug(`[FeatureFlagService] ${message}`, ...args); }, info: (message, ...args) => { if (process.env.DEBUG) console.info(`[FeatureFlagService] ${message}`, ...args); }, }; // FeatureFlags interface is now imported from flagRegistry.ts /** * Service for managing and resolving feature flags from multiple sources */ export class FeatureFlagService { static instance; cache = {}; initialized = false; constructor() { } /** * Get singleton instance */ static getInstance() { if (!FeatureFlagService.instance) { FeatureFlagService.instance = new FeatureFlagService(); } return FeatureFlagService.instance; } /** * Initialize feature flags from all sources * Must be called before using any feature flags */ async initialize() { if (this.initialized) { return; } logger.debug("Initializing feature flags"); // 1. Check environment variables (highest priority) const flagDefinitions = getAllFlagDefinitions(); for (const [flagName, flagDef] of Object.entries(flagDefinitions)) { const envValue = process.env[flagDef.envVar]; if (envValue !== undefined) { this.cache[flagName] = ["true", "1", "yes", "on"].includes(envValue.toLowerCase()); logger.debug(`${flagDef.name} enabled from environment: ${this.cache[flagName]}`); } } // 2. Check config.json if not set by environment const hasUndefinedFlags = Object.keys(flagDefinitions).some((flagName) => this.cache[flagName] === undefined); if (hasUndefinedFlags) { try { const configFlags = await getFeatureFlags(); for (const [flagName, flagDef] of Object.entries(flagDefinitions)) { if (this.cache[flagName] === undefined && configFlags?.[flagName] !== undefined) { this.cache[flagName] = configFlags[flagName] === true; logger.debug(`${flagDef.name} enabled from config.json: ${this.cache[flagName]}`); } } } catch (error) { logger.debug("Could not load feature flags from config.json:", error); } } // 3. Apply defaults for any unset flags for (const [flagName, flagDef] of Object.entries(flagDefinitions)) { if (this.cache[flagName] === undefined) { this.cache[flagName] = getFlagDefaultValue(flagName); logger.debug(`${flagDef.name} set to default: ${this.cache[flagName]}`); } } this.initialized = true; logger.info(`Feature flags initialized: ${JSON.stringify(this.cache)}`); } /** * Generic method to check if a flag is enabled * @throws Error if service not initialized */ isFlagEnabled(flagName) { if (!this.initialized) { throw new Error("FeatureFlagService not initialized. Call initialize() first."); } return this.cache[flagName] ?? getFlagDefaultValue(flagName); } /** * Check if MCP Logger is enabled * @throws Error if service not initialized */ isMcpLoggerEnabled() { return this.isFlagEnabled("mcpLoggerEnabled"); } /** * Check if Setup Wizard is enabled * @throws Error if service not initialized */ isSetupWizardEnabled() { return this.isFlagEnabled("setupWizardEnabled"); } /** * Check if DXT is enabled * @throws Error if service not initialized */ isDxtEnabled() { return this.isFlagEnabled("dxtEnabled"); } /** * Check if Config Tools Menu is enabled * @throws Error if service not initialized */ isConfigToolsMenuEnabled() { return this.isFlagEnabled("enableConfigToolsMenu"); } /** * Get all feature flags * @throws Error if service not initialized */ getAllFlags() { if (!this.initialized) { throw new Error("FeatureFlagService not initialized. Call initialize() first."); } return { ...this.cache }; } /** * Reset the service (mainly for testing) */ reset() { this.cache = {}; this.initialized = false; } /** * Force set a feature flag (mainly for testing) * Note: This bypasses normal resolution and should only be used in tests */ forceSet(flagName, value) { this.cache[flagName] = value; this.initialized = true; } } /** * Convenience function to get the singleton instance */ export function getFeatureFlagService() { return FeatureFlagService.getInstance(); } /** * Convenience function to check if MCP Logger is enabled * Ensures the service is initialized before checking */ export async function isMcpLoggerEnabledViaService() { const service = getFeatureFlagService(); await service.initialize(); return service.isMcpLoggerEnabled(); } /** * Convenience function to check if Setup Wizard is enabled * Ensures the service is initialized before checking */ export async function isSetupWizardEnabledViaService() { const service = getFeatureFlagService(); await service.initialize(); return service.isSetupWizardEnabled(); } /** * Convenience function to check if DXT is enabled * Ensures the service is initialized before checking */ export async function isDxtEnabledViaService() { const service = getFeatureFlagService(); await service.initialize(); return service.isDxtEnabled(); } /** * Convenience function to check if Config Tools Menu is enabled * Ensures the service is initialized before checking */ export async function isConfigToolsMenuEnabledViaService() { const service = getFeatureFlagService(); await service.initialize(); return service.isConfigToolsMenuEnabled(); } //# sourceMappingURL=featureFlagService.js.map