@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.
Files changed (2) hide show
  1. package/dist/index.js +199 -0
  2. 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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mintline/mcp",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "MCP server for Mintline - connect AI assistants to your receipts and transactions",
5
5
  "main": "dist/index.js",
6
6
  "bin": {