Skip to main content
Glama

hypertool-mcp

serverSync.tsโ€ข8.61 kB
/** * Server synchronization module * Reconciles configurations from multiple sources with the internal database */ import * as crypto from "crypto"; import { ServerConfig } from "../types/config.js"; import { IDatabaseService, IConfigSource } from "../db/interfaces.js"; import { ServerConfigRecord } from "../db/interfaces.js"; import { createChildLogger } from "../utils/logging.js"; const logger = createChildLogger({ module: "serverSync" }); export class ServerSyncManager { private dbService: IDatabaseService; constructor(dbService: IDatabaseService) { this.dbService = dbService; } /** * Calculate checksum for a server configuration */ private calculateChecksum(config: ServerConfig): string { 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: Record<string, ServerConfig>, source: IConfigSource ): Promise<{ added: number; updated: number; deleted: number; }> { 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<string>(); // 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: Record<string, ServerConfig>): Promise<{ added: number; updated: number; deleted: number; }> { // 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 */ private async removeServerFromGroups(serverId: string): Promise<void> { 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: string): Promise<ServerConfigRecord[]> { // 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(): Promise<ServerConfigRecord[]> { // 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(): Promise<{ total: { added: number; updated: number; deleted: number }; sources: Array<{ sourceId: string; type: string; stats: { added: number; updated: number; deleted: number }; }>; }> { const total = { added: 0, updated: 0, deleted: 0 }; const sources: Array<{ sourceId: string; type: string; stats: { added: number; updated: number; deleted: number }; }> = []; // 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; } } }

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