paisa-mcp 0.0.16 → 0.0.19
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 +109 -12
- package/dist/index.js.map +5 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42482,7 +42482,7 @@ function registerBudgetTools(server, client) {
|
|
|
42482
42482
|
year: exports_external.number()
|
|
42483
42483
|
}
|
|
42484
42484
|
}, async (body) => {
|
|
42485
|
-
const data = await client.
|
|
42485
|
+
const data = await client.put("/api/budgets", body);
|
|
42486
42486
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
42487
42487
|
});
|
|
42488
42488
|
}
|
|
@@ -42900,9 +42900,9 @@ function registerInvestmentTools(server, client) {
|
|
|
42900
42900
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
42901
42901
|
});
|
|
42902
42902
|
server.registerTool("upsert_investment", {
|
|
42903
|
-
description: "Create or update an investment. " + "type=sip: omit id to add a new SIP (name, amount, startDate, sipDay, owner required); pass id to update an existing SIP, including pausing/stopping it via status. " + "type=fixed_deposit: create-only (bankName, amount, interestRate, startDate, maturityDate, owner required).",
|
|
42903
|
+
description: "Create or update an investment. " + "type=sip: omit id to add a new SIP (name, amount, startDate, sipDay, owner required); pass id to update an existing SIP, including pausing/stopping it via status. " + "type=fixed_deposit: create-only (bankName, amount, interestRate, startDate, maturityDate, owner required). " + "type=holding: create/update a manual stock/MF holding not pulled by a sync, e.g. a Groww position (name, holdingType, owner, investedAmount, currentValue required; re-upserting the same name+platform updates it).",
|
|
42904
42904
|
inputSchema: {
|
|
42905
|
-
type: exports_external.enum(["sip", "fixed_deposit"]).describe("Investment type"),
|
|
42905
|
+
type: exports_external.enum(["sip", "fixed_deposit", "holding"]).describe("Investment type"),
|
|
42906
42906
|
id: exports_external.string().optional().describe("sip: SIP UUID — omit to add, pass to update"),
|
|
42907
42907
|
name: exports_external.string().optional().describe('sip: fund name (e.g. "Parag Parikh Flexi Cap")'),
|
|
42908
42908
|
startDate: exports_external.string().optional().describe("sip & fixed_deposit: YYYY-MM-DD"),
|
|
@@ -42915,10 +42915,43 @@ function registerInvestmentTools(server, client) {
|
|
|
42915
42915
|
interestRate: exports_external.string().optional().describe("fixed_deposit: annual interest rate %"),
|
|
42916
42916
|
maturityDate: exports_external.string().optional().describe("fixed_deposit: YYYY-MM-DD"),
|
|
42917
42917
|
notes: exports_external.string().optional().describe("fixed_deposit: notes"),
|
|
42918
|
+
holdingType: exports_external.enum(["mutual_fund", "stock", "gold", "ppf", "nps", "other"]).optional().describe("holding: asset type"),
|
|
42919
|
+
platform: exports_external.string().optional().describe("holding: broker/platform (e.g. groww); defaults to 'manual'"),
|
|
42920
|
+
symbol: exports_external.string().optional().describe("holding: ticker symbol (optional)"),
|
|
42921
|
+
isin: exports_external.string().optional().describe("holding: ISIN (optional)"),
|
|
42922
|
+
units: exports_external.string().optional().describe("holding: units held (optional)"),
|
|
42923
|
+
investedAmount: exports_external.string().optional().describe("holding: total invested / cost"),
|
|
42924
|
+
currentValue: exports_external.string().optional().describe("holding: current market value"),
|
|
42918
42925
|
amount: exports_external.string().optional().describe("sip: monthly SIP amount; fixed_deposit: principal amount"),
|
|
42919
|
-
owner: exports_external.string().optional().describe("sip & fixed_deposit: owner")
|
|
42926
|
+
owner: exports_external.string().optional().describe("sip & fixed_deposit & holding: owner")
|
|
42920
42927
|
}
|
|
42921
42928
|
}, async ({ type, ...fields }) => {
|
|
42929
|
+
if (type === "holding") {
|
|
42930
|
+
const { name, holdingType, owner: owner2, platform, symbol, isin, units, investedAmount } = fields;
|
|
42931
|
+
const { currentValue } = fields;
|
|
42932
|
+
const missing2 = [
|
|
42933
|
+
["name", name],
|
|
42934
|
+
["holdingType", holdingType],
|
|
42935
|
+
["owner", owner2],
|
|
42936
|
+
["investedAmount", investedAmount],
|
|
42937
|
+
["currentValue", currentValue]
|
|
42938
|
+
].filter(([, v]) => v === undefined).map(([k]) => k);
|
|
42939
|
+
if (missing2.length > 0) {
|
|
42940
|
+
throw new Error(`Missing required fields for holding: ${missing2.join(", ")}`);
|
|
42941
|
+
}
|
|
42942
|
+
const data2 = await client.post("/api/investments/holdings", {
|
|
42943
|
+
name,
|
|
42944
|
+
holdingType,
|
|
42945
|
+
owner: owner2,
|
|
42946
|
+
platform: platform ?? "manual",
|
|
42947
|
+
symbol,
|
|
42948
|
+
isin,
|
|
42949
|
+
units,
|
|
42950
|
+
investedAmount,
|
|
42951
|
+
currentValue
|
|
42952
|
+
});
|
|
42953
|
+
return { content: [{ type: "text", text: JSON.stringify(data2) }] };
|
|
42954
|
+
}
|
|
42922
42955
|
if (type === "sip") {
|
|
42923
42956
|
const {
|
|
42924
42957
|
id,
|
|
@@ -45732,10 +45765,54 @@ async function backfillPersons(client, crypto3, opts = {}) {
|
|
|
45732
45765
|
}
|
|
45733
45766
|
|
|
45734
45767
|
// src/tools/transactions.ts
|
|
45768
|
+
function dedupeKey(date4, description, amount, type) {
|
|
45769
|
+
return `${date4}|${description}|${amount.toFixed(2)}|${type}`;
|
|
45770
|
+
}
|
|
45771
|
+
function flagBatchDuplicates(txns) {
|
|
45772
|
+
const seen = new Set;
|
|
45773
|
+
return txns.map((t2) => {
|
|
45774
|
+
const key = dedupeKey(t2.date, t2.description ?? "", t2.amount, t2.type);
|
|
45775
|
+
if (seen.has(key))
|
|
45776
|
+
return true;
|
|
45777
|
+
seen.add(key);
|
|
45778
|
+
return false;
|
|
45779
|
+
});
|
|
45780
|
+
}
|
|
45735
45781
|
async function importTransactions(client, crypto3, opts) {
|
|
45736
|
-
|
|
45782
|
+
let txns = opts.txns;
|
|
45783
|
+
let duplicatesSkipped = 0;
|
|
45784
|
+
if (txns.length > 0) {
|
|
45785
|
+
const dates = txns.map((t2) => t2.date).sort();
|
|
45786
|
+
const existing = [];
|
|
45787
|
+
let cursor;
|
|
45788
|
+
for (let page = 0;page < 100; page++) {
|
|
45789
|
+
const params = {
|
|
45790
|
+
limit: 200,
|
|
45791
|
+
startDate: dates[0],
|
|
45792
|
+
endDate: dates[dates.length - 1]
|
|
45793
|
+
};
|
|
45794
|
+
if (cursor)
|
|
45795
|
+
params.cursor = cursor;
|
|
45796
|
+
const res = await client.get("/api/transactions", params);
|
|
45797
|
+
const batch = res.data ?? [];
|
|
45798
|
+
if (crypto3)
|
|
45799
|
+
await decryptTransactionFields(batch, crypto3.key);
|
|
45800
|
+
existing.push(...batch);
|
|
45801
|
+
if (!res.hasMore || !res.nextCursor || batch.length === 0)
|
|
45802
|
+
break;
|
|
45803
|
+
cursor = res.nextCursor;
|
|
45804
|
+
}
|
|
45805
|
+
const seen = new Set(existing.map((r2) => dedupeKey(String(r2.date), String(r2.description ?? ""), Number(r2.amount), String(r2.type))));
|
|
45806
|
+
const before = txns.length;
|
|
45807
|
+
txns = txns.filter((t2) => !seen.has(dedupeKey(t2.date, t2.description ?? "", t2.amount, t2.type)));
|
|
45808
|
+
duplicatesSkipped = before - txns.length;
|
|
45809
|
+
}
|
|
45810
|
+
const dupFlags = flagBatchDuplicates(txns);
|
|
45811
|
+
const duplicatesFlagged = dupFlags.filter(Boolean).length;
|
|
45812
|
+
const rows = txns.map((t2, i2) => {
|
|
45737
45813
|
const enrichedData = settlementEnrichedData(t2.description ?? "", t2.type, opts.source);
|
|
45738
|
-
|
|
45814
|
+
const base = { ...t2, isDuplicate: dupFlags[i2] };
|
|
45815
|
+
return enrichedData ? { ...base, enrichedData } : base;
|
|
45739
45816
|
});
|
|
45740
45817
|
if (crypto3)
|
|
45741
45818
|
await encryptTransactionFields(rows, crypto3.key);
|
|
@@ -45750,7 +45827,7 @@ async function importTransactions(client, crypto3, opts) {
|
|
|
45750
45827
|
});
|
|
45751
45828
|
imported += res.imported ?? 0;
|
|
45752
45829
|
}
|
|
45753
|
-
return { imported };
|
|
45830
|
+
return { imported, duplicatesSkipped, duplicatesFlagged };
|
|
45754
45831
|
}
|
|
45755
45832
|
async function runRescan(client, crypto3) {
|
|
45756
45833
|
const EMPTY_BAG2 = {
|
|
@@ -45831,12 +45908,22 @@ function registerTransactionTools(server, client, crypto3) {
|
|
|
45831
45908
|
type: exports_external.enum(["debit", "credit"]).optional(),
|
|
45832
45909
|
amountMin: exports_external.number().optional(),
|
|
45833
45910
|
amountMax: exports_external.number().optional(),
|
|
45834
|
-
|
|
45835
|
-
|
|
45911
|
+
uncategorized: exports_external.boolean().optional().describe("Only transactions with no category (server-side filter)"),
|
|
45912
|
+
limit: exports_external.number().optional().describe("Max results (default 50, max 200)"),
|
|
45913
|
+
cursor: exports_external.string().optional().describe("Opaque cursor from a prior page's nextCursor")
|
|
45836
45914
|
}
|
|
45837
45915
|
}, async (params) => {
|
|
45838
|
-
const { personName, ...rest } = params;
|
|
45916
|
+
const { personName, categorySlug, ...rest } = params;
|
|
45839
45917
|
const apiParams = { ...rest };
|
|
45918
|
+
if (apiParams.uncategorized !== true)
|
|
45919
|
+
delete apiParams.uncategorized;
|
|
45920
|
+
if (categorySlug) {
|
|
45921
|
+
const cats = await client.get("/api/categories");
|
|
45922
|
+
const all2 = cats.data.flatMap((c2) => [c2, ...c2.children ?? []]);
|
|
45923
|
+
const match = all2.find((c2) => c2.slug === categorySlug);
|
|
45924
|
+
if (match)
|
|
45925
|
+
apiParams.categoryId = match.id;
|
|
45926
|
+
}
|
|
45840
45927
|
if (personName && !apiParams.personId) {
|
|
45841
45928
|
const persons = await loadDecryptedPersons(client, crypto3);
|
|
45842
45929
|
const match = resolvePersonByName(persons, personName);
|
|
@@ -45867,8 +45954,18 @@ function registerTransactionTools(server, client, crypto3) {
|
|
|
45867
45954
|
apiParams.personId = match.personId;
|
|
45868
45955
|
}
|
|
45869
45956
|
const data = await client.get("/api/transactions", Object.fromEntries(Object.entries(apiParams).filter(([, v2]) => v2 !== undefined).map(([k2, v2]) => [k2, String(v2)])));
|
|
45870
|
-
if (crypto3 && data.data)
|
|
45957
|
+
if (crypto3 && data.data) {
|
|
45871
45958
|
await decryptTransactionFields(data.data, crypto3.key);
|
|
45959
|
+
if (data.data.some((r2) => r2.personId)) {
|
|
45960
|
+
const persons = await loadDecryptedPersons(client, crypto3);
|
|
45961
|
+
const nameById = new Map(persons.map((p2) => [p2.id, p2.name]));
|
|
45962
|
+
for (const r2 of data.data) {
|
|
45963
|
+
const person = r2.person;
|
|
45964
|
+
if (person && nameById.has(person.id))
|
|
45965
|
+
person.name = nameById.get(person.id);
|
|
45966
|
+
}
|
|
45967
|
+
}
|
|
45968
|
+
}
|
|
45872
45969
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
45873
45970
|
});
|
|
45874
45971
|
server.registerTool("backfill_persons", {
|
|
@@ -46214,5 +46311,5 @@ main().catch((err) => {
|
|
|
46214
46311
|
process.exit(1);
|
|
46215
46312
|
});
|
|
46216
46313
|
|
|
46217
|
-
//# debugId=
|
|
46314
|
+
//# debugId=0561A150F92599BE64756E2164756E21
|
|
46218
46315
|
//# sourceMappingURL=index.js.map
|