UNPKG

@toolprint/hypertool-mcp

Version:

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

215 lines • 6.49 kB
/** * Hashing utilities for tool identity and change detection */ import { createHash } from "crypto"; /** * Hash algorithm to use */ const HASH_ALGORITHM = "sha256"; /** * Tool hash utility class */ export class ToolHashUtils { /** * Calculate tool hash for identity and change detection */ static calculateToolHash(tool, serverName) { const toolDef = "tool" in tool ? tool.tool : tool; const hashData = { name: toolDef.name, serverName: serverName || tool.serverName, inputSchema: toolDef.inputSchema, outputSchema: toolDef.outputSchema, annotations: toolDef.annotations, }; return this.hashObject(hashData); } /** * Calculate hash for a list of tools (server tools hash) */ static calculateServerToolsHash(tools) { // Sort tools by name for consistent hashing const sortedTools = [...tools].sort((a, b) => a.name.localeCompare(b.name)); const toolHashes = sortedTools.map((tool) => this.calculateToolHash(tool)); return this.hashObject(toolHashes); } /** * Compare tools and detect changes */ static detectToolChanges(previousTools, currentTools) { const changes = []; // Create lookup maps const previousMap = new Map(); const currentMap = new Map(); // Populate maps using namespaced names as keys for (const tool of previousTools) { previousMap.set(tool.namespacedName, tool); } for (const tool of currentTools) { currentMap.set(tool.namespacedName, tool); } // Check for additions and updates for (const [namespacedName, currentTool] of currentMap) { const previousTool = previousMap.get(namespacedName); if (!previousTool) { // New tool changes.push({ tool: currentTool, changeType: "added", currentHash: currentTool.toolHash, }); } else { // Check if tool updated if (previousTool.toolHash !== currentTool.toolHash) { changes.push({ tool: currentTool, changeType: "updated", previousHash: previousTool.toolHash, currentHash: currentTool.toolHash, }); } else { changes.push({ tool: currentTool, changeType: "unchanged", currentHash: currentTool.toolHash, }); } } } // Check for removals for (const [namespacedName, previousTool] of previousMap) { if (!currentMap.has(namespacedName)) { changes.push({ tool: previousTool, changeType: "removed", previousHash: previousTool.toolHash, }); } } return changes; } /** * Create tool with hash from MCP tool definition */ static createHashedTool(toolDef, serverName, namespacedName) { const now = new Date(); const toolHash = this.calculateToolHash(toolDef, serverName); return { name: toolDef.name, serverName, namespacedName, tool: toolDef, discoveredAt: now, lastUpdated: now, serverStatus: "connected", toolHash, }; } /** * Update tool hash after modification */ static updateToolHash(tool) { return { ...tool, lastUpdated: new Date(), toolHash: this.calculateToolHash(tool), }; } /** * Get summary of tool changes */ static summarizeChanges(changes) { const summary = { added: 0, updated: 0, removed: 0, unchanged: 0, total: changes.length, }; for (const change of changes) { summary[change.changeType]++; } return summary; } /** * Hash an object to a hex string */ static hashObject(obj) { const hash = createHash(HASH_ALGORITHM); const serialized = JSON.stringify(obj); hash.update(serialized); return hash.digest("hex"); } } /** * Tool hash manager for tracking tool changes over time */ export class ToolHashManager { hashHistory = new Map(); // namespacedName -> hash history maxHistorySize = 10; /** * Add tool hash to history */ addToHistory(tool) { const key = tool.namespacedName; if (!this.hashHistory.has(key)) { this.hashHistory.set(key, []); } const history = this.hashHistory.get(key); history.push(tool.toolHash); // Limit history size if (history.length > this.maxHistorySize) { history.shift(); } } /** * Get hash history for a tool */ getHistory(namespacedName) { return this.hashHistory.get(namespacedName) || []; } /** * Check if tool has changed since last known version */ hasChanged(tool) { const history = this.getHistory(tool.namespacedName); if (history.length === 0) { return true; // New tool } const lastHash = history[history.length - 1]; return lastHash !== tool.toolHash; } /** * Get tools that have changed */ getChangedTools(tools) { return tools.filter((tool) => this.hasChanged(tool)); } /** * Clear history for a tool */ clearHistory(namespacedName) { this.hashHistory.delete(namespacedName); } /** * Clear all history */ clearAllHistory() { this.hashHistory.clear(); } /** * Get statistics about hash history */ getStats() { return { totalTools: this.hashHistory.size, averageHistorySize: this.hashHistory.size > 0 ? Array.from(this.hashHistory.values()).reduce((sum, hist) => sum + hist.length, 0) / this.hashHistory.size : 0, maxHistorySize: this.maxHistorySize, }; } } //# sourceMappingURL=hashUtils.js.map