mcp-ynab
Version:
Model Context Protocol server for YNAB integration
210 lines (178 loc) • 8.01 kB
JavaScript
/**
* 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 };