Skip to main content
Glama

hypertool-mcp

mcpConfigLoader.tsโ€ข8.27 kB
/** * MCP configuration loading - routes between database and file-based approaches */ import * as path from "path"; import { loadUserPreferences } from "./preferenceStore.js"; import { APP_TECHNICAL_NAME, BRAND_NAME } from "./appConfig.js"; import { createChildLogger } from "../utils/logging.js"; import { getCompositeDatabaseService } from "../db/compositeDatabaseService.js"; import { ServerConfig } from "../types/config.js"; import { IConfigSource } from "../db/interfaces.js"; import { getFeatureFlagService } from "./featureFlagService.js"; import { discoverMcpConfigFile, loadMcpConfigFile, } from "./mcpConfigFileLoader.js"; const logger = createChildLogger({ module: "config/discovery" }); /** * Get configuration source from database */ async function getConfigSource( sourceType: "global" | "app" | "profile", appId?: string, profileId?: string ): Promise<IConfigSource | null> { try { const dbService = getCompositeDatabaseService(); await dbService.init(); // Find the most appropriate config source const sources = await dbService.configSources.findAll(); let matchingSource: IConfigSource | null = null; let highestPriority = -1; for (const source of sources) { if (source.type === sourceType) { if (sourceType === "global" && source.priority > highestPriority) { matchingSource = source; highestPriority = source.priority; } else if ( sourceType === "app" && source.appId === appId && source.priority > highestPriority ) { matchingSource = source; highestPriority = source.priority; } else if ( sourceType === "profile" && source.appId === appId && source.profileId === profileId && source.priority > highestPriority ) { matchingSource = source; highestPriority = source.priority; } } } return matchingSource; } catch (error) { logger.debug("Failed to get config source:", error); return null; } } /** * Discover MCP configuration - routes to database or file-based based on feature flag * * @param cliConfigPath - Path provided via --mcp-config flag (for backward compatibility) * @param updatePreference - Whether to update user preference when using CLI path * @param linkedApp - Application ID to load config for * @param profile - Profile ID for workspace/project config * @returns Object with config and source information */ export async function discoverMcpConfig( cliConfigPath?: string, updatePreference: boolean = true, linkedApp?: string, profile?: string ): Promise<{ configPath: string | null; source: "cli" | "app" | "preference" | "discovered" | "none"; errorMessage?: string; configSource?: IConfigSource; }> { // Use file-based configuration discovery logger.debug("Using file-based configuration"); return discoverMcpConfigFile( cliConfigPath, updatePreference, linkedApp, profile ); // Database configuration discovery (legacy code - removed as unreachable) // This section was unreachable after file-based return above /* try { const dbService = getCompositeDatabaseService(); await dbService.init(); // 1. Check for linked app with profile (highest priority) if (linkedApp && profile) { const source = await getConfigSource("profile", linkedApp, profile); if (source) { return { configPath: source.path ?? "", // Keep for backward compatibility source: "app", configSource: source ?? undefined, }; } } // 2. Check for linked app without profile if (linkedApp) { const source = await getConfigSource("app", linkedApp); if (source) { return { configPath: source.path ?? "", // Keep for backward compatibility source: "app", configSource: source ?? undefined, }; } else { return { configPath: null, source: "none", errorMessage: `Could not find configuration for app '${linkedApp}'${profile ? ` with profile '${profile}'` : ""}`, }; } } // 3. Check user preference (from database) try { const preferences = await loadUserPreferences(); if (preferences.mcpConfigPath) { // Find config source by path const sources = await dbService.configSources.findByPath( preferences.mcpConfigPath! ); if (sources) { return { configPath: sources.path ?? "", source: "preference", configSource: sources ?? undefined, }; } } } catch (error) { logger.warn("Warning: Could not load user preferences:", error); } // 4. Use global config source const globalSource = await getConfigSource("global"); if (globalSource) { return { configPath: globalSource.path ?? "", source: "discovered", configSource: globalSource ?? undefined, }; } // 5. No config found return { configPath: null, source: "none", errorMessage: generateNoConfigFoundMessage(), }; } catch (error) { logger.error("Failed to discover MCP config:", error); return { configPath: null, source: "none", errorMessage: `Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`, }; } */ } /** * Generate helpful error message when no config is found */ function generateNoConfigFoundMessage(): string { return `No MCP configuration found in database. To get started: 1. Run '${APP_TECHNICAL_NAME} config backup' to import configurations from applications 2. Run '${APP_TECHNICAL_NAME} config link' to link applications to HyperTool 3. Run '${APP_TECHNICAL_NAME} --help' for more configuration options`; } /** * Load and validate MCP configuration - routes to database or file-based based on feature flag */ export async function loadMcpConfig( configPath: string, configSource?: IConfigSource ): Promise<any> { // Guard against empty or invalid paths if (!configPath || configPath.trim() === "") { logger.debug( "Empty config path provided - returning minimal configuration" ); return { mcpServers: {}, _metadata: { source: "minimal", path: "", loadedAt: new Date().toISOString(), }, }; } // Use file-based configuration loading logger.debug("Loading from file"); return loadMcpConfigFile(configPath, configSource); // Database configuration loading (legacy code - removed as unreachable) // This section was unreachable after file-based return above /* try { const dbService = getCompositeDatabaseService(); await dbService.init(); // If we have a config source, load servers associated with it if (configSource) { const servers = await dbService.servers.findAll(); const sourceServers = servers.filter( (s) => s.sourceId === configSource!.id ); // Convert to MCP config format const mcpServers: Record<string, ServerConfig> = {}; for (const server of sourceServers) { mcpServers[server.name] = server.config; } const config = { mcpServers, _metadata: { source: configSource?.type, sourceId: configSource?.id, path: configSource?.path, }, }; // Basic validation - ensure mcpServers field exists if (!config.mcpServers || typeof config.mcpServers !== "object") { throw new Error("Invalid MCP config: missing 'mcpServers' field"); } return config; } // Fallback: load all servers if no specific source const servers = await dbService.servers.findAll(); const mcpServers: Record<string, ServerConfig> = {}; for (const server of servers) { mcpServers[server.name] = server.config; } const config = { mcpServers, _metadata: { source: "database", loadedFrom: "all-servers", }, }; return config; } catch (error) { logger.error("Failed to load MCP config from database:", error); throw error; } */ }

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