feat(mcp): implement all MCP tools (transactions, budgets, categories, receipts, merchants, accounts, dashboard)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ModelContextProtocol.Server;
|
||||
using MoneyMap.Data;
|
||||
|
||||
namespace MoneyMap.Mcp.Tools;
|
||||
|
||||
[McpServerToolType]
|
||||
public static class MerchantTools
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
|
||||
|
||||
[McpServerTool(Name = "list_merchants"), Description("List all merchants with transaction counts and category mapping info.")]
|
||||
public static async Task<string> ListMerchants(
|
||||
[Description("Filter merchants by name (contains)")] string? query = null,
|
||||
MoneyMapContext db = default!)
|
||||
{
|
||||
var q = db.Merchants
|
||||
.Include(m => m.Transactions)
|
||||
.Include(m => m.CategoryMappings)
|
||||
.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query))
|
||||
q = q.Where(m => m.Name.Contains(query));
|
||||
|
||||
var merchants = await q
|
||||
.OrderBy(m => m.Name)
|
||||
.Select(m => new
|
||||
{
|
||||
m.Id,
|
||||
m.Name,
|
||||
TransactionCount = m.Transactions.Count,
|
||||
MappingCount = m.CategoryMappings.Count,
|
||||
Categories = m.CategoryMappings.Select(cm => cm.Category).Distinct().ToList()
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
return JsonSerializer.Serialize(new { Count = merchants.Count, Merchants = merchants }, JsonOptions);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "merge_merchants"), Description("Merge duplicate merchants. Reassigns all transactions and category mappings from source to target, then deletes source.")]
|
||||
public static async Task<string> MergeMerchants(
|
||||
[Description("Merchant ID to merge FROM (will be deleted)")] int sourceMerchantId,
|
||||
[Description("Merchant ID to merge INTO (will be kept)")] int targetMerchantId,
|
||||
MoneyMapContext db = default!)
|
||||
{
|
||||
if (sourceMerchantId == targetMerchantId)
|
||||
return "Source and target merchant cannot be the same";
|
||||
|
||||
var source = await db.Merchants.FindAsync(sourceMerchantId);
|
||||
var target = await db.Merchants.FindAsync(targetMerchantId);
|
||||
|
||||
if (source == null)
|
||||
return $"Source merchant {sourceMerchantId} not found";
|
||||
if (target == null)
|
||||
return $"Target merchant {targetMerchantId} not found";
|
||||
|
||||
var transactions = await db.Transactions
|
||||
.Where(t => t.MerchantId == sourceMerchantId)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var t in transactions)
|
||||
t.MerchantId = targetMerchantId;
|
||||
|
||||
var sourceMappings = await db.CategoryMappings
|
||||
.Where(cm => cm.MerchantId == sourceMerchantId)
|
||||
.ToListAsync();
|
||||
|
||||
var targetMappingPatterns = await db.CategoryMappings
|
||||
.Where(cm => cm.MerchantId == targetMerchantId)
|
||||
.Select(cm => cm.Pattern)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var mapping in sourceMappings)
|
||||
{
|
||||
if (targetMappingPatterns.Contains(mapping.Pattern))
|
||||
db.CategoryMappings.Remove(mapping);
|
||||
else
|
||||
mapping.MerchantId = targetMerchantId;
|
||||
}
|
||||
|
||||
db.Merchants.Remove(source);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return JsonSerializer.Serialize(new
|
||||
{
|
||||
Merged = true,
|
||||
Source = new { source.Id, source.Name },
|
||||
Target = new { target.Id, target.Name },
|
||||
TransactionsReassigned = transactions.Count,
|
||||
MappingsReassigned = sourceMappings.Count
|
||||
}, JsonOptions);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user