Skip to main content
Glama

MCP Todoist

by kentaroh7777
cache.ts9.21 kB
interface CacheItem<T> { value: T; timestamp: number; ttl: number; accessCount: number; lastAccessed: number; } interface CacheStats { hits: number; misses: number; size: number; totalItems: number; } export class AuthCache { private static cache = new Map<string, CacheItem<any>>(); private static readonly DEFAULT_TTL = 5 * 60 * 1000; // 5 minutes private static readonly MAX_CACHE_SIZE = 100; private static stats: CacheStats = { hits: 0, misses: 0, size: 0, totalItems: 0 }; private static readonly CLEANUP_INTERVAL = 60 * 1000; // 1 minute private static cleanupTimer: NodeJS.Timeout | null = null; static { // Start automatic cleanup this.startCleanup(); } static set<T>(key: string, value: T, ttl: number = this.DEFAULT_TTL): void { // Ensure cache size doesn't exceed limit if (this.cache.size >= this.MAX_CACHE_SIZE) { this.evictLRU(); } const now = Date.now(); const item: CacheItem<T> = { value, timestamp: now, ttl, accessCount: 0, lastAccessed: now }; this.cache.set(key, item); this.stats.size = this.cache.size; this.stats.totalItems++; } static get<T>(key: string): T | null { const item = this.cache.get(key); if (!item) { this.stats.misses++; return null; } const now = Date.now(); // Check if item has expired if (now - item.timestamp > item.ttl) { this.cache.delete(key); this.stats.misses++; this.stats.size = this.cache.size; return null; } // Update access information item.accessCount++; item.lastAccessed = now; this.stats.hits++; return item.value; } static has(key: string): boolean { const item = this.cache.get(key); if (!item) return false; const now = Date.now(); if (now - item.timestamp > item.ttl) { this.cache.delete(key); this.stats.size = this.cache.size; return false; } return true; } static delete(key: string): boolean { const deleted = this.cache.delete(key); if (deleted) { this.stats.size = this.cache.size; } return deleted; } static clear(): void { this.cache.clear(); this.stats = { hits: 0, misses: 0, size: 0, totalItems: 0 }; } static getStats(): CacheStats { return { ...this.stats, size: this.cache.size }; } static getHitRate(): number { const total = this.stats.hits + this.stats.misses; return total === 0 ? 0 : this.stats.hits / total; } private static evictLRU(): void { let oldestKey: string | null = null; let oldestTime = Date.now(); for (const [key, item] of this.cache.entries()) { if (item.lastAccessed < oldestTime) { oldestTime = item.lastAccessed; oldestKey = key; } } if (oldestKey) { this.cache.delete(oldestKey); } } private static startCleanup(): void { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); } this.cleanupTimer = setInterval(() => { this.cleanup(); }, this.CLEANUP_INTERVAL); } private static cleanup(): void { const now = Date.now(); const keysToDelete: string[] = []; for (const [key, item] of this.cache.entries()) { if (now - item.timestamp > item.ttl) { keysToDelete.push(key); } } keysToDelete.forEach(key => this.cache.delete(key)); this.stats.size = this.cache.size; } static stopCleanup(): void { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); this.cleanupTimer = null; } } } // Specialized cache for authentication data export class AuthDataCache { private static readonly USER_CACHE_TTL = 10 * 60 * 1000; // 10 minutes private static readonly TOKEN_CACHE_TTL = 50 * 60 * 1000; // 50 minutes (tokens valid for 1 hour) private static readonly ACCOUNT_CACHE_TTL = 30 * 60 * 1000; // 30 minutes static cacheUser(userId: string, userData: any): void { AuthCache.set(`user:${userId}`, userData, this.USER_CACHE_TTL); } static getCachedUser(userId: string): any | null { return AuthCache.get(`user:${userId}`); } static cacheToken(userId: string, token: string): void { AuthCache.set(`token:${userId}`, token, this.TOKEN_CACHE_TTL); } static getCachedToken(userId: string): string | null { return AuthCache.get(`token:${userId}`); } static cacheAccountData(accountId: string, data: any): void { AuthCache.set(`account:${accountId}`, data, this.ACCOUNT_CACHE_TTL); } static getCachedAccountData(accountId: string): any | null { return AuthCache.get(`account:${accountId}`); } static invalidateUser(userId: string): void { AuthCache.delete(`user:${userId}`); AuthCache.delete(`token:${userId}`); } static invalidateAccount(accountId: string): void { AuthCache.delete(`account:${accountId}`); } static invalidateAll(): void { AuthCache.clear(); } } // Persistent cache for offline support export class PersistentCache { private static readonly STORAGE_KEY = 'auth_persistent_cache'; private static readonly MAX_STORAGE_SIZE = 5 * 1024 * 1024; // 5MB static async set(key: string, value: any, ttl: number = 24 * 60 * 60 * 1000): Promise<void> { try { const cache = this.getStorageCache(); const now = Date.now(); cache[key] = { value, timestamp: now, ttl, expires: now + ttl }; // Check storage size and clean if necessary const serialized = JSON.stringify(cache); if (serialized.length > this.MAX_STORAGE_SIZE) { this.cleanupExpired(cache); // If still too large, remove oldest items if (JSON.stringify(cache).length > this.MAX_STORAGE_SIZE) { this.evictOldest(cache); } } localStorage.setItem(this.STORAGE_KEY, JSON.stringify(cache)); } catch (error) { console.warn('Failed to set persistent cache:', error); } } static async get(key: string): Promise<any | null> { try { const cache = this.getStorageCache(); const item = cache[key]; if (!item) return null; const now = Date.now(); if (now > item.expires) { delete cache[key]; localStorage.setItem(this.STORAGE_KEY, JSON.stringify(cache)); return null; } return item.value; } catch (error) { console.warn('Failed to get from persistent cache:', error); return null; } } static async delete(key: string): Promise<void> { try { const cache = this.getStorageCache(); delete cache[key]; localStorage.setItem(this.STORAGE_KEY, JSON.stringify(cache)); } catch (error) { console.warn('Failed to delete from persistent cache:', error); } } static async clear(): Promise<void> { localStorage.removeItem(this.STORAGE_KEY); } private static getStorageCache(): Record<string, any> { try { const cached = localStorage.getItem(this.STORAGE_KEY); return cached ? JSON.parse(cached) : {}; } catch { return {}; } } private static cleanupExpired(cache: Record<string, any>): void { const now = Date.now(); Object.keys(cache).forEach(key => { if (cache[key].expires < now) { delete cache[key]; } }); } private static evictOldest(cache: Record<string, any>): void { const entries = Object.entries(cache); entries.sort(([, a], [, b]) => a.timestamp - b.timestamp); // Remove oldest 25% of entries const toRemoveCount = Math.ceil(entries.length * 0.25); for (let i = 0; i < toRemoveCount; i++) { delete cache[entries[i][0]]; } } } // Memory usage monitoring export class CacheMonitor { static getMemoryUsage(): { cacheSize: number; hitRate: number; stats: CacheStats; storageUsed: number; } { const stats = AuthCache.getStats(); const hitRate = AuthCache.getHitRate(); // Estimate storage usage let storageUsed = 0; try { const persistentData = localStorage.getItem(PersistentCache['STORAGE_KEY']); storageUsed = persistentData ? persistentData.length : 0; } catch { storageUsed = 0; } return { cacheSize: stats.size, hitRate, stats, storageUsed }; } static optimizeCache(): { cleaned: number; optimized: boolean; } { const initialSize = AuthCache.getStats().size; // Force cleanup of expired items AuthCache['cleanup'](); const finalSize = AuthCache.getStats().size; const cleaned = initialSize - finalSize; return { cleaned, optimized: cleaned > 0 }; } static getCacheHealth(): 'healthy' | 'warning' | 'critical' { const hitRate = AuthCache.getHitRate(); const { cacheSize } = this.getMemoryUsage(); if (hitRate > 0.8 && cacheSize < AuthCache['MAX_CACHE_SIZE'] * 0.8) { return 'healthy'; } else if (hitRate > 0.6 && cacheSize < AuthCache['MAX_CACHE_SIZE'] * 0.9) { return 'warning'; } else { return 'critical'; } } }

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/kentaroh7777/mcp-todoist'

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