paisa-mcp 0.0.2 → 0.0.4
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 +248 -8
- package/dist/index.js.map +7 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42581,21 +42581,23 @@ function registerGoalTools(server, client) {
|
|
|
42581
42581
|
inputSchema: {
|
|
42582
42582
|
type: exports_external.enum(["goal", "emi", "sinking_fund"]),
|
|
42583
42583
|
id: exports_external.string().optional().describe("[goal] Goal UUID — omit to create, pass to update"),
|
|
42584
|
+
goalType: exports_external.enum(["emergency_fund", "travel", "car", "house", "education", "retirement", "custom"]).optional().describe("[goal] Goal category — required when creating"),
|
|
42584
42585
|
name: exports_external.string().optional().describe("[goal, emi, sinking_fund] Name"),
|
|
42585
42586
|
targetAmount: exports_external.string().optional().describe("[goal, sinking_fund] Target amount"),
|
|
42586
42587
|
targetDate: exports_external.string().optional().describe("[goal, sinking_fund] YYYY-MM-DD"),
|
|
42587
42588
|
currentAmount: exports_external.string().optional().describe("[goal, sinking_fund] Already saved"),
|
|
42589
|
+
monthlyContribution: exports_external.string().optional().describe("[goal] Monthly contribution"),
|
|
42590
|
+
owner: exports_external.string().optional().describe("[goal, emi] Owner"),
|
|
42588
42591
|
icon: exports_external.string().optional().describe("[goal] Icon"),
|
|
42589
42592
|
color: exports_external.string().optional().describe("[goal] Color"),
|
|
42593
|
+
notes: exports_external.string().optional().describe("[goal] Notes"),
|
|
42590
42594
|
totalAmount: exports_external.string().optional().describe("[emi] Total loan amount"),
|
|
42591
42595
|
emiAmount: exports_external.string().optional().describe("[emi] Monthly EMI amount"),
|
|
42592
42596
|
interestRate: exports_external.string().optional().describe("[emi] Interest rate (optional)"),
|
|
42593
42597
|
startDate: exports_external.string().optional().describe("[emi] YYYY-MM-DD"),
|
|
42594
42598
|
endDate: exports_external.string().optional().describe("[emi] YYYY-MM-DD last payment date"),
|
|
42595
42599
|
totalMonths: exports_external.number().optional().describe("[emi] Tenure in months"),
|
|
42596
|
-
paidMonths: exports_external.number().optional().describe("[emi] Months already paid")
|
|
42597
|
-
owner: exports_external.string().optional().describe("[emi] Owner"),
|
|
42598
|
-
monthlyContribution: exports_external.string().optional().describe("[sinking_fund] Monthly contribution (optional)")
|
|
42600
|
+
paidMonths: exports_external.number().optional().describe("[emi] Months already paid")
|
|
42599
42601
|
}
|
|
42600
42602
|
}, async ({ type, ...fields }) => {
|
|
42601
42603
|
const require2 = (keys) => {
|
|
@@ -42605,10 +42607,10 @@ function registerGoalTools(server, client) {
|
|
|
42605
42607
|
}
|
|
42606
42608
|
};
|
|
42607
42609
|
if (type === "goal") {
|
|
42608
|
-
const { id, name: name2, targetAmount: targetAmount2, targetDate: targetDate2, currentAmount: currentAmount2, icon, color } = fields;
|
|
42609
|
-
const body2 = { name: name2, targetAmount: targetAmount2, targetDate: targetDate2, currentAmount: currentAmount2, icon, color };
|
|
42610
|
+
const { id, goalType, name: name2, targetAmount: targetAmount2, targetDate: targetDate2, currentAmount: currentAmount2, monthlyContribution: monthlyContribution2, owner, icon, color, notes } = fields;
|
|
42611
|
+
const body2 = { type: goalType, name: name2, targetAmount: targetAmount2, targetDate: targetDate2, currentAmount: currentAmount2, monthlyContribution: monthlyContribution2, owner, icon, color, notes };
|
|
42610
42612
|
if (!id)
|
|
42611
|
-
require2(["name", "targetAmount"]);
|
|
42613
|
+
require2(["name", "targetAmount", "goalType"]);
|
|
42612
42614
|
const data2 = id ? await client.patch(`/api/goals/${id}`, body2) : await client.post("/api/goals", body2);
|
|
42613
42615
|
return { content: [{ type: "text", text: JSON.stringify(data2) }] };
|
|
42614
42616
|
}
|
|
@@ -42635,6 +42637,28 @@ function registerGoalTools(server, client) {
|
|
|
42635
42637
|
const data = await client.post("/api/goals/sinking-funds", body);
|
|
42636
42638
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
42637
42639
|
});
|
|
42640
|
+
server.registerTool("upsert_insurance", {
|
|
42641
|
+
description: "Create or update an insurance policy. Omit id to create (name, type, provider, premiumAmount, premiumFrequency, startDate, owner required). Pass id to update.",
|
|
42642
|
+
inputSchema: {
|
|
42643
|
+
id: exports_external.string().optional().describe("Policy UUID — omit to create, pass to update"),
|
|
42644
|
+
name: exports_external.string().optional(),
|
|
42645
|
+
type: exports_external.enum(["term", "health", "vehicle", "home", "other"]).optional(),
|
|
42646
|
+
provider: exports_external.string().optional(),
|
|
42647
|
+
coverAmount: exports_external.string().optional(),
|
|
42648
|
+
premiumAmount: exports_external.string().optional(),
|
|
42649
|
+
premiumFrequency: exports_external.string().optional().describe('e.g. "yearly", "monthly"'),
|
|
42650
|
+
startDate: exports_external.string().optional().describe("YYYY-MM-DD"),
|
|
42651
|
+
endDate: exports_external.string().optional().describe("YYYY-MM-DD"),
|
|
42652
|
+
renewalDate: exports_external.string().optional().describe("YYYY-MM-DD"),
|
|
42653
|
+
policyNumber: exports_external.string().optional(),
|
|
42654
|
+
owner: exports_external.string().optional(),
|
|
42655
|
+
isActive: exports_external.boolean().optional(),
|
|
42656
|
+
notes: exports_external.string().optional()
|
|
42657
|
+
}
|
|
42658
|
+
}, async ({ id, ...body }) => {
|
|
42659
|
+
const data = id ? await client.patch(`/api/goals/insurance/${id}`, body) : await client.post("/api/goals/insurance", body);
|
|
42660
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
42661
|
+
});
|
|
42638
42662
|
server.registerTool("contribute_to_goal", {
|
|
42639
42663
|
description: "Add a contribution to a savings goal.",
|
|
42640
42664
|
inputSchema: {
|
|
@@ -43254,6 +43278,7 @@ var MERCHANT_ALIASES = [
|
|
|
43254
43278
|
["LivPure", ["livpure smart homes", "livpure"]],
|
|
43255
43279
|
["HundredX", ["hundredx school", "hundredx"]]
|
|
43256
43280
|
];
|
|
43281
|
+
var GATEWAY_PREFIX = /^(paytm|payu|razorpay|cashfree|billdesk|easebuzz|rbl|pyu)\*/i;
|
|
43257
43282
|
var aliasIndex = [];
|
|
43258
43283
|
for (const [canonical, aliases] of MERCHANT_ALIASES) {
|
|
43259
43284
|
for (const alias of aliases) {
|
|
@@ -43261,6 +43286,24 @@ for (const [canonical, aliases] of MERCHANT_ALIASES) {
|
|
|
43261
43286
|
}
|
|
43262
43287
|
}
|
|
43263
43288
|
aliasIndex.sort((a, b) => b.alias.length - a.alias.length);
|
|
43289
|
+
function resolveAlias(name) {
|
|
43290
|
+
let lower = name.toLowerCase();
|
|
43291
|
+
const gwMatch = lower.match(GATEWAY_PREFIX);
|
|
43292
|
+
if (gwMatch) {
|
|
43293
|
+
lower = lower.slice(gwMatch[0].length).trim();
|
|
43294
|
+
}
|
|
43295
|
+
for (const { alias, canonical } of aliasIndex) {
|
|
43296
|
+
if (alias.length <= 4) {
|
|
43297
|
+
const re = new RegExp(`\\b${alias}\\b`);
|
|
43298
|
+
if (re.test(lower))
|
|
43299
|
+
return canonical;
|
|
43300
|
+
} else {
|
|
43301
|
+
if (lower.includes(alias))
|
|
43302
|
+
return canonical;
|
|
43303
|
+
}
|
|
43304
|
+
}
|
|
43305
|
+
return null;
|
|
43306
|
+
}
|
|
43264
43307
|
// ../../node_modules/.bun/fuzzball@2.2.3/node_modules/fuzzball/dist/esm/fuzzball.esm.min.js
|
|
43265
43308
|
var e;
|
|
43266
43309
|
var t = typeof globalThis != "undefined" ? globalThis : typeof window != "undefined" ? window : typeof global != "undefined" ? global : typeof self != "undefined" ? self : {};
|
|
@@ -44975,9 +45018,152 @@ var Yo = Ro.process_and_sort;
|
|
|
44975
45018
|
var ea = Ro.unique_tokens;
|
|
44976
45019
|
var ta = Ro.dedupe;
|
|
44977
45020
|
|
|
45021
|
+
// ../reconciliation/src/normalise.ts
|
|
45022
|
+
var CORPORATE_SUFFIXES = /\b(PRIVATE|PVT\.?|LIMITED|LTD\.?)\s*\w*/gi;
|
|
45023
|
+
function normaliseName(name) {
|
|
45024
|
+
return name.replace(CORPORATE_SUFFIXES, "").replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
|
|
45025
|
+
}
|
|
45026
|
+
|
|
44978
45027
|
// ../reconciliation/src/entity.ts
|
|
45028
|
+
var FUZZBALL_THRESHOLD = 92;
|
|
44979
45029
|
var _aliasReCache = new Map;
|
|
45030
|
+
function getAliasRegex(lower) {
|
|
45031
|
+
let re2 = _aliasReCache.get(lower);
|
|
45032
|
+
if (!re2) {
|
|
45033
|
+
const escaped = lower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
45034
|
+
re2 = new RegExp(`\\b${escaped}\\b`);
|
|
45035
|
+
_aliasReCache.set(lower, re2);
|
|
45036
|
+
}
|
|
45037
|
+
return re2;
|
|
45038
|
+
}
|
|
44980
45039
|
var _normCache = new WeakMap;
|
|
45040
|
+
function cachedNormaliseName(opt) {
|
|
45041
|
+
let cached2 = _normCache.get(opt);
|
|
45042
|
+
if (cached2 === undefined) {
|
|
45043
|
+
cached2 = normaliseName(opt.name);
|
|
45044
|
+
_normCache.set(opt, cached2);
|
|
45045
|
+
}
|
|
45046
|
+
return cached2;
|
|
45047
|
+
}
|
|
45048
|
+
function matchUserAlias(normalisedName, aliases) {
|
|
45049
|
+
const sorted = [...aliases].sort((a2, b2) => b2.pattern.length - a2.pattern.length);
|
|
45050
|
+
for (const alias of sorted) {
|
|
45051
|
+
const lower = alias.pattern.toLowerCase();
|
|
45052
|
+
if (lower.length <= 4) {
|
|
45053
|
+
if (getAliasRegex(lower).test(normalisedName))
|
|
45054
|
+
return alias;
|
|
45055
|
+
} else if (normalisedName.includes(lower)) {
|
|
45056
|
+
return alias;
|
|
45057
|
+
}
|
|
45058
|
+
}
|
|
45059
|
+
return null;
|
|
45060
|
+
}
|
|
45061
|
+
function fuzzMatch(normalisedName, options) {
|
|
45062
|
+
let best = null;
|
|
45063
|
+
for (const opt of options) {
|
|
45064
|
+
const candidate = cachedNormaliseName(opt);
|
|
45065
|
+
if (!candidate)
|
|
45066
|
+
continue;
|
|
45067
|
+
const score = Math.max(Fo(normalisedName, candidate), Bo(normalisedName, candidate));
|
|
45068
|
+
if (score >= FUZZBALL_THRESHOLD && (!best || score > best.score)) {
|
|
45069
|
+
best = { row: opt, score };
|
|
45070
|
+
}
|
|
45071
|
+
}
|
|
45072
|
+
return best?.row ?? null;
|
|
45073
|
+
}
|
|
45074
|
+
function resolveMerchant(normalisedName, ctx) {
|
|
45075
|
+
const canonical = resolveAlias(normalisedName);
|
|
45076
|
+
if (canonical) {
|
|
45077
|
+
const normCanonical = normaliseName(canonical);
|
|
45078
|
+
const match = ctx.globalMerchants.find((m2) => cachedNormaliseName(m2) === normCanonical);
|
|
45079
|
+
if (match)
|
|
45080
|
+
return { type: "merchant", merchantId: match.id };
|
|
45081
|
+
}
|
|
45082
|
+
const userAlias = matchUserAlias(normalisedName, ctx.userMerchantAliases);
|
|
45083
|
+
if (userAlias)
|
|
45084
|
+
return { type: "merchant", merchantId: userAlias.merchantId };
|
|
45085
|
+
const fuzzMerchant = fuzzMatch(normalisedName, [...ctx.globalMerchants, ...ctx.localMerchants]);
|
|
45086
|
+
if (fuzzMerchant)
|
|
45087
|
+
return { type: "merchant", merchantId: fuzzMerchant.id };
|
|
45088
|
+
return null;
|
|
45089
|
+
}
|
|
45090
|
+
function resolvePerson(normalisedName, ctx) {
|
|
45091
|
+
const userAlias = matchUserAlias(normalisedName, ctx.userPersonAliases);
|
|
45092
|
+
if (userAlias)
|
|
45093
|
+
return { type: "person", personId: userAlias.personId };
|
|
45094
|
+
const fuzzPerson = fuzzMatch(normalisedName, ctx.persons);
|
|
45095
|
+
if (fuzzPerson)
|
|
45096
|
+
return { type: "person", personId: fuzzPerson.id };
|
|
45097
|
+
return null;
|
|
45098
|
+
}
|
|
45099
|
+
function resolveEntity(name, hint, ctx) {
|
|
45100
|
+
const normalised = normaliseName(name);
|
|
45101
|
+
if (!normalised)
|
|
45102
|
+
return null;
|
|
45103
|
+
if (hint === "person") {
|
|
45104
|
+
return resolvePerson(normalised, ctx) ?? resolveMerchant(normalised, ctx) ?? null;
|
|
45105
|
+
}
|
|
45106
|
+
if (hint === "merchant") {
|
|
45107
|
+
return resolveMerchant(normalised, ctx) ?? null;
|
|
45108
|
+
}
|
|
45109
|
+
return resolveMerchant(normalised, ctx) ?? resolvePerson(normalised, ctx) ?? null;
|
|
45110
|
+
}
|
|
45111
|
+
|
|
45112
|
+
// ../reconciliation/src/categorize.ts
|
|
45113
|
+
function autoCategorizeFromDescriptions(ctx) {
|
|
45114
|
+
const resolveCtx = {
|
|
45115
|
+
globalMerchants: ctx.globalMerchants,
|
|
45116
|
+
localMerchants: ctx.localMerchants,
|
|
45117
|
+
persons: ctx.persons,
|
|
45118
|
+
userMerchantAliases: ctx.bag.merchantAliases,
|
|
45119
|
+
userPersonAliases: ctx.bag.personAliases
|
|
45120
|
+
};
|
|
45121
|
+
const groups = new Map;
|
|
45122
|
+
let unresolvedCount = 0;
|
|
45123
|
+
let totalAffected = 0;
|
|
45124
|
+
for (const row of ctx.rows) {
|
|
45125
|
+
if (row.isTransfer || row.isIgnored)
|
|
45126
|
+
continue;
|
|
45127
|
+
let entity = null;
|
|
45128
|
+
if (row.merchantId)
|
|
45129
|
+
entity = { type: "merchant", merchantId: row.merchantId };
|
|
45130
|
+
else if (row.personId)
|
|
45131
|
+
entity = { type: "person", personId: row.personId };
|
|
45132
|
+
else
|
|
45133
|
+
entity = resolveEntity(row.description, "unknown", resolveCtx);
|
|
45134
|
+
if (!entity) {
|
|
45135
|
+
unresolvedCount += 1;
|
|
45136
|
+
continue;
|
|
45137
|
+
}
|
|
45138
|
+
const rule = entityRule(entity, ctx.bag);
|
|
45139
|
+
if (row.categoryId && rule && rule.categoryId === row.categoryId)
|
|
45140
|
+
continue;
|
|
45141
|
+
const needsLink = !row.merchantId && !row.personId;
|
|
45142
|
+
const needsCategory = !row.categoryId && rule != null;
|
|
45143
|
+
if (!needsLink && !needsCategory)
|
|
45144
|
+
continue;
|
|
45145
|
+
const key = entityKey(entity);
|
|
45146
|
+
const existing = groups.get(key);
|
|
45147
|
+
const suggestion = existing ?? {
|
|
45148
|
+
entity,
|
|
45149
|
+
source: rule ? "category-rule" : "alias-only",
|
|
45150
|
+
categoryId: rule?.categoryId ?? null,
|
|
45151
|
+
transactionIds: []
|
|
45152
|
+
};
|
|
45153
|
+
suggestion.transactionIds.push(row.id);
|
|
45154
|
+
groups.set(key, suggestion);
|
|
45155
|
+
totalAffected += 1;
|
|
45156
|
+
}
|
|
45157
|
+
return { suggestions: Array.from(groups.values()), totalAffected, unresolvedCount };
|
|
45158
|
+
}
|
|
45159
|
+
function entityKey(e2) {
|
|
45160
|
+
return e2.type === "merchant" ? `m:${e2.merchantId}` : `p:${e2.personId}`;
|
|
45161
|
+
}
|
|
45162
|
+
function entityRule(e2, bag) {
|
|
45163
|
+
if (e2.type === "merchant")
|
|
45164
|
+
return bag.merchantRules[e2.merchantId] ?? null;
|
|
45165
|
+
return bag.personRules[e2.personId] ?? null;
|
|
45166
|
+
}
|
|
44981
45167
|
// ../reconciliation/src/settlement.ts
|
|
44982
45168
|
var SETTLEMENT_PATTERNS = [
|
|
44983
45169
|
/\bCRED\b.*\bPAYMENT\b/i,
|
|
@@ -45055,6 +45241,45 @@ function settlementEnrichedData(description, type, source) {
|
|
|
45055
45241
|
return out;
|
|
45056
45242
|
}
|
|
45057
45243
|
// src/tools/transactions.ts
|
|
45244
|
+
async function runRescan(client) {
|
|
45245
|
+
const learningRes = await client.get("/api/household-learning");
|
|
45246
|
+
const bag = learningRes.data?.bag ?? {
|
|
45247
|
+
merchantAliases: [],
|
|
45248
|
+
personAliases: [],
|
|
45249
|
+
merchantRules: {},
|
|
45250
|
+
personRules: {}
|
|
45251
|
+
};
|
|
45252
|
+
const merchantsRes = await client.get("/api/merchants");
|
|
45253
|
+
const allMerchants = merchantsRes.data ?? [];
|
|
45254
|
+
const globalMerchants = allMerchants.filter((m2) => !m2.householdId && m2.cleanName).map((m2) => ({ id: m2.id, name: m2.cleanName }));
|
|
45255
|
+
const localMerchants = allMerchants.filter((m2) => m2.householdId && m2.cleanName).map((m2) => ({ id: m2.id, name: m2.cleanName }));
|
|
45256
|
+
const personsRes = await client.get("/api/persons");
|
|
45257
|
+
const persons = (personsRes.data ?? []).filter((p2) => p2.name);
|
|
45258
|
+
const allRows = [];
|
|
45259
|
+
let offset = 0;
|
|
45260
|
+
const limit = 500;
|
|
45261
|
+
while (true) {
|
|
45262
|
+
const res = await client.get("/api/transactions", { limit, offset });
|
|
45263
|
+
const rows = res.data ?? [];
|
|
45264
|
+
allRows.push(...rows);
|
|
45265
|
+
if (!res.hasMore || rows.length === 0)
|
|
45266
|
+
break;
|
|
45267
|
+
offset += limit;
|
|
45268
|
+
}
|
|
45269
|
+
const plan = autoCategorizeFromDescriptions({ rows: allRows, bag, globalMerchants, localMerchants, persons });
|
|
45270
|
+
const links = plan.suggestions.filter((s2) => s2.categoryId).flatMap((s2) => s2.transactionIds.map((txId) => ({
|
|
45271
|
+
transactionId: txId,
|
|
45272
|
+
...s2.entity.type === "merchant" ? { merchantId: s2.entity.merchantId } : { personId: s2.entity.personId },
|
|
45273
|
+
categoryId: s2.categoryId
|
|
45274
|
+
})));
|
|
45275
|
+
if (links.length > 0) {
|
|
45276
|
+
const CHUNK = 500;
|
|
45277
|
+
for (let i2 = 0;i2 < links.length; i2 += CHUNK) {
|
|
45278
|
+
await client.post("/api/transactions/bulk-link-entity", { links: links.slice(i2, i2 + CHUNK) });
|
|
45279
|
+
}
|
|
45280
|
+
}
|
|
45281
|
+
return { linked: links.length, unresolved: plan.unresolvedCount };
|
|
45282
|
+
}
|
|
45058
45283
|
function registerTransactionTools(server, client, crypto3) {
|
|
45059
45284
|
server.registerTool("get_transactions", {
|
|
45060
45285
|
description: "Search and filter transactions. Returns paginated results with merchant and category details.",
|
|
@@ -45179,7 +45404,15 @@ function registerTransactionTools(server, client, crypto3) {
|
|
|
45179
45404
|
granularity,
|
|
45180
45405
|
transactions: rows
|
|
45181
45406
|
});
|
|
45182
|
-
|
|
45407
|
+
const rescan = await runRescan(client);
|
|
45408
|
+
return {
|
|
45409
|
+
content: [
|
|
45410
|
+
{
|
|
45411
|
+
type: "text",
|
|
45412
|
+
text: JSON.stringify({ import: data, rescan })
|
|
45413
|
+
}
|
|
45414
|
+
]
|
|
45415
|
+
};
|
|
45183
45416
|
});
|
|
45184
45417
|
server.registerTool("delete_transactions", {
|
|
45185
45418
|
description: "Hard-delete transactions. This is PERMANENT and cannot be undone. " + "Provide exactly one of: `id` (single transaction), `ids` (a list of transactions), " + "or `filters` (deletes all matching transactions). Returns the number of deleted transactions " + "for the bulk paths.",
|
|
@@ -45208,6 +45441,13 @@ function registerTransactionTools(server, client, crypto3) {
|
|
|
45208
45441
|
const data = await client.delete("/api/transactions/bulk", body);
|
|
45209
45442
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
45210
45443
|
});
|
|
45444
|
+
server.registerTool("rescan_transactions", {
|
|
45445
|
+
description: "Apply the household's saved learning rules (merchant aliases + category rules) to all uncategorized transactions. " + "Equivalent to the browser's auto-categorize flow. Run this after importing statements via MCP to populate merchants and categories.",
|
|
45446
|
+
inputSchema: {}
|
|
45447
|
+
}, async () => {
|
|
45448
|
+
const result = await runRescan(client);
|
|
45449
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
45450
|
+
});
|
|
45211
45451
|
server.registerTool("get_settlement_candidates", {
|
|
45212
45452
|
description: "Find bank debit transactions that look like bill payments for a specific credit card account. " + "Call this after importing a CC statement to identify which existing bank debits should be marked " + "as transfers (to avoid double-counting expenses). Returns transactions with their account name, " + "date, amount, and description.",
|
|
45213
45453
|
inputSchema: {
|
|
@@ -45311,5 +45551,5 @@ main().catch((err) => {
|
|
|
45311
45551
|
process.exit(1);
|
|
45312
45552
|
});
|
|
45313
45553
|
|
|
45314
|
-
//# debugId=
|
|
45554
|
+
//# debugId=E58A7271779D454264756E2164756E21
|
|
45315
45555
|
//# sourceMappingURL=index.js.map
|