paisa-mcp 0.3.1 → 0.4.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 +187 -3
- package/dist/index.js.map +5 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -38033,6 +38033,44 @@ var inputRequired = Object.assign(buildInputRequired, {
|
|
|
38033
38033
|
return { method: "roots/list" };
|
|
38034
38034
|
}
|
|
38035
38035
|
});
|
|
38036
|
+
function acceptedContent(responses, key, schema) {
|
|
38037
|
+
const view = inputResponse(responses, key);
|
|
38038
|
+
if (view.kind !== "elicit" || view.action !== "accept" || view.content === undefined)
|
|
38039
|
+
return;
|
|
38040
|
+
if (schema === undefined)
|
|
38041
|
+
return view.content;
|
|
38042
|
+
const outcome = schema["~standard"].validate(view.content);
|
|
38043
|
+
if (outcome instanceof Promise)
|
|
38044
|
+
throw new TypeError("acceptedContent(responses, key, schema) requires a synchronously-validating schema");
|
|
38045
|
+
return outcome.issues === undefined ? outcome.value : undefined;
|
|
38046
|
+
}
|
|
38047
|
+
function inputResponse(responses, key) {
|
|
38048
|
+
if (responses === undefined || typeof responses !== "object" || responses === null)
|
|
38049
|
+
return { kind: "missing" };
|
|
38050
|
+
const entry = responses[key];
|
|
38051
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry))
|
|
38052
|
+
return { kind: "missing" };
|
|
38053
|
+
const candidate = entry;
|
|
38054
|
+
if (candidate["action"] === "accept" || candidate["action"] === "decline" || candidate["action"] === "cancel") {
|
|
38055
|
+
const content = candidate["content"];
|
|
38056
|
+
return {
|
|
38057
|
+
kind: "elicit",
|
|
38058
|
+
action: candidate["action"],
|
|
38059
|
+
...content !== null && typeof content === "object" && !Array.isArray(content) && { content }
|
|
38060
|
+
};
|
|
38061
|
+
}
|
|
38062
|
+
if (Array.isArray(candidate["roots"]))
|
|
38063
|
+
return {
|
|
38064
|
+
kind: "roots",
|
|
38065
|
+
roots: candidate["roots"]
|
|
38066
|
+
};
|
|
38067
|
+
if (typeof candidate["role"] === "string" && candidate["content"] !== undefined)
|
|
38068
|
+
return {
|
|
38069
|
+
kind: "sampling",
|
|
38070
|
+
result: candidate
|
|
38071
|
+
};
|
|
38072
|
+
return { kind: "missing" };
|
|
38073
|
+
}
|
|
38036
38074
|
var REQUEST_STATE_ONLY_LEG_PACING_MS = 250;
|
|
38037
38075
|
function inputRequiredRoundsExceededMessage(method, maxRounds) {
|
|
38038
38076
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
@@ -53656,6 +53694,145 @@ function applyAnnotations(tools) {
|
|
|
53656
53694
|
return { ...tool, title: meta3.title, annotations: meta3.annotations };
|
|
53657
53695
|
});
|
|
53658
53696
|
}
|
|
53697
|
+
|
|
53698
|
+
// src/lib/confirm.ts
|
|
53699
|
+
import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
53700
|
+
var ANSWER_KEY = "confirm";
|
|
53701
|
+
function clientCanConfirm(mcp, initializeCapabilities) {
|
|
53702
|
+
const envelope = mcp.mcpReq.envelope;
|
|
53703
|
+
const caps = envelope?.[CLIENT_CAPABILITIES_META_KEY] ?? initializeCapabilities();
|
|
53704
|
+
return Boolean(caps?.elicitation);
|
|
53705
|
+
}
|
|
53706
|
+
function planFingerprint(plan) {
|
|
53707
|
+
const canonical = (v) => Array.isArray(v) ? v.map(canonical) : v && typeof v === "object" ? Object.fromEntries(Object.entries(v).filter(([, x]) => x !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([k, x]) => [k, canonical(x)])) : v;
|
|
53708
|
+
return createHash("sha256").update(JSON.stringify(canonical(plan))).digest("hex");
|
|
53709
|
+
}
|
|
53710
|
+
var STATE_KEY = randomBytes(32);
|
|
53711
|
+
function sign(nonce, fingerprint) {
|
|
53712
|
+
return createHmac("sha256", STATE_KEY).update(`${nonce}.${fingerprint}`).digest("hex");
|
|
53713
|
+
}
|
|
53714
|
+
function mintState(fingerprint) {
|
|
53715
|
+
const nonce = randomBytes(16).toString("hex");
|
|
53716
|
+
return `${nonce}.${sign(nonce, fingerprint)}`;
|
|
53717
|
+
}
|
|
53718
|
+
function verifyState(state, fingerprint) {
|
|
53719
|
+
if (typeof state !== "string")
|
|
53720
|
+
return false;
|
|
53721
|
+
const [nonce, mac3, ...rest] = state.split(".");
|
|
53722
|
+
if (!nonce || !mac3 || rest.length)
|
|
53723
|
+
return false;
|
|
53724
|
+
const expected = Buffer.from(sign(nonce, fingerprint), "hex");
|
|
53725
|
+
const given = Buffer.from(mac3, "hex");
|
|
53726
|
+
return given.length === expected.length && timingSafeEqual(given, expected);
|
|
53727
|
+
}
|
|
53728
|
+
function confirmPlan(mcp, message, plan, initializeCapabilities = () => {
|
|
53729
|
+
return;
|
|
53730
|
+
}) {
|
|
53731
|
+
if (!clientCanConfirm(mcp, initializeCapabilities))
|
|
53732
|
+
return { kind: "unsupported" };
|
|
53733
|
+
const fingerprint = planFingerprint(plan);
|
|
53734
|
+
const responses = mcp.mcpReq.inputResponses;
|
|
53735
|
+
const answered = responses !== undefined && ANSWER_KEY in responses;
|
|
53736
|
+
const echoed = mcp.mcpReq.requestState?.();
|
|
53737
|
+
if (answered && verifyState(echoed, fingerprint)) {
|
|
53738
|
+
const content = acceptedContent(responses, ANSWER_KEY);
|
|
53739
|
+
if (content?.confirm === true)
|
|
53740
|
+
return { kind: "confirmed" };
|
|
53741
|
+
return {
|
|
53742
|
+
kind: "declined",
|
|
53743
|
+
result: { cancelled: true, message: "Cancelled by the user. Nothing was changed." }
|
|
53744
|
+
};
|
|
53745
|
+
}
|
|
53746
|
+
return {
|
|
53747
|
+
kind: "ask",
|
|
53748
|
+
result: inputRequired({
|
|
53749
|
+
inputRequests: {
|
|
53750
|
+
[ANSWER_KEY]: inputRequired.elicit({
|
|
53751
|
+
message,
|
|
53752
|
+
requestedSchema: {
|
|
53753
|
+
type: "object",
|
|
53754
|
+
properties: {
|
|
53755
|
+
confirm: { type: "boolean", title: "Confirm", description: "This can't be undone." }
|
|
53756
|
+
},
|
|
53757
|
+
required: ["confirm"]
|
|
53758
|
+
}
|
|
53759
|
+
})
|
|
53760
|
+
},
|
|
53761
|
+
requestState: mintState(fingerprint)
|
|
53762
|
+
})
|
|
53763
|
+
};
|
|
53764
|
+
}
|
|
53765
|
+
function withConfirmation(tool, describe3, initializeCapabilities) {
|
|
53766
|
+
const run = tool.handler;
|
|
53767
|
+
return {
|
|
53768
|
+
...tool,
|
|
53769
|
+
handler: (args, ctx) => {
|
|
53770
|
+
const message = describe3(args);
|
|
53771
|
+
if (message === null || !ctx?.mcp)
|
|
53772
|
+
return run(args, ctx);
|
|
53773
|
+
const outcome = confirmPlan(ctx.mcp, message, { tool: tool.name, args }, initializeCapabilities);
|
|
53774
|
+
if (outcome.kind === "confirmed" || outcome.kind === "unsupported")
|
|
53775
|
+
return run(args, ctx);
|
|
53776
|
+
return outcome.result;
|
|
53777
|
+
}
|
|
53778
|
+
};
|
|
53779
|
+
}
|
|
53780
|
+
var safeDate = (v) => typeof v === "string" && /^\d{4}-\d{2}-\d{2}$/.test(v) ? v : v === undefined ? "any" : "a date";
|
|
53781
|
+
var safeAmount = (v) => typeof v === "string" && /^\d+(\.\d{1,2})?$/.test(v) || typeof v === "number" ? String(v) : null;
|
|
53782
|
+
var plural2 = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
53783
|
+
function describeTransactionFilters(f) {
|
|
53784
|
+
const parts = [];
|
|
53785
|
+
if (f.startDate || f.endDate)
|
|
53786
|
+
parts.push(`dated ${safeDate(f.startDate)} to ${safeDate(f.endDate)}`);
|
|
53787
|
+
if (f.type)
|
|
53788
|
+
parts.push(`${f.type}s only`);
|
|
53789
|
+
if (f.categoryType)
|
|
53790
|
+
parts.push(`category type ${f.categoryType}`);
|
|
53791
|
+
if (f.categoryId)
|
|
53792
|
+
parts.push("in one category");
|
|
53793
|
+
if (f.uncategorized)
|
|
53794
|
+
parts.push("uncategorized");
|
|
53795
|
+
if (f.amountMin !== undefined || f.amountMax !== undefined)
|
|
53796
|
+
parts.push(`amount ₹${f.amountMin ?? 0} to ${f.amountMax === undefined ? "any" : `₹${f.amountMax}`}`);
|
|
53797
|
+
if (f.owner)
|
|
53798
|
+
parts.push("one owner");
|
|
53799
|
+
if (f.search)
|
|
53800
|
+
parts.push("matching a text search");
|
|
53801
|
+
return parts.length ? parts.join(", ") : "with no filters (ALL transactions)";
|
|
53802
|
+
}
|
|
53803
|
+
var describeDeletes = {
|
|
53804
|
+
transactions: (a) => {
|
|
53805
|
+
if (a.id)
|
|
53806
|
+
return "Permanently delete 1 transaction?";
|
|
53807
|
+
if (Array.isArray(a.ids))
|
|
53808
|
+
return `Permanently delete ${plural2(a.ids.length, "transaction")}?`;
|
|
53809
|
+
return `Permanently delete every transaction ${describeTransactionFilters(a.filters ?? {})}?`;
|
|
53810
|
+
},
|
|
53811
|
+
entity: (a) => {
|
|
53812
|
+
const entity = String(a.entity ?? "item").replace(/_/g, " ");
|
|
53813
|
+
const n = Array.isArray(a.ids) ? a.ids.length : 1;
|
|
53814
|
+
const extra = a.entity === "merchant" ? " Their transactions are kept but unlinked." : a.entity === "debt" ? " Its history is lost; settling keeps it." : "";
|
|
53815
|
+
return `Permanently delete ${plural2(n, entity)}?${extra}`;
|
|
53816
|
+
},
|
|
53817
|
+
mergeMerchants: (a) => {
|
|
53818
|
+
const n = Array.isArray(a.duplicateIds) ? a.duplicateIds.length : 0;
|
|
53819
|
+
return `Merge ${plural2(n, "duplicate merchant")} into the one you're keeping? Their transactions move over and the duplicates are deleted.`;
|
|
53820
|
+
},
|
|
53821
|
+
encryptionMode: (a) => `Switch encryption mode to ${a.encryptionMode === "private" ? "private" : "standard"}?`,
|
|
53822
|
+
settleDebt: (a) => {
|
|
53823
|
+
if (a.action !== "settle")
|
|
53824
|
+
return null;
|
|
53825
|
+
const amount = a.settledAmount === undefined ? "0" : safeAmount(a.settledAmount);
|
|
53826
|
+
return amount === null ? "Settle this debt? It will be closed with the remaining balance you gave." : `Settle this debt? It will be closed with a remaining balance of ₹${amount}.`;
|
|
53827
|
+
}
|
|
53828
|
+
};
|
|
53829
|
+
var CONFIRMED_TOOLS = {
|
|
53830
|
+
delete_transactions: describeDeletes.transactions,
|
|
53831
|
+
delete_entity: describeDeletes.entity,
|
|
53832
|
+
merge_merchants: describeDeletes.mergeMerchants,
|
|
53833
|
+
set_encryption_mode: describeDeletes.encryptionMode,
|
|
53834
|
+
manage_debt: describeDeletes.settleDebt
|
|
53835
|
+
};
|
|
53659
53836
|
// src/lib/union-tool.ts
|
|
53660
53837
|
function variant(input2, run) {
|
|
53661
53838
|
return { input: input2, run };
|
|
@@ -60392,7 +60569,7 @@ function registerResources(server, tools) {
|
|
|
60392
60569
|
// package.json
|
|
60393
60570
|
var package_default = {
|
|
60394
60571
|
name: "paisa-mcp",
|
|
60395
|
-
version: "0.
|
|
60572
|
+
version: "0.4.0",
|
|
60396
60573
|
repository: {
|
|
60397
60574
|
type: "git",
|
|
60398
60575
|
url: "git+https://github.com/nimit9/paisa.git",
|
|
@@ -60448,7 +60625,13 @@ function paisaErrorResult(err) {
|
|
|
60448
60625
|
});
|
|
60449
60626
|
}
|
|
60450
60627
|
function buildServer(client, crypto3) {
|
|
60451
|
-
|
|
60628
|
+
let initializeCapabilities = () => {
|
|
60629
|
+
return;
|
|
60630
|
+
};
|
|
60631
|
+
const tools = applyAnnotations(REGISTRATIONS.flatMap((group) => group.tools(client, crypto3)).map((t2) => {
|
|
60632
|
+
const describe3 = CONFIRMED_TOOLS[t2.name];
|
|
60633
|
+
return describe3 ? withConfirmation(t2, describe3, () => initializeCapabilities()) : t2;
|
|
60634
|
+
}));
|
|
60452
60635
|
for (const t2 of tools)
|
|
60453
60636
|
if (t2.input)
|
|
60454
60637
|
withCompactJsonSchema(t2.input);
|
|
@@ -60458,6 +60641,7 @@ function buildServer(client, crypto3) {
|
|
|
60458
60641
|
tools,
|
|
60459
60642
|
errorResult: paisaErrorResult
|
|
60460
60643
|
});
|
|
60644
|
+
initializeCapabilities = () => server.server.getClientCapabilities();
|
|
60461
60645
|
registerResources(server, tools);
|
|
60462
60646
|
registerPrompts(server);
|
|
60463
60647
|
return server;
|
|
@@ -60484,5 +60668,5 @@ main().catch((err) => {
|
|
|
60484
60668
|
process.exit(1);
|
|
60485
60669
|
});
|
|
60486
60670
|
|
|
60487
|
-
//# debugId=
|
|
60671
|
+
//# debugId=F56B7BEACF2C363564756E2164756E21
|
|
60488
60672
|
//# sourceMappingURL=index.js.map
|