financeops-mcp 0.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 (75) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  3. package/.github/assets/banner.svg +104 -0
  4. package/.github/pull_request_template.md +25 -0
  5. package/CODE_OF_CONDUCT.md +40 -0
  6. package/CONTRIBUTING.md +71 -0
  7. package/LICENSE +21 -0
  8. package/README.md +390 -0
  9. package/SECURITY.md +30 -0
  10. package/dist/adapters/stripe.d.ts +15 -0
  11. package/dist/adapters/stripe.js +175 -0
  12. package/dist/adapters/types.d.ts +30 -0
  13. package/dist/adapters/types.js +2 -0
  14. package/dist/adapters/xero.d.ts +19 -0
  15. package/dist/adapters/xero.js +261 -0
  16. package/dist/analysis/anomaly.d.ts +12 -0
  17. package/dist/analysis/anomaly.js +103 -0
  18. package/dist/analysis/cashflow.d.ts +10 -0
  19. package/dist/analysis/cashflow.js +134 -0
  20. package/dist/analysis/pnl.d.ts +8 -0
  21. package/dist/analysis/pnl.js +56 -0
  22. package/dist/analysis/reconciliation.d.ts +14 -0
  23. package/dist/analysis/reconciliation.js +98 -0
  24. package/dist/analysis/tax.d.ts +11 -0
  25. package/dist/analysis/tax.js +81 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.js +565 -0
  28. package/dist/lib/audit.d.ts +17 -0
  29. package/dist/lib/audit.js +70 -0
  30. package/dist/lib/providers.d.ts +6 -0
  31. package/dist/lib/providers.js +25 -0
  32. package/dist/premium/gate.d.ts +12 -0
  33. package/dist/premium/gate.js +22 -0
  34. package/dist/types.d.ts +138 -0
  35. package/dist/types.js +7 -0
  36. package/mcpize.yaml +10 -0
  37. package/package.json +35 -0
  38. package/src/adapters/stripe.ts +190 -0
  39. package/src/adapters/types.ts +34 -0
  40. package/src/adapters/xero.ts +317 -0
  41. package/src/analysis/anomaly.ts +119 -0
  42. package/src/analysis/cashflow.ts +158 -0
  43. package/src/analysis/pnl.ts +80 -0
  44. package/src/analysis/reconciliation.ts +117 -0
  45. package/src/analysis/tax.ts +98 -0
  46. package/src/index.ts +649 -0
  47. package/src/lib/audit.ts +92 -0
  48. package/src/lib/providers.ts +29 -0
  49. package/src/premium/gate.ts +24 -0
  50. package/src/types.ts +153 -0
  51. package/tests/adapters/stripe.test.ts +150 -0
  52. package/tests/adapters/xero.test.ts +188 -0
  53. package/tests/analysis/anomaly.test.ts +137 -0
  54. package/tests/analysis/cashflow.test.ts +112 -0
  55. package/tests/analysis/pnl.test.ts +95 -0
  56. package/tests/analysis/reconciliation.test.ts +121 -0
  57. package/tests/analysis/tax.test.ts +163 -0
  58. package/tests/helpers/mock-data.ts +135 -0
  59. package/tests/lib/audit.test.ts +89 -0
  60. package/tests/lib/providers.test.ts +129 -0
  61. package/tests/premium/cash_flow_forecast.test.ts +157 -0
  62. package/tests/premium/detect_anomalies.test.ts +189 -0
  63. package/tests/premium/gate.test.ts +59 -0
  64. package/tests/premium/generate_pnl.test.ts +155 -0
  65. package/tests/premium/multi_currency_report.test.ts +141 -0
  66. package/tests/premium/reconcile.test.ts +174 -0
  67. package/tests/premium/tax_summary.test.ts +166 -0
  68. package/tests/tools/expense_tracker.test.ts +181 -0
  69. package/tests/tools/financial_health.test.ts +196 -0
  70. package/tests/tools/get_balances.test.ts +160 -0
  71. package/tests/tools/list_invoices.test.ts +191 -0
  72. package/tests/tools/list_transactions.test.ts +210 -0
  73. package/tests/tools/revenue_summary.test.ts +188 -0
  74. package/tsconfig.json +20 -0
  75. package/vitest.config.ts +9 -0
