Skip to main content
Glama
FosterG4

Code Reference Optimizer MCP Server

by FosterG4
CacheManager.ts10.3 kB
import { LRUCache } from 'lru-cache'; import type { CodeContext, CacheEntry, CacheStats, CacheEvictionPolicy, OptimizationConfig } from '../types/index.js'; export class CacheManager { private cache: LRUCache<string, CodeContext>; private stats: CacheStats; private config: OptimizationConfig; private evictionPolicy: CacheEvictionPolicy; private accessCounts: Map<string, number>; private lastAccessed: Map<string, number>; constructor(config?: Partial<OptimizationConfig>) { this.config = { maxCacheSize: 100 * 1024 * 1024, // 100MB maxTokensPerEntry: 10000, cacheExpirationMs: 24 * 60 * 60 * 1000, // 24 hours enableImportOptimization: true, enableDiffOptimization: true, tokenEstimationRatio: 4, relevanceThreshold: 0.5, ...config, }; this.cache = new LRUCache<string, CodeContext>({ max: 1000, // Maximum number of entries maxSize: this.config.maxCacheSize, sizeCalculation: (value) => this.calculateEntrySize(value), dispose: (value, key) => this.onEviction(key, value), ttl: this.config.cacheExpirationMs, }); this.stats = { totalEntries: 0, totalSize: 0, hitRate: 0, missRate: 0, evictionCount: 0, }; this.accessCounts = new Map(); this.lastAccessed = new Map(); this.evictionPolicy = 'lru'; } /** * Get cached context by key */ async get(key: string): Promise<CodeContext | null> { const context = this.cache.get(key); if (context) { this.recordHit(key); this.updateAccessStats(key); return context; } this.recordMiss(key); return null; } /** * Set cached context */ async set(key: string, context: CodeContext): Promise<void> { // Check if entry exceeds token limit if (context.tokenCount > this.config.maxTokensPerEntry) { context = this.truncateContext(context); } // Apply relevance-based filtering if (context.relevanceScore < this.config.relevanceThreshold) { return; // Don't cache low-relevance content } // Check if we need to evict entries before adding await this.ensureCapacity(this.calculateEntrySize(context)); this.cache.set(key, context); this.updateAccessStats(key); this.updateStats(); } /** * Get all cached contexts for a specific file path */ async getByFilePath(filePath: string): Promise<CodeContext[]> { const results: CodeContext[] = []; for (const [key, context] of this.cache.entries()) { if (context.filePath === filePath) { results.push(context); this.updateAccessStats(key); } } return results.sort((a, b) => { // Sort by relevance score and recency const scoreA = a.relevanceScore * this.getRecencyWeight(a.timestamp); const scoreB = b.relevanceScore * this.getRecencyWeight(b.timestamp); return scoreB - scoreA; }); } /** * Invalidate cache entries for a specific file */ async invalidateFile(filePath: string): Promise<void> { const keysToDelete: string[] = []; for (const [key, context] of this.cache.entries()) { if (context.filePath === filePath) { keysToDelete.push(key); } } for (const key of keysToDelete) { this.cache.delete(key); this.accessCounts.delete(key); this.lastAccessed.delete(key); } this.updateStats(); } /** * Clear all cache entries */ async clear(): Promise<void> { this.cache.clear(); this.accessCounts.clear(); this.lastAccessed.clear(); this.updateStats(); } /** * Get cache statistics */ getStats(): CacheStats { return { ...this.stats }; } /** * Get cache entries sorted by various criteria */ getEntriesByRelevance(): Array<{ key: string; context: CodeContext; score: number }> { const entries: Array<{ key: string; context: CodeContext; score: number }> = []; for (const [key, context] of this.cache.entries()) { const accessCount = this.accessCounts.get(key) || 0; const lastAccess = this.lastAccessed.get(key) || 0; const recencyWeight = this.getRecencyWeight(lastAccess); const accessWeight = Math.log(accessCount + 1) / 10; // Logarithmic scaling const score = context.relevanceScore * 0.4 + recencyWeight * 0.3 + accessWeight * 0.3; entries.push({ key, context, score }); } return entries.sort((a, b) => b.score - a.score); } /** * Optimize cache by removing low-value entries */ async optimizeCache(): Promise<{ removedEntries: number; spaceSaved: number; newHitRate: number; }> { const initialSize = this.cache.size; const initialEntries = this.stats.totalEntries; // Get entries sorted by relevance const entries = this.getEntriesByRelevance(); // Remove bottom 20% of entries if cache is getting full const cacheUtilization = this.stats.totalSize / this.config.maxCacheSize; if (cacheUtilization > 0.8) { const entriesToRemove = Math.floor(entries.length * 0.2); const lowValueEntries = entries.slice(-entriesToRemove); for (const entry of lowValueEntries) { this.cache.delete(entry.key); this.accessCounts.delete(entry.key); this.lastAccessed.delete(entry.key); } } // Apply custom eviction policies based on current policy for (const [key, context] of this.cache.entries()) { if (this.shouldEvictEntry(key, context)) { this.cache.delete(key); this.accessCounts.delete(key); this.lastAccessed.delete(key); } } this.updateStats(); return { removedEntries: initialEntries - this.stats.totalEntries, spaceSaved: initialSize - this.cache.size, newHitRate: this.stats.hitRate, }; } private async ensureCapacity(requiredSize: number): Promise<void> { const availableSpace = this.config.maxCacheSize - this.stats.totalSize; if (availableSpace < requiredSize) { // Need to free up space const spaceToFree = requiredSize - availableSpace + (this.config.maxCacheSize * 0.1); // Free 10% extra await this.freeSpace(spaceToFree); } } private async freeSpace(targetSize: number): Promise<void> { let freedSpace = 0; const entries = this.getEntriesByRelevance(); // Remove lowest scoring entries first for (let i = entries.length - 1; i >= 0 && freedSpace < targetSize; i--) { const entry = entries[i]; if (!entry) continue; const entrySize = this.calculateEntrySize(entry.context); this.cache.delete(entry.key); this.accessCounts.delete(entry.key); this.lastAccessed.delete(entry.key); freedSpace += entrySize; this.stats.evictionCount++; } } private calculateEntrySize(context: CodeContext): number { const codeSize = context.extractedCode.length; const importsSize = context.imports.join('').length; const metadataSize = JSON.stringify({ symbols: context.symbols, dependencies: context.dependencies, }).length; return codeSize + importsSize + metadataSize; } private truncateContext(context: CodeContext): CodeContext { const maxChars = this.config.maxTokensPerEntry * this.config.tokenEstimationRatio; if (context.extractedCode.length <= maxChars) { return context; } // Truncate while preserving structure const lines = context.extractedCode.split('\n'); const truncatedLines: string[] = []; let currentLength = 0; for (const line of lines) { if (currentLength + line.length > maxChars) { break; } truncatedLines.push(line); currentLength += line.length + 1; // +1 for newline } return { ...context, extractedCode: truncatedLines.join('\n'), tokenCount: Math.ceil(currentLength / this.config.tokenEstimationRatio), }; } private recordHit(_key: string): void { // Update hit rate calculation const totalRequests = this.stats.hitRate + this.stats.missRate; this.stats.hitRate = (this.stats.hitRate * totalRequests + 1) / (totalRequests + 1); } private recordMiss(_key: string): void { // Update miss rate calculation const totalRequests = this.stats.hitRate + this.stats.missRate; this.stats.missRate = (this.stats.missRate * totalRequests + 1) / (totalRequests + 1); } private updateAccessStats(key: string): void { const currentCount = this.accessCounts.get(key) || 0; this.accessCounts.set(key, currentCount + 1); this.lastAccessed.set(key, Date.now()); } private updateStats(): void { this.stats.totalEntries = this.cache.size; this.stats.totalSize = this.cache.calculatedSize || 0; } private getRecencyWeight(timestamp: number): number { const ageMs = Date.now() - timestamp; const maxAge = this.config.cacheExpirationMs; return Math.max(0, 1 - (ageMs / maxAge)); } private onEviction(_key: string, _context: CodeContext): void { // Eviction cleanup is handled elsewhere this.stats.evictionCount++; } private shouldEvictEntry(key: string, context: CodeContext): boolean { const entry = this.createCacheEntry(key, context); const stats = this.getStats(); switch (this.evictionPolicy) { case 'lru': return entry.lastAccessed < Date.now() - this.config.cacheExpirationMs; case 'lfu': const avgAccess = stats.totalEntries > 0 ? Array.from(this.accessCounts.values()).reduce((a, b) => a + b, 0) / stats.totalEntries : 0; return entry.accessCount < avgAccess * 0.1; case 'ttl': const age = Date.now() - entry.lastAccessed; return age > this.config.cacheExpirationMs * 2; case 'fifo': default: return context.relevanceScore < this.config.relevanceThreshold * 0.5; } } private createCacheEntry(key: string, context: CodeContext): CacheEntry { return { key, value: context, accessCount: this.accessCounts.get(key) || 0, lastAccessed: this.lastAccessed.get(key) || Date.now(), size: this.calculateEntrySize(context) }; } }

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/FosterG4/mcpsaver'

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