@mintline/mcp 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +199 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -77,6 +77,32 @@ function createClient(apiKey2) {
|
|
|
77
77
|
},
|
|
78
78
|
async rejectMatch(id, reason) {
|
|
79
79
|
return request("POST", `/api/matches/${id}/reject`, { reason });
|
|
80
|
+
},
|
|
81
|
+
// Analytics
|
|
82
|
+
async getSpendingSummary(params = {}) {
|
|
83
|
+
const query = new URLSearchParams();
|
|
84
|
+
if (params.groupBy) query.set("groupBy", params.groupBy);
|
|
85
|
+
if (params.dateFrom) query.set("dateFrom", params.dateFrom);
|
|
86
|
+
if (params.dateTo) query.set("dateTo", params.dateTo);
|
|
87
|
+
if (params.vendorId) query.set("vendorId", params.vendorId);
|
|
88
|
+
if (params.limit) query.set("limit", params.limit);
|
|
89
|
+
const qs = query.toString();
|
|
90
|
+
return request("GET", `/api/analytics/spending${qs ? `?${qs}` : ""}`);
|
|
91
|
+
},
|
|
92
|
+
async getTopVendors(params = {}) {
|
|
93
|
+
const query = new URLSearchParams();
|
|
94
|
+
if (params.limit) query.set("limit", params.limit);
|
|
95
|
+
const qs = query.toString();
|
|
96
|
+
return request("GET", `/api/analytics/top-vendors${qs ? `?${qs}` : ""}`);
|
|
97
|
+
},
|
|
98
|
+
async getSpendingTrends(params = {}) {
|
|
99
|
+
const query = new URLSearchParams();
|
|
100
|
+
if (params.months) query.set("months", params.months);
|
|
101
|
+
const qs = query.toString();
|
|
102
|
+
return request("GET", `/api/analytics/trends${qs ? `?${qs}` : ""}`);
|
|
103
|
+
},
|
|
104
|
+
async getUnmatchedSummary() {
|
|
105
|
+
return request("GET", `/api/analytics/unmatched`);
|
|
80
106
|
}
|
|
81
107
|
};
|
|
82
108
|
}
|
|
@@ -221,6 +247,70 @@ var tools = [
|
|
|
221
247
|
},
|
|
222
248
|
required: ["id"]
|
|
223
249
|
}
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: "spending_summary",
|
|
253
|
+
description: "Get spending summary with flexible grouping. Use this to answer questions like 'How much did I spend this month?' or 'What are my expenses by vendor?'",
|
|
254
|
+
inputSchema: {
|
|
255
|
+
type: "object",
|
|
256
|
+
properties: {
|
|
257
|
+
groupBy: {
|
|
258
|
+
type: "string",
|
|
259
|
+
enum: ["total", "vendor", "month", "week", "day"],
|
|
260
|
+
description: "How to group the spending data (default: total)"
|
|
261
|
+
},
|
|
262
|
+
dateFrom: {
|
|
263
|
+
type: "string",
|
|
264
|
+
description: "Start date (YYYY-MM-DD)"
|
|
265
|
+
},
|
|
266
|
+
dateTo: {
|
|
267
|
+
type: "string",
|
|
268
|
+
description: "End date (YYYY-MM-DD)"
|
|
269
|
+
},
|
|
270
|
+
vendorId: {
|
|
271
|
+
type: "string",
|
|
272
|
+
description: "Filter by specific vendor ID"
|
|
273
|
+
},
|
|
274
|
+
limit: {
|
|
275
|
+
type: "number",
|
|
276
|
+
description: "Max results for grouped queries (default 20)"
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: "top_vendors",
|
|
283
|
+
description: "Get top vendors ranked by total spending. Use this to answer 'Who are my biggest vendors?' or 'Where do I spend the most?'",
|
|
284
|
+
inputSchema: {
|
|
285
|
+
type: "object",
|
|
286
|
+
properties: {
|
|
287
|
+
limit: {
|
|
288
|
+
type: "number",
|
|
289
|
+
description: "Number of vendors to return (default 10)"
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: "spending_trends",
|
|
296
|
+
description: "Get monthly spending trends over time. Use this to answer 'How has my spending changed?' or 'Show me spending trends'",
|
|
297
|
+
inputSchema: {
|
|
298
|
+
type: "object",
|
|
299
|
+
properties: {
|
|
300
|
+
months: {
|
|
301
|
+
type: "number",
|
|
302
|
+
description: "Number of months to include (default 6)"
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: "unmatched_summary",
|
|
309
|
+
description: "Get a summary of items needing attention: unmatched receipts, transactions without receipts, and proposed matches to review. Use this for 'What needs my attention?' or 'Do I have unmatched expenses?'",
|
|
310
|
+
inputSchema: {
|
|
311
|
+
type: "object",
|
|
312
|
+
properties: {}
|
|
313
|
+
}
|
|
224
314
|
}
|
|
225
315
|
];
|
|
226
316
|
async function handleTool(client2, name, args) {
|
|
@@ -271,6 +361,32 @@ async function handleTool(client2, name, args) {
|
|
|
271
361
|
await client2.rejectMatch(args.id, args.reason);
|
|
272
362
|
return `Match ${args.id} rejected.${args.reason ? ` Reason: ${args.reason}` : ""}`;
|
|
273
363
|
}
|
|
364
|
+
case "spending_summary": {
|
|
365
|
+
const result = await client2.getSpendingSummary({
|
|
366
|
+
groupBy: args.groupBy || "total",
|
|
367
|
+
dateFrom: args.dateFrom,
|
|
368
|
+
dateTo: args.dateTo,
|
|
369
|
+
vendorId: args.vendorId,
|
|
370
|
+
limit: args.limit || 20
|
|
371
|
+
});
|
|
372
|
+
return formatSpendingSummary(result.data, args.groupBy || "total");
|
|
373
|
+
}
|
|
374
|
+
case "top_vendors": {
|
|
375
|
+
const result = await client2.getTopVendors({
|
|
376
|
+
limit: args.limit || 10
|
|
377
|
+
});
|
|
378
|
+
return formatTopVendors(result.data.vendors);
|
|
379
|
+
}
|
|
380
|
+
case "spending_trends": {
|
|
381
|
+
const result = await client2.getSpendingTrends({
|
|
382
|
+
months: args.months || 6
|
|
383
|
+
});
|
|
384
|
+
return formatSpendingTrends(result.data.trends);
|
|
385
|
+
}
|
|
386
|
+
case "unmatched_summary": {
|
|
387
|
+
const result = await client2.getUnmatchedSummary();
|
|
388
|
+
return formatUnmatchedSummary(result.data);
|
|
389
|
+
}
|
|
274
390
|
default:
|
|
275
391
|
throw new Error(`Unknown tool: ${name}`);
|
|
276
392
|
}
|
|
@@ -335,6 +451,89 @@ function formatMatches(matches) {
|
|
|
335
451
|
(m) => `\u2022 ${m.id}: Receipt ${m.receiptId} \u2194 Transaction ${m.transactionId} (${Math.round(m.confidenceScore * 100)}% confidence) [${m.status}]`
|
|
336
452
|
).join("\n");
|
|
337
453
|
}
|
|
454
|
+
function formatCurrency(amount) {
|
|
455
|
+
return new Intl.NumberFormat("en-US", {
|
|
456
|
+
style: "currency",
|
|
457
|
+
currency: "USD"
|
|
458
|
+
}).format(amount);
|
|
459
|
+
}
|
|
460
|
+
function formatSpendingSummary(data, groupBy) {
|
|
461
|
+
if (groupBy === "total" && data.summary) {
|
|
462
|
+
const s = data.summary;
|
|
463
|
+
let text2 = `Spending Summary (${data.period.from} to ${data.period.to})
|
|
464
|
+
`;
|
|
465
|
+
text2 += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
466
|
+
`;
|
|
467
|
+
text2 += `Total Spent: ${formatCurrency(s.totalAmount)}
|
|
468
|
+
`;
|
|
469
|
+
text2 += `Receipt Count: ${s.receiptCount}
|
|
470
|
+
`;
|
|
471
|
+
text2 += `Average: ${formatCurrency(s.averageAmount)}
|
|
472
|
+
`;
|
|
473
|
+
text2 += `Min: ${formatCurrency(s.minAmount)} | Max: ${formatCurrency(s.maxAmount)}`;
|
|
474
|
+
return text2;
|
|
475
|
+
}
|
|
476
|
+
if (!data.data?.length) return "No spending data found for this period.";
|
|
477
|
+
let text = `Spending by ${groupBy} (${data.period.from} to ${data.period.to})
|
|
478
|
+
`;
|
|
479
|
+
text += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
480
|
+
`;
|
|
481
|
+
if (groupBy === "vendor") {
|
|
482
|
+
data.data.forEach((item, i) => {
|
|
483
|
+
text += `${i + 1}. ${item.vendorName}: ${formatCurrency(item.totalAmount)} (${item.receiptCount} receipts)
|
|
484
|
+
`;
|
|
485
|
+
});
|
|
486
|
+
} else {
|
|
487
|
+
data.data.forEach((item) => {
|
|
488
|
+
text += `\u2022 ${item.period}: ${formatCurrency(item.totalAmount)} (${item.receiptCount} receipts)
|
|
489
|
+
`;
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
return text;
|
|
493
|
+
}
|
|
494
|
+
function formatTopVendors(vendors) {
|
|
495
|
+
if (!vendors?.length) return "No vendor data found.";
|
|
496
|
+
let text = `Top Vendors by Spending
|
|
497
|
+
`;
|
|
498
|
+
text += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
499
|
+
`;
|
|
500
|
+
vendors.forEach((v, i) => {
|
|
501
|
+
text += `${i + 1}. ${v.name}: ${formatCurrency(v.total)} (${v.count} receipts)
|
|
502
|
+
`;
|
|
503
|
+
});
|
|
504
|
+
return text;
|
|
505
|
+
}
|
|
506
|
+
function formatSpendingTrends(trends) {
|
|
507
|
+
if (!trends?.length) return "No trend data found.";
|
|
508
|
+
let text = `Monthly Spending Trends
|
|
509
|
+
`;
|
|
510
|
+
text += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
511
|
+
`;
|
|
512
|
+
trends.forEach((t) => {
|
|
513
|
+
const bar = "\u2588".repeat(Math.min(20, Math.round(t.total / 100)));
|
|
514
|
+
text += `${t.month}: ${formatCurrency(t.total)} ${bar}
|
|
515
|
+
`;
|
|
516
|
+
});
|
|
517
|
+
return text;
|
|
518
|
+
}
|
|
519
|
+
function formatUnmatchedSummary(data) {
|
|
520
|
+
let text = `Action Items Summary
|
|
521
|
+
`;
|
|
522
|
+
text += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
523
|
+
|
|
524
|
+
`;
|
|
525
|
+
text += `\u{1F4CB} Proposed Matches to Review: ${data.proposedMatches.count}
|
|
526
|
+
`;
|
|
527
|
+
text += `\u{1F4C4} Unmatched Receipts: ${data.unmatchedReceipts.count} (${formatCurrency(data.unmatchedReceipts.totalAmount)})
|
|
528
|
+
`;
|
|
529
|
+
text += `\u{1F4B3} Unmatched Transactions: ${data.unmatchedTransactions.count} (${formatCurrency(data.unmatchedTransactions.totalAmount)})
|
|
530
|
+
`;
|
|
531
|
+
text += `\u26A0\uFE0F Large Transactions (>${formatCurrency(data.largeUnmatchedTransactions.threshold)}) without receipts: ${data.largeUnmatchedTransactions.count}
|
|
532
|
+
|
|
533
|
+
`;
|
|
534
|
+
text += `${data.actionItems.message}`;
|
|
535
|
+
return text;
|
|
536
|
+
}
|
|
338
537
|
|
|
339
538
|
// src/index.js
|
|
340
539
|
var apiKey = process.env.MINTLINE_API_KEY;
|