@@ -0,0 +1,98 @@
1
+ const DATE_TOLERANCE_DAYS = 2;
2
+ /**
3
+ * Levenshtein distance between two strings.
4
+ */
5
+ function levenshtein(a, b) {
6
+ const m = a.length;
7
+ const n = b.length;
8
+ const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)));
9
+ for (let i = 1; i <= m; i++) {
10
+ for (let j = 1; j <= n; j++) {
11
+ if (a[i - 1] === b[j - 1]) {
12
+ dp[i][j] = dp[i - 1][j - 1];
13
+ }
14
+ else {
15
+ dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
16
+ }
17
+ }
18
+ }
19
+ return dp[m][n];
20
+ }
21
+ /**
22
+ * Normalized Levenshtein distance: 0 = identical, 1 = completely different.
23
+ */
24
+ function normalizedLevenshtein(a, b) {
25
+ if (a.length === 0 && b.length === 0)
26
+ return 0;
27
+ const maxLen = Math.max(a.length, b.length);
28
+ if (maxLen === 0)
29
+ return 0;
30
+ return levenshtein(a, b) / maxLen;
31
+ }
32
+ /**
33
+ * Reconcile Stripe transactions against Xero transactions.
34
+ *
35
+ * Matching criteria:
36
+ * 1. Exact amount match (in cents)
37
+ * 2. Date within ±2 days
38
+ * 3. HARD GATE: description similarity >= 20% (Critical Fix 4)
39
+ * Prevents false positives like "AWS hosting $50" matching "Employee lunch $50"
40
+ *
41
+ * Confidence: weighted score (40% date proximity + 60% description similarity).
42
+ */
43
+ export function reconcile(stripeTransactions, xeroTransactions) {
44
+ const matchedStripeIds = new Set();
45
+ const matchedXeroIds = new Set();
46
+ const matched = [];
47
+ for (const st of stripeTransactions) {
48
+ const stDate = new Date(st.date).getTime();
49
+ let bestMatch = null;
50
+ for (const xt of xeroTransactions) {
51
+ if (matchedXeroIds.has(xt.id))
52
+ continue;
53
+ // 1. Exact amount match required
54
+ if (st.amount !== xt.amount)
55
+ continue;
56
+ // 2. Date tolerance check
57
+ const xtDate = new Date(xt.date).getTime();
58
+ const daysDiff = Math.abs(stDate - xtDate) / (1000 * 60 * 60 * 24);
59
+ if (daysDiff > DATE_TOLERANCE_DAYS)
60
+ continue;
61
+ // 3. HARD GATE: description similarity (Critical Fix 4)
62
+ const descSimilarity = 1 -
63
+ normalizedLevenshtein(st.description.toLowerCase(), xt.description.toLowerCase());
64
+ if (descSimilarity < 0.2)
65
+ continue;
66
+ // Confidence score
67
+ const dateScore = 1 - daysDiff / DATE_TOLERANCE_DAYS;
68
+ const confidence = dateScore * 0.4 + descSimilarity * 0.6;
69
+ if (!bestMatch || confidence > bestMatch.confidence) {
70
+ bestMatch = { xero: xt, confidence };
71
+ }
72
+ }
73
+ if (bestMatch) {
74
+ matchedStripeIds.add(st.id);
75
+ matchedXeroIds.add(bestMatch.xero.id);
76
+ matched.push({
77
+ stripe: st,
78
+ xero: bestMatch.xero,
79
+ confidence: Math.round(bestMatch.confidence * 1000) / 1000,
80
+ });
81
+ }
82
+ }
83
+ const unmatchedStripe = stripeTransactions.filter((t) => !matchedStripeIds.has(t.id));
84
+ const unmatchedXero = xeroTransactions.filter((t) => !matchedXeroIds.has(t.id));
85
+ return {
86
+ matched,
87
+ unmatched_stripe: unmatchedStripe,
88
+ unmatched_xero: unmatchedXero,
89
+ summary: {
90
+ total_stripe: stripeTransactions.length,
91
+ total_xero: xeroTransactions.length,
92
+ matched_count: matched.length,
93
+ unmatched_stripe_count: unmatchedStripe.length,
94
+ unmatched_xero_count: unmatchedXero.length,
95
+ },
96
+ };
97
+ }
98
+ //# sourceMappingURL=reconciliation.js.map
@@ -0,0 +1,11 @@
1
+ import type { Transaction, TaxSummary } from '../types.js';
2
+ /**
3
+ * Calculate VAT/sales tax summary from transactions.
4
+ * Input amounts are in cents; output is in major currency units.
5
+ *
6
+ * This is a simplified v1 calculation. For production:
7
+ * - Use actual tax metadata from the payment processor
8
+ * - Apply jurisdiction-specific rules
9
+ */
10
+ export declare function calculateTax(transactions: Transaction[], period: 'quarter' | 'year', jurisdiction?: string): TaxSummary;
11
+ //# sourceMappingURL=tax.d.ts.map
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Default VAT/tax rates by jurisdiction.
3
+ * Key = jurisdiction code, value = array of applicable rates.
4
+ */
5
+ const TAX_RATES_BY_JURISDICTION = {
6
+ EU: [0.0, 0.05, 0.12, 0.2, 0.21, 0.25],
7
+ NL: [0.0, 0.09, 0.21],
8
+ DE: [0.0, 0.07, 0.19],
9
+ FR: [0.0, 0.055, 0.1, 0.2],
10
+ GB: [0.0, 0.05, 0.2],
11
+ US: [0.0, 0.05, 0.07, 0.08, 0.1],
12
+ DEFAULT: [0.0, 0.1, 0.2],
13
+ };
14
+ const STANDARD_RATE_BY_JURISDICTION = {
15
+ NL: 0.21,
16
+ DE: 0.19,
17
+ FR: 0.2,
18
+ GB: 0.2,
19
+ US: 0.08,
20
+ EU: 0.21,
21
+ DEFAULT: 0.2,
22
+ };
23
+ /**
24
+ * Calculate VAT/sales tax summary from transactions.
25
+ * Input amounts are in cents; output is in major currency units.
26
+ *
27
+ * This is a simplified v1 calculation. For production:
28
+ * - Use actual tax metadata from the payment processor
29
+ * - Apply jurisdiction-specific rules
30
+ */
31
+ export function calculateTax(transactions, period, jurisdiction) {
32
+ const provider = transactions.length > 0 ? transactions[0].provider : 'stripe';
33
+ const jur = (jurisdiction ?? 'DEFAULT').toUpperCase();
34
+ // Standard rate for the jurisdiction
35
+ const standardRate = STANDARD_RATE_BY_JURISDICTION[jur] ?? STANDARD_RATE_BY_JURISDICTION['DEFAULT'];
36
+ // Income transactions are taxable
37
+ const taxableTransactions = transactions.filter((t) => t.type === 'income');
38
+ // Group by assumed tax rate (in v1, we assume all income at standard rate unless metadata says otherwise)
39
+ const byRate = {};
40
+ for (const t of taxableTransactions) {
41
+ // Try to get tax rate from metadata
42
+ const rateStr = t.metadata?.tax_rate;
43
+ const rate = rateStr ? parseFloat(rateStr) : standardRate;
44
+ // Tax-exclusive: taxable amount = amount / (1 + rate), tax = amount - taxable
45
+ const taxableAmountCents = t.amount / (1 + rate);
46
+ const taxAmountCents = t.amount - taxableAmountCents;
47
+ if (!byRate[rate])
48
+ byRate[rate] = { taxable: 0, tax: 0 };
49
+ byRate[rate].taxable += taxableAmountCents;
50
+ byRate[rate].tax += taxAmountCents;
51
+ }
52
+ const byRateResult = Object.entries(byRate).map(([rateStr, totals]) => {
53
+ const rate = parseFloat(rateStr);
54
+ return {
55
+ rate,
56
+ rate_label: rate === 0 ? 'Exempt' : `${(rate * 100).toFixed(0)}% VAT`,
57
+ taxable_amount: Math.round(totals.taxable) / 100,
58
+ tax_amount: Math.round(totals.tax) / 100,
59
+ };
60
+ });
61
+ const totalTaxableCents = Object.values(byRate).reduce((sum, r) => sum + r.taxable, 0);
62
+ const totalTaxCents = Object.values(byRate).reduce((sum, r) => sum + r.tax, 0);
63
+ // Filing period label
64
+ const now = new Date();
65
+ const year = now.getFullYear();
66
+ const quarter = Math.ceil((now.getMonth() + 1) / 3);
67
+ const filingPeriod = period === 'year'
68
+ ? `${year}`
69
+ : `Q${quarter} ${year}`;
70
+ return {
71
+ provider,
72
+ period,
73
+ jurisdiction: jur !== 'DEFAULT' ? jur : undefined,
74
+ total_taxable_amount: Math.round(totalTaxableCents) / 100,
75
+ total_tax_collected: Math.round(totalTaxCents) / 100,
76
+ total_tax_owed: Math.round(totalTaxCents) / 100, // In v1, owed = collected
77
+ by_rate: byRateResult,
78
+ filing_period: filingPeriod,
79
+ };
80
+ }
81
+ //# sourceMappingURL=tax.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map