UNPKG

mcp-ynab

Version:

Model Context Protocol server for YNAB integration

113 lines (97 loc) 4.23 kB
import { YnabClient } from '../../shared/ynab-client.js'; import { CacheManager } from '../../shared/cache-manager.js'; import { ErrorHandler } from '../../shared/error-handler.js'; const cache = new CacheManager(); const errorHandler = new ErrorHandler(); const toolDefinition = { name: 'get_categories', description: 'Get all budget categories with current balances, budgeted amounts, and goal information', inputSchema: { type: 'object', properties: { budget_id: { type: 'string', description: 'Specific budget ID (optional, defaults to default budget)' }, group_id: { type: 'string', description: 'Filter by specific category group ID (optional)' }, month: { type: 'string', description: 'Specific month in YYYY-MM-DD format (optional, defaults to current month)', pattern: '^\\d{4}-\\d{2}-\\d{2}$' } }, additionalProperties: false } }; const handler = async (params) => { try { const ynab = new YnabClient(); const { budget_id, group_id, month } = params; // Generate cache key const cacheKey = cache.generateKey('categories', { budget_id, group_id, month }); return await cache.getOrSet(cacheKey, async () => { const budgetId = budget_id || await ynab.getDefaultBudgetId(); const monthToUse = month || new Date().toISOString().substring(0, 10); // Get categories const categoryGroups = await ynab.getCategories(budgetId, monthToUse); // Filter by group if specified let filteredGroups = categoryGroups; if (group_id) { filteredGroups = categoryGroups.filter(group => group.id === group_id); } // Transform data for response const transformedGroups = filteredGroups.map(group => { // Skip hidden/internal category groups if (group.name.startsWith('Internal Master Category') || group.hidden) { return null; } const categories = group.categories .filter(category => !category.hidden && !category.deleted) .map(category => { // Calculate goal percentage let goalPercentageComplete = 0; if (category.goal_target && category.goal_target > 0) { goalPercentageComplete = Math.min( Math.max((category.balance / category.goal_target) * 100, 0), 100 ); } return { id: category.id, name: category.name, budgeted: ynab.milliunitsToAmount(category.budgeted), activity: ynab.milliunitsToAmount(category.activity), balance: ynab.milliunitsToAmount(category.balance), goal_type: category.goal_type || null, goal_target: category.goal_target ? ynab.milliunitsToAmount(category.goal_target) : null, goal_target_month: category.goal_target_month || null, goal_percentage_complete: Math.round(goalPercentageComplete), note: category.note || null, original_category_group_id: category.original_category_group_id || null }; }); return { id: group.id, name: group.name, categories: categories, categories_count: categories.length, total_budgeted: categories.reduce((sum, cat) => sum + cat.budgeted, 0), total_activity: categories.reduce((sum, cat) => sum + cat.activity, 0), total_balance: categories.reduce((sum, cat) => sum + cat.balance, 0) }; }).filter(group => group !== null); // Remove null groups return { month: monthToUse, category_groups: transformedGroups, total_groups: transformedGroups.length, total_categories: transformedGroups.reduce((sum, group) => sum + group.categories_count, 0) }; }, 10); // Cache for 10 minutes } catch (error) { return errorHandler.formatForMCP(error); } }; export { toolDefinition, handler };