Skip to main content
Glama

Formula One MCP Server

cache.ts4.86 kB
// Cache Layer Service - Redis-backed caching with single-flight protection // Provides <500ms latency for cached results export interface CacheOptions { ttl?: number; // Time to live in seconds key?: string; // Custom cache key bypass?: boolean; // Skip cache entirely } export interface CacheStats { hits: number; misses: number; errors: number; hitRate: number; } class CacheLayer { private static cache = new Map<string, { data: any; expires: number }>(); private static pendingCalls = new Map<string, Promise<any>>(); private static stats = { hits: 0, misses: 0, errors: 0 }; /** * Cached function call with single-flight protection */ static async cachedCall<T>( toolName: string, args: Record<string, any>, fn: () => Promise<T>, options: CacheOptions = {} ): Promise<T> { const key = this.generateCacheKey(toolName, args, options.key); const ttl = options.ttl || 300; // 5 minutes default // Check if we have a pending call for this key if (this.pendingCalls.has(key)) { console.log(`🔄 Waiting for pending call: ${key}`); return this.pendingCalls.get(key)!; } // Check cache first if (!options.bypass) { const cached = this.getFromCache(key); if (cached) { this.stats.hits++; console.log(`✅ Cache hit: ${key}`); return cached; } } // Create new promise for this call const promise = this.executeWithCache(key, fn, ttl); this.pendingCalls.set(key, promise); try { const result = await promise; return result; } finally { // Clean up pending call this.pendingCalls.delete(key); } } /** * Generate cache key from tool name and arguments */ private static generateCacheKey( toolName: string, args: Record<string, any>, customKey?: string ): string { if (customKey) return customKey; const argsHash = JSON.stringify(args); return `f1:${toolName}:${Buffer.from(argsHash).toString('base64')}`; } /** * Get data from cache */ private static getFromCache(key: string): any | null { const cached = this.cache.get(key); if (!cached) { this.stats.misses++; return null; } // Check if expired if (Date.now() > cached.expires) { this.cache.delete(key); this.stats.misses++; return null; } return cached.data; } /** * Execute function and cache result */ private static async executeWithCache<T>( key: string, fn: () => Promise<T>, ttl: number ): Promise<T> { try { const startTime = Date.now(); const result = await fn(); const duration = Date.now() - startTime; // Cache the result this.cache.set(key, { data: result, expires: Date.now() + (ttl * 1000) }); console.log(`💾 Cached result for ${key} (${duration}ms)`); return result; } catch (error) { this.stats.errors++; console.error(`❌ Cache execution failed for ${key}:`, error); throw error; } } /** * Clear cache for specific tool */ static clearToolCache(toolName: string): void { const keysToDelete: string[] = []; for (const key of this.cache.keys()) { if (key.startsWith(`f1:${toolName}:`)) { keysToDelete.push(key); } } keysToDelete.forEach(key => this.cache.delete(key)); console.log(`🗑️ Cleared ${keysToDelete.length} cache entries for ${toolName}`); } /** * Clear all cache */ static clearAllCache(): void { const size = this.cache.size; this.cache.clear(); console.log(`🗑️ Cleared all cache (${size} entries)`); } /** * Get cache statistics */ static getStats(): CacheStats { const total = this.stats.hits + this.stats.misses; const hitRate = total > 0 ? (this.stats.hits / total) * 100 : 0; return { ...this.stats, hitRate: Math.round(hitRate * 100) / 100 }; } /** * Get cache size */ static getCacheSize(): number { return this.cache.size; } /** * Get pending calls count */ static getPendingCallsCount(): number { return this.pendingCalls.size; } /** * Check if key is cached */ static isCached(key: string): boolean { const cached = this.cache.get(key); if (!cached) return false; return Date.now() <= cached.expires; } /** * Get cache keys for debugging */ static getCacheKeys(): string[] { return Array.from(this.cache.keys()); } /** * Set cache TTL for specific key */ static setCacheTTL(key: string, ttl: number): void { const cached = this.cache.get(key); if (cached) { cached.expires = Date.now() + (ttl * 1000); console.log(`⏰ Updated TTL for ${key} to ${ttl}s`); } } } export { CacheLayer };

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/notsedano/f1-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server