Skip to main content
Glama

hypertool-mcp

hashUtils.tsโ€ข6.6 kB
/** * Hashing utilities for tool identity and change detection */ import { createHash } from "crypto"; // Tool type not used in hash utility functions import { DiscoveredTool, MCPToolDefinition, ToolChangeInfo } from "./types.js"; /** * Hash algorithm to use */ const HASH_ALGORITHM = "sha256"; /** * Tool hash data - only includes fields that affect tool identity/functionality */ interface ToolHashData { name: string; serverName: string; inputSchema: any; outputSchema?: any; annotations?: Record<string, any>; } /** * Tool hash utility class */ export class ToolHashUtils { /** * Calculate tool hash for identity and change detection */ static calculateToolHash( tool: DiscoveredTool | MCPToolDefinition, serverName?: string ): string { const toolDef = "tool" in tool ? tool.tool : tool; const hashData: ToolHashData = { name: (toolDef as any).name, serverName: serverName || (tool as DiscoveredTool).serverName, inputSchema: (toolDef as any).inputSchema, outputSchema: (toolDef as any).outputSchema, annotations: (toolDef as any).annotations, }; return this.hashObject(hashData); } /** * Calculate hash for a list of tools (server tools hash) */ static calculateServerToolsHash(tools: DiscoveredTool[]): string { // 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: DiscoveredTool[], currentTools: DiscoveredTool[] ): ToolChangeInfo[] { const changes: ToolChangeInfo[] = []; // Create lookup maps const previousMap = new Map<string, DiscoveredTool>(); const currentMap = new Map<string, DiscoveredTool>(); // 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: MCPToolDefinition, serverName: string, namespacedName: string ): DiscoveredTool { 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: DiscoveredTool): DiscoveredTool { return { ...tool, lastUpdated: new Date(), toolHash: this.calculateToolHash(tool), }; } /** * Get summary of tool changes */ static summarizeChanges(changes: ToolChangeInfo[]) { 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 */ private static hashObject(obj: any): string { 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 { private hashHistory = new Map<string, string[]>(); // namespacedName -> hash history private maxHistorySize = 10; /** * Add tool hash to history */ addToHistory(tool: DiscoveredTool): void { 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: string): string[] { return this.hashHistory.get(namespacedName) || []; } /** * Check if tool has changed since last known version */ hasChanged(tool: DiscoveredTool): boolean { 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: DiscoveredTool[]): DiscoveredTool[] { return tools.filter((tool) => this.hasChanged(tool)); } /** * Clear history for a tool */ clearHistory(namespacedName: string): void { this.hashHistory.delete(namespacedName); } /** * Clear all history */ clearAllHistory(): void { 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, }; } }

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