Skip to main content
Glama
sascodiego

MCP Vibe Coding Knowledge Graph

by sascodiego
MultiLayerCache.js41.2 kB
/** * CONTEXT: Multi-layer caching system for MCP server performance optimization * REASON: Reduce redundant operations and improve response times across all MCP components * CHANGE: Comprehensive caching with memory, disk, and distributed layers * PREVENTION: Performance degradation, resource waste, redundant computations */ import { EventEmitter } from 'events'; import fs from 'fs/promises'; import path from 'path'; import crypto from 'crypto'; import { logger } from '../utils/logger.js'; export class MultiLayerCache extends EventEmitter { constructor(config = {}) { super(); this.config = { // Memory cache configuration memory: { enabled: true, maxSize: config.memory?.maxSize || 100 * 1024 * 1024, // 100MB maxItems: config.memory?.maxItems || 10000, ttl: config.memory?.ttl || 30 * 60 * 1000, // 30 minutes checkPeriod: config.memory?.checkPeriod || 5 * 60 * 1000, // 5 minutes ...config.memory }, // Disk cache configuration disk: { enabled: config.disk?.enabled !== false, cacheDir: config.disk?.cacheDir || './.kg-context/cache', maxSize: config.disk?.maxSize || 1024 * 1024 * 1024, // 1GB maxFiles: config.disk?.maxFiles || 50000, ttl: config.disk?.ttl || 24 * 60 * 60 * 1000, // 24 hours compression: config.disk?.compression !== false, ...config.disk }, // Distributed cache configuration (future extension) distributed: { enabled: config.distributed?.enabled || false, provider: config.distributed?.provider || 'redis', connectionString: config.distributed?.connectionString, keyPrefix: config.distributed?.keyPrefix || 'mcp:cache:', ttl: config.distributed?.ttl || 60 * 60 * 1000, // 1 hour ...config.distributed }, // Cache strategies strategies: { defaultTtl: config.strategies?.defaultTtl || 30 * 60 * 1000, lruEviction: config.strategies?.lruEviction !== false, writeThrough: config.strategies?.writeThrough !== false, refreshAhead: config.strategies?.refreshAhead || false, ...config.strategies }, // Performance settings performance: { compressionThreshold: config.performance?.compressionThreshold || 1024, asyncWrites: config.performance?.asyncWrites !== false, batchOperations: config.performance?.batchOperations !== false, prefetchEnabled: config.performance?.prefetchEnabled || false, ...config.performance } }; // Cache layers this.memoryCache = new Map(); this.memoryMeta = new Map(); // Metadata for memory cache entries this.diskCache = null; this.distributedCache = null; // Cache statistics this.stats = { hits: { memory: 0, disk: 0, distributed: 0 }, misses: { memory: 0, disk: 0, distributed: 0 }, sets: { memory: 0, disk: 0, distributed: 0 }, deletes: { memory: 0, disk: 0, distributed: 0 }, evictions: { memory: 0, disk: 0, distributed: 0 }, errors: { memory: 0, disk: 0, distributed: 0 }, totalSize: { memory: 0, disk: 0, distributed: 0 }, totalItems: { memory: 0, disk: 0, distributed: 0 }, avgAccessTime: { memory: 0, disk: 0, distributed: 0 }, hitRatio: { memory: 0, disk: 0, distributed: 0, overall: 0 } }; // Cache dependency tracking this.dependencies = new Map(); // key -> Set of dependent keys this.dependents = new Map(); // key -> Set of keys this depends on // Cleanup intervals this.cleanupIntervals = []; // Performance monitoring this.performanceMetrics = { operationTimes: new Map(), hitRatios: [], memoryUsage: [], diskUsage: [] }; this.initialized = false; } /** * Initialize the multi-layer cache system */ async initialize() { if (this.initialized) return; try { logger.info('Initializing multi-layer cache system', { memoryEnabled: this.config.memory.enabled, diskEnabled: this.config.disk.enabled, distributedEnabled: this.config.distributed.enabled }); // Initialize disk cache if (this.config.disk.enabled) { await this.initializeDiskCache(); } // Initialize distributed cache if (this.config.distributed.enabled) { await this.initializeDistributedCache(); } // Start maintenance tasks this.startMaintenanceTasks(); // Start performance monitoring this.startPerformanceMonitoring(); this.initialized = true; this.emit('initialized'); logger.info('Multi-layer cache system initialized successfully'); } catch (error) { logger.error('Failed to initialize cache system:', error); throw error; } } /** * Initialize disk cache */ async initializeDiskCache() { try { const cacheDir = this.config.disk.cacheDir; // Create cache directory structure await fs.mkdir(cacheDir, { recursive: true }); await fs.mkdir(path.join(cacheDir, 'data'), { recursive: true }); await fs.mkdir(path.join(cacheDir, 'meta'), { recursive: true }); await fs.mkdir(path.join(cacheDir, 'temp'), { recursive: true }); // Create cache index file if it doesn't exist const indexPath = path.join(cacheDir, 'index.json'); try { await fs.access(indexPath); } catch { await fs.writeFile(indexPath, JSON.stringify({ version: '1.0', entries: {} })); } this.diskCache = new DiskCache(this.config.disk); await this.diskCache.initialize(); logger.info('Disk cache initialized', { cacheDir }); } catch (error) { logger.error('Failed to initialize disk cache:', error); throw error; } } /** * Initialize distributed cache (Redis, etc.) */ async initializeDistributedCache() { try { if (this.config.distributed.provider === 'redis') { this.distributedCache = new RedisCache(this.config.distributed); } else { throw new Error(`Unsupported distributed cache provider: ${this.config.distributed.provider}`); } await this.distributedCache.initialize(); logger.info('Distributed cache initialized', { provider: this.config.distributed.provider }); } catch (error) { logger.warn('Failed to initialize distributed cache:', error); // Don't throw - continue with memory and disk cache only this.config.distributed.enabled = false; } } /** * Get value from cache with multi-layer lookup */ async get(key, options = {}) { const startTime = Date.now(); try { // Validate key this.validateKey(key); let value = null; let source = null; // Try memory cache first if (this.config.memory.enabled) { value = await this.getFromMemory(key); if (value !== null) { source = 'memory'; this.stats.hits.memory++; } else { this.stats.misses.memory++; } } // Try disk cache if not found in memory if (value === null && this.config.disk.enabled && this.diskCache) { value = await this.getFromDisk(key); if (value !== null) { source = 'disk'; this.stats.hits.disk++; // Promote to memory cache if configured if (this.config.memory.enabled && this.config.strategies.writeThrough) { await this.setToMemory(key, value, options); } } else { this.stats.misses.disk++; } } // Try distributed cache if not found if (value === null && this.config.distributed.enabled && this.distributedCache) { value = await this.getFromDistributed(key); if (value !== null) { source = 'distributed'; this.stats.hits.distributed++; // Promote to local caches if configured if (this.config.strategies.writeThrough) { if (this.config.memory.enabled) { await this.setToMemory(key, value, options); } if (this.config.disk.enabled) { await this.setToDisk(key, value, options); } } } else { this.stats.misses.distributed++; } } // Update performance metrics const accessTime = Date.now() - startTime; this.updateAccessTime(source || 'miss', accessTime); // Check for refresh-ahead strategy if (value !== null && this.config.strategies.refreshAhead) { this.checkRefreshAhead(key, source); } this.emit('get', { key, hit: value !== null, source, accessTime }); return value; } catch (error) { logger.error('Cache get error:', { key, error: error.message }); this.stats.errors[source || 'unknown']++; throw error; } } /** * Set value in cache with multi-layer storage */ async set(key, value, options = {}) { const startTime = Date.now(); try { // Validate inputs this.validateKey(key); this.validateValue(value); const ttl = options.ttl || this.config.strategies.defaultTtl; const setOptions = { ...options, ttl }; // Set in all enabled cache layers const promises = []; // Memory cache if (this.config.memory.enabled) { promises.push(this.setToMemory(key, value, setOptions)); } // Disk cache if (this.config.disk.enabled && this.diskCache) { if (this.config.performance.asyncWrites) { // Async write for better performance this.setToDisk(key, value, setOptions).catch(error => { logger.error('Async disk cache set failed:', { key, error: error.message }); this.stats.errors.disk++; }); } else { promises.push(this.setToDisk(key, value, setOptions)); } } // Distributed cache if (this.config.distributed.enabled && this.distributedCache) { if (this.config.performance.asyncWrites) { // Async write for better performance this.setToDistributed(key, value, setOptions).catch(error => { logger.error('Async distributed cache set failed:', { key, error: error.message }); this.stats.errors.distributed++; }); } else { promises.push(this.setToDistributed(key, value, setOptions)); } } // Wait for synchronous operations await Promise.all(promises); // Update dependencies if specified if (options.dependencies) { this.updateDependencies(key, options.dependencies); } const setTime = Date.now() - startTime; this.emit('set', { key, setTime, layers: promises.length }); return true; } catch (error) { logger.error('Cache set error:', { key, error: error.message }); throw error; } } /** * Delete key from all cache layers */ async delete(key, options = {}) { try { this.validateKey(key); const promises = []; // Delete from all cache layers if (this.config.memory.enabled) { promises.push(this.deleteFromMemory(key)); } if (this.config.disk.enabled && this.diskCache) { promises.push(this.deleteFromDisk(key)); } if (this.config.distributed.enabled && this.distributedCache) { promises.push(this.deleteFromDistributed(key)); } await Promise.all(promises); // Handle dependent cache invalidation if (options.invalidateDependents !== false) { await this.invalidateDependents(key); } // Clean up dependency tracking this.cleanupDependencies(key); this.emit('delete', { key }); return true; } catch (error) { logger.error('Cache delete error:', { key, error: error.message }); throw error; } } /** * Clear cache with optional pattern matching */ async clear(pattern = null) { try { const promises = []; if (this.config.memory.enabled) { promises.push(this.clearMemory(pattern)); } if (this.config.disk.enabled && this.diskCache) { promises.push(this.clearDisk(pattern)); } if (this.config.distributed.enabled && this.distributedCache) { promises.push(this.clearDistributed(pattern)); } await Promise.all(promises); // Clear dependency tracking if (!pattern) { this.dependencies.clear(); this.dependents.clear(); } this.emit('clear', { pattern }); return true; } catch (error) { logger.error('Cache clear error:', { pattern, error: error.message }); throw error; } } /** * Get from memory cache */ async getFromMemory(key) { if (!this.config.memory.enabled) return null; const entry = this.memoryCache.get(key); if (!entry) return null; const meta = this.memoryMeta.get(key); // Check expiration if (meta && meta.expires && Date.now() > meta.expires) { this.memoryCache.delete(key); this.memoryMeta.delete(key); this.stats.evictions.memory++; return null; } // Update access time for LRU if (meta) { meta.lastAccess = Date.now(); meta.accessCount++; } return entry; } /** * Set to memory cache */ async setToMemory(key, value, options = {}) { if (!this.config.memory.enabled) return; // Check if eviction is needed await this.evictFromMemoryIfNeeded(); const serializedValue = this.serializeValue(value); const size = this.calculateSize(serializedValue); this.memoryCache.set(key, value); this.memoryMeta.set(key, { size, created: Date.now(), lastAccess: Date.now(), accessCount: 1, expires: options.ttl ? Date.now() + options.ttl : null }); this.stats.sets.memory++; this.stats.totalSize.memory += size; this.stats.totalItems.memory++; } /** * Delete from memory cache */ async deleteFromMemory(key) { if (!this.config.memory.enabled) return; const meta = this.memoryMeta.get(key); if (meta) { this.stats.totalSize.memory -= meta.size; this.stats.totalItems.memory--; } this.memoryCache.delete(key); this.memoryMeta.delete(key); this.stats.deletes.memory++; } /** * Clear memory cache */ async clearMemory(pattern = null) { if (!this.config.memory.enabled) return; if (pattern) { const regex = new RegExp(pattern); for (const key of this.memoryCache.keys()) { if (regex.test(key)) { await this.deleteFromMemory(key); } } } else { this.memoryCache.clear(); this.memoryMeta.clear(); this.stats.totalSize.memory = 0; this.stats.totalItems.memory = 0; } } /** * Evict from memory cache if needed */ async evictFromMemoryIfNeeded() { const maxSize = this.config.memory.maxSize; const maxItems = this.config.memory.maxItems; // Check size constraint if (this.stats.totalSize.memory > maxSize) { await this.evictFromMemoryBySize(); } // Check item count constraint if (this.stats.totalItems.memory > maxItems) { await this.evictFromMemoryByCount(); } } /** * Evict from memory by size (LRU) */ async evictFromMemoryBySize() { const targetSize = this.config.memory.maxSize * 0.8; // Evict to 80% capacity // Sort by last access time (LRU) const entries = Array.from(this.memoryMeta.entries()) .sort((a, b) => a[1].lastAccess - b[1].lastAccess); for (const [key, meta] of entries) { if (this.stats.totalSize.memory <= targetSize) break; await this.deleteFromMemory(key); this.stats.evictions.memory++; } } /** * Evict from memory by count (LRU) */ async evictFromMemoryByCount() { const targetCount = this.config.memory.maxItems * 0.8; // Evict to 80% capacity // Sort by last access time (LRU) const entries = Array.from(this.memoryMeta.entries()) .sort((a, b) => a[1].lastAccess - b[1].lastAccess); for (const [key, meta] of entries) { if (this.stats.totalItems.memory <= targetCount) break; await this.deleteFromMemory(key); this.stats.evictions.memory++; } } /** * Get from disk cache */ async getFromDisk(key) { if (!this.config.disk.enabled || !this.diskCache) return null; try { return await this.diskCache.get(key); } catch (error) { this.stats.errors.disk++; logger.error('Disk cache get error:', { key, error: error.message }); return null; } } /** * Set to disk cache */ async setToDisk(key, value, options = {}) { if (!this.config.disk.enabled || !this.diskCache) return; try { await this.diskCache.set(key, value, options); this.stats.sets.disk++; } catch (error) { this.stats.errors.disk++; logger.error('Disk cache set error:', { key, error: error.message }); throw error; } } /** * Delete from disk cache */ async deleteFromDisk(key) { if (!this.config.disk.enabled || !this.diskCache) return; try { await this.diskCache.delete(key); this.stats.deletes.disk++; } catch (error) { this.stats.errors.disk++; logger.error('Disk cache delete error:', { key, error: error.message }); throw error; } } /** * Clear disk cache */ async clearDisk(pattern = null) { if (!this.config.disk.enabled || !this.diskCache) return; try { await this.diskCache.clear(pattern); } catch (error) { this.stats.errors.disk++; logger.error('Disk cache clear error:', { pattern, error: error.message }); throw error; } } /** * Get from distributed cache */ async getFromDistributed(key) { if (!this.config.distributed.enabled || !this.distributedCache) return null; try { return await this.distributedCache.get(key); } catch (error) { this.stats.errors.distributed++; logger.error('Distributed cache get error:', { key, error: error.message }); return null; } } /** * Set to distributed cache */ async setToDistributed(key, value, options = {}) { if (!this.config.distributed.enabled || !this.distributedCache) return; try { await this.distributedCache.set(key, value, options); this.stats.sets.distributed++; } catch (error) { this.stats.errors.distributed++; logger.error('Distributed cache set error:', { key, error: error.message }); throw error; } } /** * Delete from distributed cache */ async deleteFromDistributed(key) { if (!this.config.distributed.enabled || !this.distributedCache) return; try { await this.distributedCache.delete(key); this.stats.deletes.distributed++; } catch (error) { this.stats.errors.distributed++; logger.error('Distributed cache delete error:', { key, error: error.message }); throw error; } } /** * Clear distributed cache */ async clearDistributed(pattern = null) { if (!this.config.distributed.enabled || !this.distributedCache) return; try { await this.distributedCache.clear(pattern); } catch (error) { this.stats.errors.distributed++; logger.error('Distributed cache clear error:', { pattern, error: error.message }); throw error; } } /** * Update cache dependencies */ updateDependencies(key, dependencies) { // Clear existing dependencies for this key const existingDeps = this.dependents.get(key); if (existingDeps) { for (const dep of existingDeps) { const depSet = this.dependencies.get(dep); if (depSet) { depSet.delete(key); if (depSet.size === 0) { this.dependencies.delete(dep); } } } } // Set new dependencies this.dependents.set(key, new Set(dependencies)); for (const dep of dependencies) { if (!this.dependencies.has(dep)) { this.dependencies.set(dep, new Set()); } this.dependencies.get(dep).add(key); } } /** * Invalidate dependent cache entries */ async invalidateDependents(key) { const dependents = this.dependencies.get(key); if (!dependents) return; const promises = []; for (const dependent of dependents) { promises.push(this.delete(dependent, { invalidateDependents: false })); } await Promise.all(promises); this.dependencies.delete(key); } /** * Clean up dependency tracking for a key */ cleanupDependencies(key) { // Remove from dependencies const dependents = this.dependencies.get(key); if (dependents) { for (const dependent of dependents) { const depSet = this.dependents.get(dependent); if (depSet) { depSet.delete(key); if (depSet.size === 0) { this.dependents.delete(dependent); } } } this.dependencies.delete(key); } // Remove from dependents const deps = this.dependents.get(key); if (deps) { for (const dep of deps) { const depSet = this.dependencies.get(dep); if (depSet) { depSet.delete(key); if (depSet.size === 0) { this.dependencies.delete(dep); } } } this.dependents.delete(key); } } /** * Check for refresh-ahead strategy */ checkRefreshAhead(key, source) { // Implement refresh-ahead logic here // This could trigger background refresh of the cache entry // before it expires to maintain performance this.emit('refreshAhead', { key, source }); } /** * Start maintenance tasks */ startMaintenanceTasks() { // Cleanup expired entries const cleanupInterval = setInterval(() => { this.cleanupExpiredEntries().catch(error => { logger.error('Cleanup task error:', error); }); }, this.config.memory.checkPeriod); this.cleanupIntervals.push(cleanupInterval); // Update statistics const statsInterval = setInterval(() => { this.updateStatistics(); }, 60000); // Every minute this.cleanupIntervals.push(statsInterval); } /** * Start performance monitoring */ startPerformanceMonitoring() { const monitorInterval = setInterval(() => { this.collectPerformanceMetrics(); }, 30000); // Every 30 seconds this.cleanupIntervals.push(monitorInterval); } /** * Cleanup expired entries from memory */ async cleanupExpiredEntries() { const now = Date.now(); const expiredKeys = []; for (const [key, meta] of this.memoryMeta.entries()) { if (meta.expires && now > meta.expires) { expiredKeys.push(key); } } for (const key of expiredKeys) { await this.deleteFromMemory(key); this.stats.evictions.memory++; } if (expiredKeys.length > 0) { logger.debug(`Cleaned up ${expiredKeys.length} expired cache entries`); } } /** * Update cache statistics */ updateStatistics() { // Calculate hit ratios const totalHits = this.stats.hits.memory + this.stats.hits.disk + this.stats.hits.distributed; const totalMisses = this.stats.misses.memory + this.stats.misses.disk + this.stats.misses.distributed; const totalRequests = totalHits + totalMisses; if (totalRequests > 0) { this.stats.hitRatio.overall = totalHits / totalRequests; this.stats.hitRatio.memory = this.stats.hits.memory / (this.stats.hits.memory + this.stats.misses.memory || 1); this.stats.hitRatio.disk = this.stats.hits.disk / (this.stats.hits.disk + this.stats.misses.disk || 1); this.stats.hitRatio.distributed = this.stats.hits.distributed / (this.stats.hits.distributed + this.stats.misses.distributed || 1); } this.emit('statisticsUpdated', this.stats); } /** * Collect performance metrics */ collectPerformanceMetrics() { const metrics = { timestamp: Date.now(), memory: { usage: this.stats.totalSize.memory, items: this.stats.totalItems.memory, hitRatio: this.stats.hitRatio.memory }, disk: { hitRatio: this.stats.hitRatio.disk }, distributed: { hitRatio: this.stats.hitRatio.distributed }, overall: { hitRatio: this.stats.hitRatio.overall } }; this.performanceMetrics.hitRatios.push(metrics.overall.hitRatio); this.performanceMetrics.memoryUsage.push(metrics.memory.usage); // Keep only last 100 data points if (this.performanceMetrics.hitRatios.length > 100) { this.performanceMetrics.hitRatios.shift(); } if (this.performanceMetrics.memoryUsage.length > 100) { this.performanceMetrics.memoryUsage.shift(); } this.emit('performanceMetrics', metrics); } /** * Update access time metrics */ updateAccessTime(source, time) { if (!this.performanceMetrics.operationTimes.has(source)) { this.performanceMetrics.operationTimes.set(source, []); } const times = this.performanceMetrics.operationTimes.get(source); times.push(time); // Keep only last 100 measurements if (times.length > 100) { times.shift(); } // Update average this.stats.avgAccessTime[source] = times.reduce((a, b) => a + b, 0) / times.length; } /** * Validate cache key */ validateKey(key) { if (typeof key !== 'string' || key.length === 0) { throw new Error('Cache key must be a non-empty string'); } if (key.length > 250) { throw new Error('Cache key too long (max 250 characters)'); } // Check for invalid characters if (!/^[\w\-:._/]+$/.test(key)) { throw new Error('Cache key contains invalid characters'); } } /** * Validate cache value */ validateValue(value) { if (value === undefined) { throw new Error('Cache value cannot be undefined'); } // Check size limit const serialized = this.serializeValue(value); const size = this.calculateSize(serialized); if (size > 10 * 1024 * 1024) { // 10MB limit per value throw new Error('Cache value too large (max 10MB)'); } } /** * Serialize value for storage */ serializeValue(value) { if (typeof value === 'string') { return value; } try { return JSON.stringify(value); } catch (error) { throw new Error(`Failed to serialize cache value: ${error.message}`); } } /** * Calculate size of serialized value */ calculateSize(value) { return Buffer.byteLength(typeof value === 'string' ? value : JSON.stringify(value), 'utf8'); } /** * Get cache statistics */ getStatistics() { return { ...this.stats, performance: { hitRatios: [...this.performanceMetrics.hitRatios], memoryUsage: [...this.performanceMetrics.memoryUsage], averageAccessTimes: { ...this.stats.avgAccessTime } }, dependencies: { totalDependencies: this.dependencies.size, totalDependents: this.dependents.size } }; } /** * Get cache health status */ getHealthStatus() { const health = { status: 'healthy', issues: [], layers: { memory: this.config.memory.enabled, disk: this.config.disk.enabled && !!this.diskCache, distributed: this.config.distributed.enabled && !!this.distributedCache } }; // Check memory usage if (this.stats.totalSize.memory > this.config.memory.maxSize * 0.9) { health.issues.push('Memory cache near capacity'); health.status = 'warning'; } // Check error rates const totalOps = this.stats.sets.memory + this.stats.gets + this.stats.deletes.memory; const totalErrors = this.stats.errors.memory + this.stats.errors.disk + this.stats.errors.distributed; if (totalOps > 0 && totalErrors / totalOps > 0.05) { health.issues.push('High error rate detected'); health.status = 'warning'; } // Check hit ratio if (this.stats.hitRatio.overall < 0.5) { health.issues.push('Low cache hit ratio'); health.status = 'warning'; } return health; } /** * Shutdown cache system */ async shutdown() { try { logger.info('Shutting down multi-layer cache system'); // Clear cleanup intervals for (const interval of this.cleanupIntervals) { clearInterval(interval); } // Shutdown cache layers if (this.diskCache) { await this.diskCache.shutdown(); } if (this.distributedCache) { await this.distributedCache.shutdown(); } // Clear memory this.memoryCache.clear(); this.memoryMeta.clear(); this.dependencies.clear(); this.dependents.clear(); this.initialized = false; this.emit('shutdown'); logger.info('Cache system shutdown completed'); } catch (error) { logger.error('Error during cache shutdown:', error); throw error; } } } /** * Disk cache implementation */ class DiskCache { constructor(config) { this.config = config; this.cacheDir = config.cacheDir; this.index = new Map(); this.indexPath = path.join(this.cacheDir, 'index.json'); } async initialize() { // Load existing index try { const indexData = await fs.readFile(this.indexPath, 'utf8'); const parsed = JSON.parse(indexData); this.index = new Map(Object.entries(parsed.entries || {})); } catch (error) { // Index doesn't exist or is corrupted, start fresh this.index = new Map(); } } async get(key) { const entry = this.index.get(key); if (!entry) return null; // Check expiration if (entry.expires && Date.now() > entry.expires) { await this.delete(key); return null; } try { const filePath = path.join(this.cacheDir, 'data', this.getFileName(key)); const data = await fs.readFile(filePath, 'utf8'); // Decompress if needed if (entry.compressed) { // Implement decompression logic here return JSON.parse(data); } return JSON.parse(data); } catch (error) { // File doesn't exist or is corrupted await this.delete(key); return null; } } async set(key, value, options = {}) { const fileName = this.getFileName(key); const filePath = path.join(this.cacheDir, 'data', fileName); const metaPath = path.join(this.cacheDir, 'meta', fileName + '.meta'); // Serialize value const serialized = JSON.stringify(value); const compressed = this.shouldCompress(serialized); // Write data file await fs.writeFile(filePath, serialized); // Write metadata const meta = { key, created: Date.now(), expires: options.ttl ? Date.now() + options.ttl : null, size: Buffer.byteLength(serialized), compressed }; await fs.writeFile(metaPath, JSON.stringify(meta)); // Update index this.index.set(key, meta); await this.saveIndex(); } async delete(key) { const fileName = this.getFileName(key); try { await fs.unlink(path.join(this.cacheDir, 'data', fileName)); await fs.unlink(path.join(this.cacheDir, 'meta', fileName + '.meta')); } catch (error) { // Files may not exist, ignore } this.index.delete(key); await this.saveIndex(); } async clear(pattern = null) { if (pattern) { const regex = new RegExp(pattern); for (const key of this.index.keys()) { if (regex.test(key)) { await this.delete(key); } } } else { // Clear all const dataDir = path.join(this.cacheDir, 'data'); const metaDir = path.join(this.cacheDir, 'meta'); try { await fs.rm(dataDir, { recursive: true, force: true }); await fs.rm(metaDir, { recursive: true, force: true }); await fs.mkdir(dataDir, { recursive: true }); await fs.mkdir(metaDir, { recursive: true }); } catch (error) { // Ignore errors } this.index.clear(); await this.saveIndex(); } } getFileName(key) { return crypto.createHash('sha256').update(key).digest('hex'); } shouldCompress(data) { return this.config.compression && data.length > this.config.compressionThreshold; } async saveIndex() { const indexData = { version: '1.0', entries: Object.fromEntries(this.index.entries()) }; await fs.writeFile(this.indexPath, JSON.stringify(indexData, null, 2)); } async shutdown() { await this.saveIndex(); } } /** * Redis cache implementation (placeholder) */ class RedisCache { constructor(config) { this.config = config; this.client = null; } async initialize() { // Initialize Redis client here throw new Error('Redis cache not implemented yet'); } async get(key) { // Implement Redis get } async set(key, value, options = {}) { // Implement Redis set } async delete(key) { // Implement Redis delete } async clear(pattern = null) { // Implement Redis clear } async shutdown() { // Implement Redis shutdown } } export default MultiLayerCache;

Latest Blog Posts

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/sascodiego/KGsMCP'

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