Skip to main content
Glama

OmniFocus-MCP

cacheManager.ts6.03 kB
import { OmnifocusDatabase } from '../types.js'; import { executeOmniFocusScript } from './scriptExecution.js'; interface CacheEntry { data: OmnifocusDatabase; timestamp: Date; checksum?: string; } interface CacheOptions { ttlSeconds?: number; // Time to live in seconds maxSize?: number; // Maximum cache size in MB useChecksum?: boolean; // Whether to use checksum for validation } class OmniFocusCacheManager { private cache: Map<string, CacheEntry> = new Map(); private options: Required<CacheOptions>; private totalSize: number = 0; constructor(options: CacheOptions = {}) { this.options = { ttlSeconds: options.ttlSeconds ?? 300, // 5 minutes default maxSize: options.maxSize ?? 50, // 50MB default useChecksum: options.useChecksum ?? true }; } /** * Get cached data if valid, otherwise return null */ async get(key: string): Promise<OmnifocusDatabase | null> { const entry = this.cache.get(key); if (!entry) { return null; } // Check if cache has expired const age = Date.now() - entry.timestamp.getTime(); if (age > this.options.ttlSeconds * 1000) { this.cache.delete(key); return null; } // If using checksums, validate the cache is still current if (this.options.useChecksum && entry.checksum) { const currentChecksum = await this.getDatabaseChecksum(); if (currentChecksum !== entry.checksum) { this.cache.delete(key); return null; } } return entry.data; } /** * Store data in cache */ async set(key: string, data: OmnifocusDatabase): Promise<void> { // Estimate size (rough approximation) const dataSize = JSON.stringify(data).length / (1024 * 1024); // Convert to MB // Check if adding this would exceed max size if (this.totalSize + dataSize > this.options.maxSize) { this.evictOldest(); } const checksum = this.options.useChecksum ? await this.getDatabaseChecksum() : undefined; this.cache.set(key, { data, timestamp: new Date(), checksum }); this.totalSize += dataSize; } /** * Invalidate specific cache entry */ invalidate(key: string): void { this.cache.delete(key); } /** * Clear all cache entries */ clear(): void { this.cache.clear(); this.totalSize = 0; } /** * Get cache statistics */ getStats(): { entries: number; sizeEstimateMB: number; oldestEntry: Date | null; hitRate: number; } { let oldestEntry: Date | null = null; this.cache.forEach(entry => { if (!oldestEntry || entry.timestamp < oldestEntry) { oldestEntry = entry.timestamp; } }); return { entries: this.cache.size, sizeEstimateMB: this.totalSize, oldestEntry, hitRate: this.calculateHitRate() }; } /** * Get a lightweight checksum of the database state */ private async getDatabaseChecksum(): Promise<string> { try { const script = ` (() => { try { // Get counts and latest modification times as a simple checksum const taskCount = flattenedTasks.length; const projectCount = flattenedProjects.length; // Get the most recent modification time let latestMod = new Date(0); flattenedTasks.forEach(task => { if (task.modificationDate && task.modificationDate > latestMod) { latestMod = task.modificationDate; } }); flattenedProjects.forEach(project => { if (project.modificationDate && project.modificationDate > latestMod) { latestMod = project.modificationDate; } }); // Create a simple checksum string const checksum = taskCount + "-" + projectCount + "-" + latestMod.getTime(); return JSON.stringify({ checksum }); } catch (error) { return JSON.stringify({ checksum: "error-" + Date.now() }); } })(); `; // Write to temp file and execute const fs = await import('fs'); const tempFile = `/tmp/omnifocus_checksum_${Date.now()}.js`; fs.writeFileSync(tempFile, script); const result = await executeOmniFocusScript(tempFile); fs.unlinkSync(tempFile); return result.checksum || `fallback-${Date.now()}`; } catch (error) { console.error('Error getting database checksum:', error); return `error-${Date.now()}`; } } /** * Evict oldest cache entries when size limit is reached */ private evictOldest(): void { let oldestKey: string | null = null; let oldestTime = new Date(); this.cache.forEach((entry, key) => { if (entry.timestamp < oldestTime) { oldestTime = entry.timestamp; oldestKey = key; } }); if (oldestKey) { this.cache.delete(oldestKey); // Recalculate total size (simplified for now) this.totalSize *= 0.9; } } // Tracking for hit rate calculation private hits = 0; private misses = 0; private calculateHitRate(): number { const total = this.hits + this.misses; if (total === 0) return 0; return (this.hits / total) * 100; } trackHit(): void { this.hits++; } trackMiss(): void { this.misses++; } } // Singleton instance let cacheManager: OmniFocusCacheManager | null = null; /** * Get or create the cache manager instance */ export function getCacheManager(options?: CacheOptions): OmniFocusCacheManager { if (!cacheManager) { cacheManager = new OmniFocusCacheManager(options); } return cacheManager; } /** * Reset the cache manager (useful for testing) */ export function resetCacheManager(): void { if (cacheManager) { cacheManager.clear(); } cacheManager = null; }

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/themotionmachine/OmniFocus-MCP'

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