Skip to main content
Glama

Chicken Business Management MCP Server

by PSYGER02
intelligent-cache-example.ts9.97 kB
/** * Advanced Caching Layer for MCP Client * Intelligent caching with TTL, invalidation, and memory management */ interface CacheEntry { key: string; data: any; expiry: number; lastAccessed: number; accessCount: number; size: number; // Estimated size in bytes } interface CacheConfig { maxSize: number; // Max cache size in MB defaultTTL: number; // Default TTL in ms cleanupInterval: number; // Cleanup interval in ms } export class IntelligentCache { private cache = new Map<string, CacheEntry>(); private config: CacheConfig; private cleanupTimer?: NodeJS.Timeout; private currentSize = 0; // Current cache size in bytes constructor(config: Partial<CacheConfig> = {}) { this.config = { maxSize: config.maxSize || 50, // 50MB default defaultTTL: config.defaultTTL || 5 * 60 * 1000, // 5 minutes cleanupInterval: config.cleanupInterval || 60 * 1000 // 1 minute }; this.startCleanup(); } /** * Generate cache key for MCP operations */ private generateKey(operation: string, params: any): string { // Create deterministic key from operation and parameters const paramString = JSON.stringify(params, Object.keys(params).sort()); return `${operation}:${this.hashString(paramString)}`; } private hashString(str: string): string { // Simple hash function for cache keys let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } return hash.toString(36); } /** * Get cached result for operation */ get(operation: string, params: any): any | null { const key = this.generateKey(operation, params); const entry = this.cache.get(key); if (!entry) { console.log(`💾 Cache MISS: ${operation}`); return null; } if (Date.now() > entry.expiry) { console.log(`⏰ Cache EXPIRED: ${operation}`); this.cache.delete(key); this.currentSize -= entry.size; return null; } // Update access statistics entry.lastAccessed = Date.now(); entry.accessCount++; console.log(`✅ Cache HIT: ${operation} (accessed ${entry.accessCount} times)`); return entry.data; } /** * Store result in cache */ set(operation: string, params: any, data: any, customTTL?: number): void { const key = this.generateKey(operation, params); const ttl = customTTL || this.getTTLForOperation(operation); const size = this.estimateSize(data); // Check if we need to make space this.ensureSpace(size); const entry: CacheEntry = { key, data, expiry: Date.now() + ttl, lastAccessed: Date.now(), accessCount: 1, size }; // Remove old entry if exists const oldEntry = this.cache.get(key); if (oldEntry) { this.currentSize -= oldEntry.size; } this.cache.set(key, entry); this.currentSize += size; console.log(`💾 Cached ${operation} (${this.formatSize(size)}, TTL: ${ttl}ms)`); } /** * Get TTL based on operation type */ private getTTLForOperation(operation: string): number { const operationTTLs = { // Business advice can be cached longer 'get_business_advice': 10 * 60 * 1000, // 10 minutes // Stock info changes frequently 'get_stock_status': 2 * 60 * 1000, // 2 minutes // Forecasts are expensive to compute, cache longer 'forecast_stock': 30 * 60 * 1000, // 30 minutes // Health checks are quick, short cache 'health_check': 30 * 1000, // 30 seconds // Search results can be cached moderately 'search_business_context': 5 * 60 * 1000, // 5 minutes }; return operationTTLs[operation] || this.config.defaultTTL; } /** * Estimate memory size of data */ private estimateSize(data: any): number { const jsonString = JSON.stringify(data); return jsonString.length * 2; // Rough estimate (UTF-16) } /** * Ensure we have space for new entry */ private ensureSpace(neededSize: number): void { const maxSizeBytes = this.config.maxSize * 1024 * 1024; if (this.currentSize + neededSize <= maxSizeBytes) { return; // We have space } console.log(`🧹 Cache full, evicting entries to make space for ${this.formatSize(neededSize)}`); // Get entries sorted by priority (LRU + access count) const entries = Array.from(this.cache.entries()) .map(([key, entry]) => ({ key, entry })) .sort((a, b) => { // Priority = recency * access frequency const priorityA = a.entry.lastAccessed * Math.log(a.entry.accessCount + 1); const priorityB = b.entry.lastAccessed * Math.log(b.entry.accessCount + 1); return priorityA - priorityB; // Lowest priority first }); // Remove entries until we have space for (const { key, entry } of entries) { this.cache.delete(key); this.currentSize -= entry.size; console.log(`🗑️ Evicted ${key} (${this.formatSize(entry.size)})`); if (this.currentSize + neededSize <= maxSizeBytes) { break; } } } /** * Invalidate cache entries by pattern */ invalidate(pattern: string): number { let count = 0; for (const [key, entry] of this.cache.entries()) { if (key.includes(pattern)) { this.cache.delete(key); this.currentSize -= entry.size; count++; } } console.log(`🧹 Invalidated ${count} cache entries matching "${pattern}"`); return count; } /** * Get cache statistics */ getStats() { const entries = Array.from(this.cache.values()); return { entries: entries.length, size: this.formatSize(this.currentSize), hitRate: this.calculateHitRate(), oldestEntry: entries.length > 0 ? new Date(Math.min(...entries.map(e => e.lastAccessed))) : null, mostAccessed: entries.sort((a, b) => b.accessCount - a.accessCount)[0]?.key || null }; } /** * Start automatic cleanup */ private startCleanup(): void { this.cleanupTimer = setInterval(() => { this.cleanup(); }, this.config.cleanupInterval); } /** * Clean expired entries */ private cleanup(): void { const now = Date.now(); let cleanedCount = 0; let cleanedSize = 0; for (const [key, entry] of this.cache.entries()) { if (now > entry.expiry) { this.cache.delete(key); this.currentSize -= entry.size; cleanedCount++; cleanedSize += entry.size; } } if (cleanedCount > 0) { console.log(`🧹 Cleanup: removed ${cleanedCount} expired entries (${this.formatSize(cleanedSize)})`); } } private formatSize(bytes: number): string { const units = ['B', 'KB', 'MB', 'GB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(1)}${units[unitIndex]}`; } private calculateHitRate(): string { // This would track hits vs misses over time return "85%"; // Placeholder } /** * Destroy cache and cleanup */ destroy(): void { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); } this.cache.clear(); this.currentSize = 0; } } /** * Enhanced MCP Client with intelligent caching */ export class CachedMCPClient extends MCPClient { private cache = new IntelligentCache(); async processChickenNote(note: ChickenNote): Promise<MCPResponse> { // Notes are unique, don't cache processing return super.processChickenNote(note); } async getBusinessAdvice(request: BusinessAdviceRequest): Promise<MCPResponse> { // Cache advice requests since they're expensive const cached = this.cache.get('get_business_advice', request); if (cached) { return { success: true, result: cached }; } const result = await super.getBusinessAdvice(request); if (result.success) { this.cache.set('get_business_advice', request, result.result); } return result; } async getForecast(salesHistory: any[]): Promise<MCPResponse> { // Cache forecasts - they're computationally expensive const cacheKey = { historyHash: this.hashArray(salesHistory) }; const cached = this.cache.get('forecast_stock', cacheKey); if (cached) { return { success: true, result: cached }; } const result = await super.getForecast(salesHistory); if (result.success) { this.cache.set('forecast_stock', cacheKey, result.result, 30 * 60 * 1000); // 30 min cache } return result; } async searchBusinessContext(query: string, entityTypes?: string[]): Promise<MCPResponse> { // Cache search results const cacheKey = { query, entityTypes }; const cached = this.cache.get('search_business_context', cacheKey); if (cached) { return { success: true, result: cached }; } const result = await super.searchBusinessContext(query, entityTypes); if (result.success) { this.cache.set('search_business_context', cacheKey, result.result); } return result; } /** * Invalidate cache when stock changes */ async applyToStock(noteId: string, dryRun: boolean = false): Promise<MCPResponse> { const result = await super.applyToStock(noteId, dryRun); if (result.success && !dryRun) { // Invalidate stock-related caches this.cache.invalidate('get_stock'); this.cache.invalidate('forecast_stock'); console.log('🔄 Invalidated stock-related caches after stock update'); } return result; } getCacheStats() { return this.cache.getStats(); } private hashArray(arr: any[]): string { return JSON.stringify(arr).slice(0, 50); // Simple hash } } // Usage export const cachedMCPClient = new CachedMCPClient();

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/PSYGER02/mcpserver'

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