UNPKG

mcp-ynab

Version:

Model Context Protocol server for YNAB integration

210 lines (178 loc) 8.01 kB
/** * Show YNAB Cache Statistics and Status * Displays traditional cache and global cache performance metrics */ import { getCacheCompatibility } from '../shared/cache-compatibility.js'; const toolDefinition = { name: 'show_cache_stats', description: 'Display comprehensive cache statistics including global cache performance, request tracking, and optimization metrics', inputSchema: { type: 'object', properties: { detailed: { type: 'boolean', description: 'Show detailed statistics including dataset breakdowns and session info', default: false }, reset_stats: { type: 'boolean', description: 'Reset request tracking statistics (useful for testing)', default: false } }, additionalProperties: false } }; const handler = async (params) => { try { const { detailed = false, reset_stats = false } = params; // Get compatibility layer instance const compatibility = getCacheCompatibility(); if (reset_stats && compatibility.globalCache) { // Reset request tracking for testing compatibility.globalCache.requestTracker.requests = []; compatibility.globalCache.sessionState = { lastActivity: Date.now(), callFrequency: 0, activeSession: false, recentTools: [] }; } const stats = compatibility.getGlobalStats(); const result = { timestamp: new Date().toISOString(), globalCacheEnabled: stats.enabled, summary: {} }; if (stats.enabled) { // Request tracking statistics - handle case where requestTracker might not be fully initialized const requestStats = stats.requestTracker || { used: 0, remaining: 200, limit: 200, percentUsed: 0, isNearLimit: false, shouldBlock: false, isActiveSession: false, endpointStats: {}, oldestRequest: null, totalRequestsLogged: 0 }; result.requestTracking = { used: requestStats.used, remaining: requestStats.remaining, limit: requestStats.limit, percentUsed: Math.round(requestStats.percentUsed * 100) / 100, isNearLimit: requestStats.isNearLimit, shouldBlock: requestStats.shouldBlock, isActiveSession: requestStats.isActiveSession }; // Session state - handle case where sessionState might not be fully initialized const sessionState = stats.sessionState || { activeSession: false, callFrequency: 0, lastActivity: Date.now(), recentTools: [] }; result.sessionState = { activeSession: sessionState.activeSession, callFrequency: sessionState.callFrequency, lastActivity: new Date(sessionState.lastActivity).toISOString(), recentTools: (sessionState.recentTools || []).slice(0, 5) // Last 5 tools }; // Dataset statistics - handle case where no datasets exist yet result.datasets = {}; let totalCachedItems = 0; let totalDatasets = 0; const datasets = stats.datasets || {}; for (const [datasetType, datasetStats] of Object.entries(datasets)) { if (datasetStats && typeof datasetStats === 'object') { totalDatasets += datasetStats.count || 0; totalCachedItems += datasetStats.totalItems || 0; result.datasets[datasetType] = { budgetsCached: datasetStats.count || 0, totalItems: datasetStats.totalItems || 0, totalAccesses: datasetStats.totalAccesses || 0, averageAgeSeconds: datasetStats.averageAge || 0 }; if (detailed) { const count = datasetStats.count || 1; // Avoid division by zero result.datasets[datasetType].averageItemsPerBudget = Math.round((datasetStats.totalItems || 0) / count); result.datasets[datasetType].accessesPerItem = (datasetStats.totalItems || 0) > 0 ? Math.round(((datasetStats.totalAccesses || 0) / (datasetStats.totalItems || 1)) * 100) / 100 : 0; } } } // Performance summary - use safe defaults result.summary.totalBudgetsCached = stats.budgets || 0; result.summary.totalDatasetsCached = totalDatasets; result.summary.totalItemsCached = totalCachedItems; result.summary.estimatedMemoryUsageKB = Math.round((stats.totalMemoryUsage || 0) / 1024); result.summary.apiRequestsSavedThisHour = Math.max(0, totalDatasets - (requestStats.used || 0)); // Efficiency metrics if ((requestStats.used || 0) > 0) { result.summary.cacheEfficiencyRatio = Math.round((totalDatasets / (requestStats.used || 1)) * 100) / 100; result.summary.estimatedRequestsSavedPercent = totalDatasets > 0 ? Math.round(((totalDatasets - (requestStats.used || 0)) / totalDatasets) * 100) : 0; } if (detailed) { // Detailed request tracking result.detailedRequestTracking = { endpointStats: requestStats.endpointStats || {}, oldestRequestAge: requestStats.oldestRequest ? Math.round((Date.now() - new Date(requestStats.oldestRequest).getTime()) / 60000) : null, totalRequestsLogged: requestStats.totalRequestsLogged || 0 }; // Rate limiting recommendations const timeUntilCapacity = compatibility.globalCache?.requestTracker?.getTimeUntilCapacityAvailable(10) || 0; const optimalDelay = compatibility.globalCache?.requestTracker?.getOptimalDelay() || 0; result.rateLimitingAdvice = { timeUntilCapacityAvailableMs: timeUntilCapacity, recommendedDelayMs: optimalDelay, canMakeRequests: (requestStats.used || 0) < (requestStats.limit || 200) * 0.95 }; } // Health indicators result.healthIndicators = { status: (requestStats.shouldBlock) ? 'RATE_LIMITED' : (requestStats.isNearLimit) ? 'WARNING' : 'HEALTHY', memoryUsage: (stats.totalMemoryUsage || 0) < 50 * 1024 * 1024 ? 'NORMAL' : 'HIGH', // 50MB threshold cacheAge: Object.values(datasets).some(ds => (ds.averageAge || 0) > 1800) ? 'AGING' : 'FRESH' // 30 min threshold }; } else { result.message = 'Global cache is disabled. Using traditional caching only.'; result.globalCacheEnabled = false; } // Recommendations result.recommendations = []; if (stats.enabled) { const requestStats = stats.requestTracker || {}; if ((requestStats.percentUsed || 0) > 80) { result.recommendations.push('Consider spacing out API-heavy operations to avoid rate limiting'); } if ((stats.totalMemoryUsage || 0) > 100 * 1024 * 1024) { // 100MB result.recommendations.push('High memory usage detected - consider reducing cache TTL values'); } if (Object.keys(stats.datasets || {}).length === 0) { result.recommendations.push('No cached datasets found - cache may need time to warm up'); } const efficiencyRatio = result.summary.cacheEfficiencyRatio || 0; if (efficiencyRatio < 2) { result.recommendations.push('Low cache efficiency - consider longer TTL values for better API optimization'); } else if (efficiencyRatio > 10) { result.recommendations.push('Excellent cache efficiency! API requests are being minimized effectively'); } } else { result.recommendations.push('Enable global cache by setting GLOBAL_CACHE_ENABLED=true for better API efficiency'); } return result; } catch (error) { console.error('Cache stats error:', error); return { error: true, message: `Failed to retrieve cache statistics: ${error.message}`, timestamp: new Date().toISOString() }; } }; export { toolDefinition, handler };