@toolprint/hypertool-mcp
Version:
HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers
230 lines (215 loc) • 8.17 kB
JavaScript
/**
* MCP configuration loading - routes between database and file-based approaches
*/
import { APP_TECHNICAL_NAME } from "./appConfig.js";
import { createChildLogger } from "../utils/logging.js";
import { getCompositeDatabaseService } from "../db/compositeDatabaseService.js";
import { discoverMcpConfigFile, loadMcpConfigFile, } from "./mcpConfigFileLoader.js";
const logger = createChildLogger({ module: "config/discovery" });
/**
* Get configuration source from database
*/
async function getConfigSource(sourceType, appId, profileId) {
try {
const dbService = getCompositeDatabaseService();
await dbService.init();
// Find the most appropriate config source
const sources = await dbService.configSources.findAll();
let matchingSource = 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, updatePreference = true, linkedApp, profile) {
// 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() {
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, configSource) {
// 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;
} */
}
//# sourceMappingURL=mcpConfigLoader.js.map