using System.ComponentModel; using System.Text.Json; using Microsoft.EntityFrameworkCore; using ModelContextProtocol.Server; using MoneyMap.Data; using MoneyMap.Services; namespace MoneyMap.Mcp.Tools; [McpServerToolType] public static class CategoryTools { private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; [McpServerTool(Name = "list_categories"), Description("List all categories with transaction counts.")] public static async Task ListCategories( MoneyMapContext db = default!) { var categories = await db.Transactions .Where(t => t.Category != null && t.Category != "") .GroupBy(t => t.Category!) .Select(g => new { Category = g.Key, Count = g.Count(), TotalSpent = g.Where(t => t.Amount < 0).Sum(t => Math.Abs(t.Amount)) }) .OrderByDescending(x => x.Count) .ToListAsync(); var uncategorized = await db.Transactions .CountAsync(t => t.Category == null || t.Category == ""); return JsonSerializer.Serialize(new { Categories = categories, UncategorizedCount = uncategorized }, JsonOptions); } [McpServerTool(Name = "get_category_mappings"), Description("Get auto-categorization pattern rules (CategoryMappings).")] public static async Task GetCategoryMappings( [Description("Filter mappings to a specific category")] string? category = null, ITransactionCategorizer categorizer = default!) { var mappings = await categorizer.GetAllMappingsAsync(); if (!string.IsNullOrWhiteSpace(category)) mappings = mappings.Where(m => m.Category.Equals(category, StringComparison.OrdinalIgnoreCase)).ToList(); var result = mappings.Select(m => new { m.Id, m.Pattern, m.Category, m.MerchantId, m.Priority }).OrderBy(m => m.Category).ThenByDescending(m => m.Priority).ToList(); return JsonSerializer.Serialize(result, JsonOptions); } [McpServerTool(Name = "add_category_mapping"), Description("Add a new auto-categorization rule that maps transaction name patterns to categories.")] public static async Task AddCategoryMapping( [Description("Pattern to match in transaction name (case-insensitive)")] string pattern, [Description("Category to assign when pattern matches")] string category, [Description("Merchant name to assign (creates if new)")] string? merchantName = null, [Description("Priority (higher = checked first, default 0)")] int priority = 0, MoneyMapContext db = default!, IMerchantService merchantService = default!) { int? merchantId = null; if (!string.IsNullOrWhiteSpace(merchantName)) merchantId = await merchantService.GetOrCreateIdAsync(merchantName); var mapping = new MoneyMap.Models.CategoryMapping { Pattern = pattern, Category = category, MerchantId = merchantId, Priority = priority }; db.CategoryMappings.Add(mapping); await db.SaveChangesAsync(); return JsonSerializer.Serialize(new { Created = true, mapping.Id, mapping.Pattern, mapping.Category, Merchant = merchantName, mapping.Priority }, JsonOptions); } }