UNPKG

@toolprint/hypertool-mcp

Version:

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

200 lines • 8.69 kB
/** * Server synchronization module * Reconciles configurations from multiple sources with the internal database */ import * as crypto from "crypto"; import { createChildLogger } from "../utils/logging.js"; const logger = createChildLogger({ module: "serverSync" }); export class ServerSyncManager { dbService; constructor(dbService) { this.dbService = dbService; } /** * Calculate checksum for a server configuration */ calculateChecksum(config) { const configString = JSON.stringify(config, Object.keys(config).sort()); return crypto.createHash("sha256").update(configString).digest("hex"); } /** * Sync server configurations from a specific source with the database */ async syncServersFromSource(mcpServers, source) { const stats = { added: 0, updated: 0, deleted: 0 }; // If NeDB is disabled, skip database synchronization if (!false) { logger.debug("NeDB disabled - skipping server synchronization to database"); // Return zero stats as no database operations were performed return stats; } try { logger.debug(`Starting server synchronization from source: ${source.type}/${source.id}`); // Get all existing servers from this source const allServers = await this.dbService.servers.findAll(); const sourceServers = allServers.filter((s) => s.sourceId === source.id); const sourceServerMap = new Map(sourceServers.map((server) => [server.name, server])); // Track which servers we've seen in the config const seenServerNames = new Set(); // Process each server from the config for (const [serverName, serverConfig] of Object.entries(mcpServers)) { seenServerNames.add(serverName); const checksum = this.calculateChecksum(serverConfig); // Check if server exists (from any source) const existingServer = await this.dbService.servers.findByName(serverName); if (!existingServer) { // Add new server await this.dbService.servers.add({ name: serverName, type: serverConfig.type, config: serverConfig, lastModified: Date.now(), checksum, sourceId: source.id, }); stats.added++; logger.debug(`Added new server: ${serverName}`); } else if (existingServer.sourceId === source.id) { // Update if owned by this source and changed if (existingServer.checksum !== checksum) { await this.dbService.servers.update({ ...existingServer, config: serverConfig, type: serverConfig.type, lastModified: Date.now(), checksum, }); stats.updated++; logger.debug(`Updated server: ${serverName}`); } } else { // Server exists but owned by different source // Check priority to determine if we should update const existingSource = await this.dbService.configSources.findById(existingServer.sourceId); if (existingSource && source.priority > existingSource.priority) { await this.dbService.servers.update({ ...existingServer, config: serverConfig, type: serverConfig.type, lastModified: Date.now(), checksum, sourceId: source.id, }); stats.updated++; logger.debug(`Updated server: ${serverName} (took ownership from lower priority source)`); } } } // Delete servers from this source that are no longer in the config for (const sourceServer of sourceServers) { if (!seenServerNames.has(sourceServer.name)) { await this.dbService.servers.delete(sourceServer.id); stats.deleted++; logger.debug(`Deleted server: ${sourceServer.name}`); // Clean up group references await this.removeServerFromGroups(sourceServer.id); } } logger.info(`Server sync completed for source ${source.type}/${source.id}: ${stats.added} added, ${stats.updated} updated, ${stats.deleted} deleted`); return stats; } catch (error) { logger.error("Server synchronization failed:", error); throw error; } } /** * Legacy sync method - creates/uses global source */ async syncServers(mcpServers) { // If NeDB is disabled, skip database synchronization if (!false) { logger.debug("NeDB disabled - skipping server synchronization to database"); return { added: 0, updated: 0, deleted: 0 }; } // Get or create global config source let globalSource = await this.dbService.configSources.findByPath("global"); if (!globalSource) { globalSource = await this.dbService.configSources.add({ type: "global", path: "global", priority: 100, lastSynced: Date.now(), }); } return this.syncServersFromSource(mcpServers, globalSource); } /** * Remove a server from all groups that reference it */ async removeServerFromGroups(serverId) { const groups = await this.dbService.groups.findAll(); for (const group of groups) { if (group.serverIds.includes(serverId)) { const updatedServerIds = group.serverIds.filter((id) => id !== serverId); await this.dbService.groups.update({ ...group, serverIds: updatedServerIds, }); logger.debug(`Removed server ${serverId} from group ${group.name}`); } } } /** * Get server configurations for a specific group */ async getServersForGroup(groupName) { // If NeDB is disabled, groups are not available if (!false) { logger.warn("NeDB disabled - server groups are not available"); throw new Error("Server groups require NeDB to be enabled (set HYPERTOOL_NEDB_ENABLED=true)"); } const group = await this.dbService.groups.findByName(groupName); if (!group) { throw new Error(`Group "${groupName}" not found`); } return this.dbService.groups.findServersInGroup(group.id); } /** * Get all server configurations from the database */ async getAllServers() { // If NeDB is disabled, return empty array if (!false) { logger.debug("NeDB disabled - returning empty server list"); return []; } return this.dbService.servers.findAll(); } /** * Sync all configuration sources */ async syncAllSources() { const total = { added: 0, updated: 0, deleted: 0 }; const sources = []; // If NeDB is disabled, skip synchronization if (!false) { logger.debug("NeDB disabled - skipping source synchronization"); return { total, sources }; } try { // Get all config sources const configSources = await this.dbService.configSources.findAll(); logger.info(`Syncing ${configSources.length} configuration sources`); // Sync each source for (const source of configSources) { // Skip syncing for now - would need to implement file reading // In the future, this could read from external sources if needed logger.debug(`Would sync source: ${source.type}/${source.id} from ${source.path}`); } return { total, sources }; } catch (error) { logger.error("Failed to sync all sources:", error); throw error; } } } //# sourceMappingURL=serverSync.js.map