@toolprint/hypertool-mcp
Version:
HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers
200 lines • 8.69 kB
JavaScript
/**
* 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