paisa-mcp 0.4.1 → 0.4.2
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 +288 -132
- package/dist/index.js.map +13 -13
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -38033,17 +38033,6 @@ 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
38036
|
function inputResponse(responses, key) {
|
|
38048
38037
|
if (responses === undefined || typeof responses !== "object" || responses === null)
|
|
38049
38038
|
return { kind: "missing" };
|
|
@@ -53439,10 +53428,153 @@ async function initCrypto(client, pin, userId) {
|
|
|
53439
53428
|
return { key };
|
|
53440
53429
|
}
|
|
53441
53430
|
|
|
53431
|
+
// src/server.ts
|
|
53432
|
+
import { randomBytes } from "node:crypto";
|
|
53433
|
+
|
|
53442
53434
|
// ../../node_modules/.bun/@modelcontextprotocol+server@2.1.0/node_modules/@modelcontextprotocol/server/dist/index.mjs
|
|
53443
53435
|
var DEFAULT_MAX_REQUEST_BODY_SIZE = 4 * 1024 * 1024;
|
|
53436
|
+
var PREFIX = "v1.";
|
|
53437
|
+
function bytesToBase64Url(bytes) {
|
|
53438
|
+
let bin = "";
|
|
53439
|
+
for (const b of bytes)
|
|
53440
|
+
bin += String.fromCodePoint(b);
|
|
53441
|
+
return btoa(bin).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
53442
|
+
}
|
|
53443
|
+
function constantTimeTagEqual(a, b) {
|
|
53444
|
+
if (a.length !== b.length)
|
|
53445
|
+
return false;
|
|
53446
|
+
let r = 0;
|
|
53447
|
+
for (let i = 0;i < a.length; i++)
|
|
53448
|
+
r |= a.codePointAt(i) ^ b.codePointAt(i);
|
|
53449
|
+
return r === 0;
|
|
53450
|
+
}
|
|
53451
|
+
function base64UrlToBytes(s) {
|
|
53452
|
+
const b64 = s.replaceAll("-", "+").replaceAll("_", "/");
|
|
53453
|
+
const bin = atob(b64);
|
|
53454
|
+
const bytes = new Uint8Array(bin.length);
|
|
53455
|
+
for (let i = 0;i < bin.length; i++)
|
|
53456
|
+
bytes[i] = bin.codePointAt(i);
|
|
53457
|
+
return bytes;
|
|
53458
|
+
}
|
|
53459
|
+
function createRequestStateCodec(options) {
|
|
53460
|
+
const subtle = globalThis.crypto?.subtle;
|
|
53461
|
+
if (subtle === undefined)
|
|
53462
|
+
throw new TypeError("createRequestStateCodec requires the Web Crypto API (globalThis.crypto.subtle); see https://ts.sdk.modelcontextprotocol.io/v2/troubleshooting for the Node.js polyfill instructions");
|
|
53463
|
+
const keyBytes = typeof options.key === "string" ? new TextEncoder().encode(options.key) : Uint8Array.from(options.key);
|
|
53464
|
+
if (keyBytes.byteLength < 32)
|
|
53465
|
+
throw new RangeError(`createRequestStateCodec: key must be at least 32 bytes (got ${keyBytes.byteLength})`);
|
|
53466
|
+
const ttlSeconds = options.ttlSeconds ?? 600;
|
|
53467
|
+
if (!Number.isFinite(ttlSeconds))
|
|
53468
|
+
throw new RangeError("createRequestStateCodec: ttlSeconds must be a finite number");
|
|
53469
|
+
const bind2 = options.bind;
|
|
53470
|
+
let cryptoKey;
|
|
53471
|
+
const importedKey = () => cryptoKey ??= subtle.importKey("raw", keyBytes, {
|
|
53472
|
+
name: "HMAC",
|
|
53473
|
+
hash: "SHA-256"
|
|
53474
|
+
}, false, ["sign", "verify"]);
|
|
53475
|
+
const utf8 = new TextEncoder;
|
|
53476
|
+
const BIND_LABEL = "mcp.requestState.bind:";
|
|
53477
|
+
const bindTag = async (value) => {
|
|
53478
|
+
return bytesToBase64Url(new Uint8Array(await subtle.sign("HMAC", await importedKey(), utf8.encode(BIND_LABEL + value))).slice(0, 16));
|
|
53479
|
+
};
|
|
53480
|
+
return {
|
|
53481
|
+
async mint(payload, ctx) {
|
|
53482
|
+
const envelope = {
|
|
53483
|
+
p: payload,
|
|
53484
|
+
exp: Math.floor(Date.now() / 1000) + ttlSeconds
|
|
53485
|
+
};
|
|
53486
|
+
if (bind2 !== undefined) {
|
|
53487
|
+
if (ctx === undefined)
|
|
53488
|
+
throw new TypeError("createRequestStateCodec: mint() requires ctx when a bind callback is configured");
|
|
53489
|
+
envelope.b = await bindTag(bind2(ctx));
|
|
53490
|
+
}
|
|
53491
|
+
const body = bytesToBase64Url(utf8.encode(JSON.stringify(envelope)));
|
|
53492
|
+
return `${PREFIX}${body}.${bytesToBase64Url(new Uint8Array(await subtle.sign("HMAC", await importedKey(), utf8.encode(PREFIX + body))))}`;
|
|
53493
|
+
},
|
|
53494
|
+
async verify(state, ctx) {
|
|
53495
|
+
const dot = state.lastIndexOf(".");
|
|
53496
|
+
if (!state.startsWith(PREFIX) || dot <= 3)
|
|
53497
|
+
throw new Error("malformed");
|
|
53498
|
+
const body = state.slice(3, dot);
|
|
53499
|
+
let macBytes;
|
|
53500
|
+
try {
|
|
53501
|
+
macBytes = base64UrlToBytes(state.slice(dot + 1));
|
|
53502
|
+
} catch {
|
|
53503
|
+
throw new Error("malformed");
|
|
53504
|
+
}
|
|
53505
|
+
if (!await subtle.verify("HMAC", await importedKey(), macBytes, utf8.encode(PREFIX + body)))
|
|
53506
|
+
throw new Error("mac");
|
|
53507
|
+
let envelope;
|
|
53508
|
+
try {
|
|
53509
|
+
envelope = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(base64UrlToBytes(body)));
|
|
53510
|
+
} catch {
|
|
53511
|
+
throw new Error("malformed");
|
|
53512
|
+
}
|
|
53513
|
+
if (typeof envelope.exp !== "number" || envelope.exp < Math.floor(Date.now() / 1000))
|
|
53514
|
+
throw new Error("expired");
|
|
53515
|
+
if (bind2 !== undefined) {
|
|
53516
|
+
const expected = await bindTag(bind2(ctx));
|
|
53517
|
+
if (envelope.b === undefined || !constantTimeTagEqual(envelope.b, expected))
|
|
53518
|
+
throw new Error("bind");
|
|
53519
|
+
} else if (envelope.b !== undefined)
|
|
53520
|
+
throw new Error("bind");
|
|
53521
|
+
return envelope.p;
|
|
53522
|
+
}
|
|
53523
|
+
};
|
|
53524
|
+
}
|
|
53444
53525
|
|
|
53445
|
-
// ../../node_modules/.bun/@nimit9+signet-server@0.2.
|
|
53526
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/interaction.js
|
|
53527
|
+
var CONFIRM_KEY = "signet_confirm";
|
|
53528
|
+
function canonicalize(value) {
|
|
53529
|
+
if (Array.isArray(value))
|
|
53530
|
+
return value.map(canonicalize);
|
|
53531
|
+
if (value !== null && typeof value === "object") {
|
|
53532
|
+
const obj = value;
|
|
53533
|
+
return Object.fromEntries(Object.keys(obj).sort().map((k) => [k, canonicalize(obj[k])]));
|
|
53534
|
+
}
|
|
53535
|
+
return value;
|
|
53536
|
+
}
|
|
53537
|
+
async function fingerprint(args) {
|
|
53538
|
+
const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify(canonicalize(args ?? null))));
|
|
53539
|
+
return [...new Uint8Array(bytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
53540
|
+
}
|
|
53541
|
+
async function confirm(ctx, { message, detail, args, key }) {
|
|
53542
|
+
const id = key ?? `${CONFIRM_KEY}:${ctx.tool}`;
|
|
53543
|
+
const view = inputResponse(ctx.mcp.mcpReq.inputResponses, id);
|
|
53544
|
+
if (view.kind === "missing") {
|
|
53545
|
+
if (!ctx.confirmSupport.getClientCapabilities()?.elicitation)
|
|
53546
|
+
return "unsupported";
|
|
53547
|
+
const payload = { tool: ctx.tool, id, fp: await fingerprint(args) };
|
|
53548
|
+
const requestState = await ctx.confirmSupport.mint?.(payload);
|
|
53549
|
+
return inputRequired({
|
|
53550
|
+
inputRequests: {
|
|
53551
|
+
[id]: inputRequired.elicit({
|
|
53552
|
+
message: detail ? `${message}
|
|
53553
|
+
|
|
53554
|
+
${detail}` : message,
|
|
53555
|
+
requestedSchema: {
|
|
53556
|
+
type: "object",
|
|
53557
|
+
properties: { confirm: { type: "boolean", title: "Confirm" } },
|
|
53558
|
+
required: ["confirm"]
|
|
53559
|
+
}
|
|
53560
|
+
})
|
|
53561
|
+
},
|
|
53562
|
+
...requestState ? { requestState } : {}
|
|
53563
|
+
});
|
|
53564
|
+
}
|
|
53565
|
+
if (view.kind !== "elicit" || view.action !== "accept" || view.content?.confirm !== true) {
|
|
53566
|
+
return "declined";
|
|
53567
|
+
}
|
|
53568
|
+
if (ctx.confirmSupport.mint) {
|
|
53569
|
+
const state = ctx.mcp.mcpReq.requestState();
|
|
53570
|
+
if (!state || state.tool !== ctx.tool || state.id !== id || state.fp !== await fingerprint(args))
|
|
53571
|
+
return "declined";
|
|
53572
|
+
}
|
|
53573
|
+
return "confirmed";
|
|
53574
|
+
}
|
|
53575
|
+
function isConfirmPending(value) {
|
|
53576
|
+
return isInputRequiredResult(value);
|
|
53577
|
+
}
|
|
53446
53578
|
async function progress(ctx, done, total, message) {
|
|
53447
53579
|
const token = ctx.mcp.mcpReq._meta?.progressToken;
|
|
53448
53580
|
if (token === undefined)
|
|
@@ -53459,7 +53591,7 @@ async function progress(ctx, done, total, message) {
|
|
|
53459
53591
|
});
|
|
53460
53592
|
} catch {}
|
|
53461
53593
|
}
|
|
53462
|
-
// ../../node_modules/.bun/@nimit9+signet-server@0.2.
|
|
53594
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/http/status-codes.js
|
|
53463
53595
|
var STATUS_CODES = {
|
|
53464
53596
|
400: "BAD_REQUEST",
|
|
53465
53597
|
401: "UNAUTHORIZED",
|
|
@@ -53473,7 +53605,7 @@ var STATUS_CODES = {
|
|
|
53473
53605
|
429: "RATE_LIMITED"
|
|
53474
53606
|
};
|
|
53475
53607
|
|
|
53476
|
-
// ../../node_modules/.bun/@nimit9+signet-server@0.2.
|
|
53608
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/errors.js
|
|
53477
53609
|
function isZodError(err) {
|
|
53478
53610
|
return err instanceof Error && err.name === "ZodError" && Array.isArray(err.issues);
|
|
53479
53611
|
}
|
|
@@ -53537,7 +53669,7 @@ function report(opts, err, meta3, c) {
|
|
|
53537
53669
|
} catch {}
|
|
53538
53670
|
}
|
|
53539
53671
|
|
|
53540
|
-
// ../../node_modules/.bun/@nimit9+signet-server@0.2.
|
|
53672
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/primitives.js
|
|
53541
53673
|
var RAW = Symbol.for("signet.mcp.rawResult");
|
|
53542
53674
|
function protocolError(err, deps, meta3) {
|
|
53543
53675
|
if (ProtocolError.isInstance(err))
|
|
@@ -53570,8 +53702,14 @@ function checkScopes(scopes, token) {
|
|
|
53570
53702
|
});
|
|
53571
53703
|
}
|
|
53572
53704
|
function baseContext(mcp, deps) {
|
|
53573
|
-
const { c, token } = deps.context;
|
|
53574
|
-
return {
|
|
53705
|
+
const { c, token, confirmSupport } = deps.context;
|
|
53706
|
+
return {
|
|
53707
|
+
signal: mcp.mcpReq.signal,
|
|
53708
|
+
mcp,
|
|
53709
|
+
confirmSupport,
|
|
53710
|
+
...c ? { c } : {},
|
|
53711
|
+
...token ? { token } : {}
|
|
53712
|
+
};
|
|
53575
53713
|
}
|
|
53576
53714
|
function toContents(uri, value, mimeType) {
|
|
53577
53715
|
if (value !== null && typeof value === "object" && RAW in value)
|
|
@@ -53681,7 +53819,7 @@ function registerPrompts(server, prompts, deps) {
|
|
|
53681
53819
|
}
|
|
53682
53820
|
}
|
|
53683
53821
|
|
|
53684
|
-
// ../../node_modules/.bun/@nimit9+signet-server@0.2.
|
|
53822
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/server.js
|
|
53685
53823
|
var RAW2 = Symbol.for("signet.mcp.rawResult");
|
|
53686
53824
|
function toolResult(result) {
|
|
53687
53825
|
return Object.defineProperty({ ...result }, RAW2, { value: true, enumerable: false });
|
|
@@ -53709,9 +53847,14 @@ function createMcpServer(options) {
|
|
|
53709
53847
|
...options.resources?.length ? { resources: { listChanged: false } } : {},
|
|
53710
53848
|
...options.prompts?.length ? { prompts: { listChanged: false } } : {}
|
|
53711
53849
|
};
|
|
53850
|
+
const confirmCodec = options.confirm ? createRequestStateCodec({
|
|
53851
|
+
key: options.confirm.key,
|
|
53852
|
+
...options.confirm.ttlSeconds !== undefined ? { ttlSeconds: options.confirm.ttlSeconds } : {}
|
|
53853
|
+
}) : undefined;
|
|
53712
53854
|
const server = new McpServer({ name: options.name, version: options.version }, {
|
|
53713
53855
|
...unlisted.size ? {} : { capabilities: toolsCapability },
|
|
53714
|
-
...options.instructions ? { instructions: options.instructions } : {}
|
|
53856
|
+
...options.instructions ? { instructions: options.instructions } : {},
|
|
53857
|
+
...confirmCodec ? { requestState: { verify: confirmCodec.verify } } : {}
|
|
53715
53858
|
});
|
|
53716
53859
|
let restore;
|
|
53717
53860
|
if (unlisted.size) {
|
|
@@ -53726,6 +53869,10 @@ function createMcpServer(options) {
|
|
|
53726
53869
|
tool: tool.name,
|
|
53727
53870
|
signal: mcp.mcpReq.signal,
|
|
53728
53871
|
mcp,
|
|
53872
|
+
confirmSupport: {
|
|
53873
|
+
getClientCapabilities: () => server.server.getClientCapabilities(),
|
|
53874
|
+
...confirmCodec ? { mint: (payload) => confirmCodec.mint(payload, mcp) } : {}
|
|
53875
|
+
},
|
|
53729
53876
|
...c ? { c } : {},
|
|
53730
53877
|
...token ? { token } : {}
|
|
53731
53878
|
};
|
|
@@ -53764,7 +53911,14 @@ function createMcpServer(options) {
|
|
|
53764
53911
|
restore?.();
|
|
53765
53912
|
restore = undefined;
|
|
53766
53913
|
}
|
|
53767
|
-
const
|
|
53914
|
+
const confirmSupport = {
|
|
53915
|
+
getClientCapabilities: () => server.server.getClientCapabilities(),
|
|
53916
|
+
...confirmCodec ? { mint: (payload) => confirmCodec.mint(payload) } : {}
|
|
53917
|
+
};
|
|
53918
|
+
const deps = {
|
|
53919
|
+
options,
|
|
53920
|
+
context: { confirmSupport, ...c ? { c } : {}, ...token ? { token } : {} }
|
|
53921
|
+
};
|
|
53768
53922
|
if (options.resources?.length)
|
|
53769
53923
|
registerResources(server, options.resources, deps);
|
|
53770
53924
|
if (options.prompts?.length)
|
|
@@ -53867,84 +54021,34 @@ function applyAnnotations(tools) {
|
|
|
53867
54021
|
}
|
|
53868
54022
|
|
|
53869
54023
|
// src/lib/confirm.ts
|
|
53870
|
-
|
|
53871
|
-
var
|
|
53872
|
-
|
|
53873
|
-
|
|
53874
|
-
|
|
53875
|
-
|
|
53876
|
-
}
|
|
53877
|
-
|
|
53878
|
-
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;
|
|
53879
|
-
return createHash("sha256").update(JSON.stringify(canonical(plan))).digest("hex");
|
|
53880
|
-
}
|
|
53881
|
-
var STATE_KEY = randomBytes(32);
|
|
53882
|
-
function sign(nonce, fingerprint) {
|
|
53883
|
-
return createHmac("sha256", STATE_KEY).update(`${nonce}.${fingerprint}`).digest("hex");
|
|
53884
|
-
}
|
|
53885
|
-
function mintState(fingerprint) {
|
|
53886
|
-
const nonce = randomBytes(16).toString("hex");
|
|
53887
|
-
return `${nonce}.${sign(nonce, fingerprint)}`;
|
|
53888
|
-
}
|
|
53889
|
-
function verifyState(state, fingerprint) {
|
|
53890
|
-
if (typeof state !== "string")
|
|
53891
|
-
return false;
|
|
53892
|
-
const [nonce, mac3, ...rest] = state.split(".");
|
|
53893
|
-
if (!nonce || !mac3 || rest.length)
|
|
53894
|
-
return false;
|
|
53895
|
-
const expected = Buffer.from(sign(nonce, fingerprint), "hex");
|
|
53896
|
-
const given = Buffer.from(mac3, "hex");
|
|
53897
|
-
return given.length === expected.length && timingSafeEqual(given, expected);
|
|
54024
|
+
var CANCELLED = { cancelled: true, message: "Cancelled by the user. Nothing was changed." };
|
|
54025
|
+
var PLAN_CHANGED = {
|
|
54026
|
+
planChanged: true,
|
|
54027
|
+
message: "The request changed after you confirmed, so nothing was changed. Run it again to see the new plan."
|
|
54028
|
+
};
|
|
54029
|
+
function acceptedDifferentPlan(ctx) {
|
|
54030
|
+
const view = inputResponse(ctx.mcp.mcpReq.inputResponses, `signet_confirm:${ctx.tool}`);
|
|
54031
|
+
return view.kind === "elicit" && view.action === "accept" && view.content?.confirm === true;
|
|
53898
54032
|
}
|
|
53899
|
-
function
|
|
53900
|
-
|
|
53901
|
-
|
|
53902
|
-
|
|
53903
|
-
|
|
53904
|
-
|
|
53905
|
-
|
|
53906
|
-
|
|
53907
|
-
|
|
53908
|
-
if (answered && verifyState(echoed, fingerprint)) {
|
|
53909
|
-
const content = acceptedContent(responses, ANSWER_KEY);
|
|
53910
|
-
if (content?.confirm === true)
|
|
53911
|
-
return { kind: "confirmed" };
|
|
53912
|
-
return {
|
|
53913
|
-
kind: "declined",
|
|
53914
|
-
result: { cancelled: true, message: "Cancelled by the user. Nothing was changed." }
|
|
53915
|
-
};
|
|
53916
|
-
}
|
|
53917
|
-
return {
|
|
53918
|
-
kind: "ask",
|
|
53919
|
-
result: inputRequired({
|
|
53920
|
-
inputRequests: {
|
|
53921
|
-
[ANSWER_KEY]: inputRequired.elicit({
|
|
53922
|
-
message,
|
|
53923
|
-
requestedSchema: {
|
|
53924
|
-
type: "object",
|
|
53925
|
-
properties: {
|
|
53926
|
-
confirm: { type: "boolean", title: "Confirm", description: "This can't be undone." }
|
|
53927
|
-
},
|
|
53928
|
-
required: ["confirm"]
|
|
53929
|
-
}
|
|
53930
|
-
})
|
|
53931
|
-
},
|
|
53932
|
-
requestState: mintState(fingerprint)
|
|
53933
|
-
})
|
|
53934
|
-
};
|
|
54033
|
+
async function askThenRun(ctx, message, plan, run, fallback = run) {
|
|
54034
|
+
const answer = await confirm(ctx, { message, args: plan });
|
|
54035
|
+
if (isConfirmPending(answer))
|
|
54036
|
+
return answer;
|
|
54037
|
+
if (answer === "confirmed")
|
|
54038
|
+
return run();
|
|
54039
|
+
if (answer === "unsupported")
|
|
54040
|
+
return fallback();
|
|
54041
|
+
return acceptedDifferentPlan(ctx) ? PLAN_CHANGED : CANCELLED;
|
|
53935
54042
|
}
|
|
53936
|
-
function withConfirmation(tool, describe3
|
|
54043
|
+
function withConfirmation(tool, describe3) {
|
|
53937
54044
|
const run = tool.handler;
|
|
53938
54045
|
return {
|
|
53939
54046
|
...tool,
|
|
53940
|
-
handler: (args, ctx) => {
|
|
54047
|
+
handler: async (args, ctx) => {
|
|
53941
54048
|
const message = describe3(args);
|
|
53942
54049
|
if (message === null || !ctx?.mcp)
|
|
53943
54050
|
return run(args, ctx);
|
|
53944
|
-
|
|
53945
|
-
if (outcome.kind === "confirmed" || outcome.kind === "unsupported")
|
|
53946
|
-
return run(args, ctx);
|
|
53947
|
-
return outcome.result;
|
|
54051
|
+
return askThenRun(ctx, message, { tool: tool.name, args }, () => run(args, ctx));
|
|
53948
54052
|
}
|
|
53949
54053
|
};
|
|
53950
54054
|
}
|
|
@@ -54021,11 +54125,7 @@ var BULK_CONFIRMED_TOOLS = {
|
|
|
54021
54125
|
}),
|
|
54022
54126
|
bulk_link_entity: async (args) => ({ n: Array.isArray(args.links) ? args.links.length : 0 })
|
|
54023
54127
|
};
|
|
54024
|
-
|
|
54025
|
-
planChanged: true,
|
|
54026
|
-
message: "The transactions changed after they were counted, so nothing was written. Run it again to see the new count."
|
|
54027
|
-
};
|
|
54028
|
-
function withBulkConfirmation(tool, count, initializeCapabilities) {
|
|
54128
|
+
function withBulkConfirmation(tool, count) {
|
|
54029
54129
|
const run = tool.handler;
|
|
54030
54130
|
const runPlanned = async (args, ctx, hash2) => {
|
|
54031
54131
|
const result = await run(hash2 === undefined ? args : { ...args, expectedPlanHash: hash2 }, ctx);
|
|
@@ -54034,18 +54134,13 @@ function withBulkConfirmation(tool, count, initializeCapabilities) {
|
|
|
54034
54134
|
return {
|
|
54035
54135
|
...tool,
|
|
54036
54136
|
handler: async (args, ctx) => {
|
|
54037
|
-
|
|
54137
|
+
const canAsk = Boolean(ctx?.confirmSupport?.getClientCapabilities()?.elicitation);
|
|
54138
|
+
if (args.dryRun === true || !ctx?.mcp || !canAsk)
|
|
54038
54139
|
return run(args, ctx);
|
|
54039
54140
|
const { n, planHash: hash2 } = await count(args, async (a) => run(a, ctx));
|
|
54040
54141
|
if (n <= BULK_CONFIRM_THRESHOLD)
|
|
54041
54142
|
return runPlanned(args, ctx, hash2);
|
|
54042
|
-
|
|
54043
|
-
const outcome = confirmPlan(ctx.mcp, message, { tool: tool.name, args, n, planHash: hash2 }, initializeCapabilities);
|
|
54044
|
-
if (outcome.kind === "confirmed")
|
|
54045
|
-
return runPlanned(args, ctx, hash2);
|
|
54046
|
-
if (outcome.kind === "unsupported")
|
|
54047
|
-
return run(args, ctx);
|
|
54048
|
-
return outcome.result;
|
|
54143
|
+
return askThenRun(ctx, `Update ${plural2(n, "transaction")} in one go?`, { tool: tool.name, args, n, planHash: hash2 }, () => runPlanned(args, ctx, hash2), () => run(args, ctx));
|
|
54049
54144
|
}
|
|
54050
54145
|
};
|
|
54051
54146
|
}
|
|
@@ -54718,7 +54813,7 @@ function goalTools(client) {
|
|
|
54718
54813
|
return [
|
|
54719
54814
|
defineTool({
|
|
54720
54815
|
name: "upsert_savings",
|
|
54721
|
-
description: "Create (omit id) or update (pass id; all fields optional) a savings goal, EMI or sinking fund. Amounts are ₹ decimal strings, dates YYYY-MM-DD. Required on create — " + "goal: goalType, name, targetAmount. emi: name, totalAmount, emiAmount (monthly), startDate, endDate (last payment), totalMonths, owner. " + "sinking_fund: name, targetAmount, targetDate (due), monthlyContribution.",
|
|
54816
|
+
description: "Create (omit id) or update (pass id; all fields optional) a savings goal, EMI or sinking fund. Amounts are ₹ decimal strings, dates YYYY-MM-DD. Required on create — " + "goal: goalType, name, targetAmount. emi: name, totalAmount, emiAmount (monthly), startDate, endDate (last payment), totalMonths, owner. " + "sinking_fund: name, targetAmount, targetDate (due), monthlyContribution. " + "status — goal: active|paused|completed|cancelled; emi: active|completed|foreclosed; sinking funds have none.",
|
|
54722
54817
|
input: exports_external.object({
|
|
54723
54818
|
type: exports_external.enum(["goal", "emi", "sinking_fund"]),
|
|
54724
54819
|
id: exports_external.string().optional(),
|
|
@@ -54739,7 +54834,8 @@ function goalTools(client) {
|
|
|
54739
54834
|
startDate: exports_external.string().optional(),
|
|
54740
54835
|
endDate: exports_external.string().optional(),
|
|
54741
54836
|
totalMonths: exports_external.number().optional(),
|
|
54742
|
-
paidMonths: exports_external.number().optional()
|
|
54837
|
+
paidMonths: exports_external.number().optional(),
|
|
54838
|
+
status: exports_external.enum(["active", "paused", "completed", "cancelled", "foreclosed"]).optional().describe("goal: active|paused|completed|cancelled; emi: active|completed|foreclosed")
|
|
54743
54839
|
}),
|
|
54744
54840
|
handler: async ({ type, ...fields }) => {
|
|
54745
54841
|
const require2 = (keys) => {
|
|
@@ -54748,6 +54844,14 @@ function goalTools(client) {
|
|
|
54748
54844
|
throw new Error(`upsert_savings type='${type}' requires: ${missing.join(", ")}`);
|
|
54749
54845
|
}
|
|
54750
54846
|
};
|
|
54847
|
+
const allowedStatus = {
|
|
54848
|
+
goal: ["active", "paused", "completed", "cancelled"],
|
|
54849
|
+
emi: ["active", "completed", "foreclosed"],
|
|
54850
|
+
sinking_fund: []
|
|
54851
|
+
};
|
|
54852
|
+
if (fields.status !== undefined && !allowedStatus[type].includes(fields.status)) {
|
|
54853
|
+
throw new Error(type === "sinking_fund" ? "upsert_savings type='sinking_fund' has no status" : `upsert_savings type='${type}' status must be one of: ${allowedStatus[type].join(", ")}`);
|
|
54854
|
+
}
|
|
54751
54855
|
if (type === "goal") {
|
|
54752
54856
|
const {
|
|
54753
54857
|
id: id2,
|
|
@@ -54760,7 +54864,8 @@ function goalTools(client) {
|
|
|
54760
54864
|
owner,
|
|
54761
54865
|
icon,
|
|
54762
54866
|
color,
|
|
54763
|
-
notes
|
|
54867
|
+
notes,
|
|
54868
|
+
status
|
|
54764
54869
|
} = fields;
|
|
54765
54870
|
const body2 = {
|
|
54766
54871
|
type: goalType,
|
|
@@ -54772,7 +54877,8 @@ function goalTools(client) {
|
|
|
54772
54877
|
owner,
|
|
54773
54878
|
icon,
|
|
54774
54879
|
color,
|
|
54775
|
-
notes
|
|
54880
|
+
notes,
|
|
54881
|
+
status
|
|
54776
54882
|
};
|
|
54777
54883
|
if (!id2)
|
|
54778
54884
|
require2(["name", "targetAmount", "goalType"]);
|
|
@@ -54790,7 +54896,8 @@ function goalTools(client) {
|
|
|
54790
54896
|
totalMonths,
|
|
54791
54897
|
paidMonths,
|
|
54792
54898
|
interestRate,
|
|
54793
|
-
outstandingPrincipal
|
|
54899
|
+
outstandingPrincipal,
|
|
54900
|
+
status
|
|
54794
54901
|
} = fields;
|
|
54795
54902
|
const { owner } = fields;
|
|
54796
54903
|
if (!id2) {
|
|
@@ -54814,7 +54921,8 @@ function goalTools(client) {
|
|
|
54814
54921
|
paidMonths,
|
|
54815
54922
|
interestRate,
|
|
54816
54923
|
outstandingPrincipal,
|
|
54817
|
-
owner
|
|
54924
|
+
owner,
|
|
54925
|
+
status
|
|
54818
54926
|
};
|
|
54819
54927
|
const data2 = id2 ? await client.patch(`/api/goals/emis/${id2}`, body2) : await client.post("/api/goals/emis", body2);
|
|
54820
54928
|
return data2;
|
|
@@ -59025,9 +59133,9 @@ function parseAmount4(raw) {
|
|
|
59025
59133
|
return Number.isNaN(num) ? 0 : Math.abs(num);
|
|
59026
59134
|
}
|
|
59027
59135
|
// src/lib/plan-hash.ts
|
|
59028
|
-
import { createHash
|
|
59136
|
+
import { createHash } from "node:crypto";
|
|
59029
59137
|
function planHash(ids) {
|
|
59030
|
-
return
|
|
59138
|
+
return createHash("sha256").update([...ids].sort().join(",")).digest("hex");
|
|
59031
59139
|
}
|
|
59032
59140
|
|
|
59033
59141
|
// src/lib/txn-rows.ts
|
|
@@ -59105,6 +59213,9 @@ function toLeanRow(r2, personNames) {
|
|
|
59105
59213
|
function normalizePersonName(name) {
|
|
59106
59214
|
return name.toLowerCase().trim().replace(/\s+/g, " ");
|
|
59107
59215
|
}
|
|
59216
|
+
function normalizeUpiHandle(handle) {
|
|
59217
|
+
return handle.toLowerCase().trim().split("@")[0];
|
|
59218
|
+
}
|
|
59108
59219
|
async function loadDecryptedPersons(client, crypto3) {
|
|
59109
59220
|
const res = await client.get("/api/persons");
|
|
59110
59221
|
const persons = [];
|
|
@@ -59116,7 +59227,7 @@ async function loadDecryptedPersons(client, crypto3) {
|
|
|
59116
59227
|
} catch {}
|
|
59117
59228
|
}
|
|
59118
59229
|
if (name)
|
|
59119
|
-
persons.push({ id: p2.id, name });
|
|
59230
|
+
persons.push({ id: p2.id, name, upiHandles: p2.upiHandles ?? [] });
|
|
59120
59231
|
}
|
|
59121
59232
|
return persons;
|
|
59122
59233
|
}
|
|
@@ -59156,10 +59267,15 @@ function isPersonLikeNarration(description) {
|
|
|
59156
59267
|
async function backfillPersons(client, crypto3, opts = {}) {
|
|
59157
59268
|
const existing = await loadDecryptedPersons(client, crypto3);
|
|
59158
59269
|
const personIdByNorm = new Map;
|
|
59159
|
-
|
|
59270
|
+
const personIdByHandle = new Map;
|
|
59271
|
+
for (const p2 of existing) {
|
|
59160
59272
|
personIdByNorm.set(normalizePersonName(p2.name), p2.id);
|
|
59273
|
+
for (const h2 of p2.upiHandles)
|
|
59274
|
+
personIdByHandle.set(normalizeUpiHandle(h2), p2.id);
|
|
59275
|
+
}
|
|
59161
59276
|
const { rows, truncated } = await fetchAllRowsPaged(client, crypto3, { unresolved: "true" });
|
|
59162
59277
|
const candidates = rows.filter((r2) => !r2.merchantId && !r2.personId && !r2.isTransfer && !r2.isIgnored);
|
|
59278
|
+
const directLinks = [];
|
|
59163
59279
|
const groups = new Map;
|
|
59164
59280
|
for (const t2 of candidates) {
|
|
59165
59281
|
const { name, upiId, paymentMethod } = parseNarration(t2.description);
|
|
@@ -59167,6 +59283,11 @@ async function backfillPersons(client, crypto3, opts = {}) {
|
|
|
59167
59283
|
continue;
|
|
59168
59284
|
if (classifyEntity({ paymentMethod, extractedName: name }) !== "person")
|
|
59169
59285
|
continue;
|
|
59286
|
+
const handlePersonId = upiId ? personIdByHandle.get(normalizeUpiHandle(upiId)) : undefined;
|
|
59287
|
+
if (handlePersonId) {
|
|
59288
|
+
directLinks.push({ transactionId: t2.id, personId: handlePersonId, upiHandle: upiId });
|
|
59289
|
+
continue;
|
|
59290
|
+
}
|
|
59170
59291
|
const norm = normalizePersonName(name);
|
|
59171
59292
|
if (!norm)
|
|
59172
59293
|
continue;
|
|
@@ -59182,7 +59303,7 @@ async function backfillPersons(client, crypto3, opts = {}) {
|
|
|
59182
59303
|
}
|
|
59183
59304
|
const plan = [];
|
|
59184
59305
|
const newGroups = [];
|
|
59185
|
-
|
|
59306
|
+
const reusedPersonIds = new Set;
|
|
59186
59307
|
for (const [norm, g2] of groups) {
|
|
59187
59308
|
const upiHandles = [...g2.upiHandles];
|
|
59188
59309
|
const isReuse = personIdByNorm.has(norm);
|
|
@@ -59194,11 +59315,34 @@ async function backfillPersons(client, crypto3, opts = {}) {
|
|
|
59194
59315
|
action: isReuse ? "reuse" : "create"
|
|
59195
59316
|
});
|
|
59196
59317
|
if (isReuse)
|
|
59197
|
-
|
|
59318
|
+
reusedPersonIds.add(personIdByNorm.get(norm));
|
|
59198
59319
|
else
|
|
59199
59320
|
newGroups.push({ norm, displayName: g2.displayName, upiHandles });
|
|
59200
59321
|
}
|
|
59201
|
-
const
|
|
59322
|
+
const directByPerson = new Map;
|
|
59323
|
+
for (const d2 of directLinks) {
|
|
59324
|
+
const e2 = directByPerson.get(d2.personId) ?? { txnIds: [], handles: new Set };
|
|
59325
|
+
e2.txnIds.push(d2.transactionId);
|
|
59326
|
+
if (d2.upiHandle)
|
|
59327
|
+
e2.handles.add(d2.upiHandle);
|
|
59328
|
+
directByPerson.set(d2.personId, e2);
|
|
59329
|
+
}
|
|
59330
|
+
for (const [personId, e2] of directByPerson) {
|
|
59331
|
+
const person = existing.find((p2) => p2.id === personId);
|
|
59332
|
+
plan.push({
|
|
59333
|
+
name: person?.name ?? personId,
|
|
59334
|
+
normalized: person ? normalizePersonName(person.name) : personId,
|
|
59335
|
+
txnCount: e2.txnIds.length,
|
|
59336
|
+
upiHandles: [...e2.handles],
|
|
59337
|
+
action: "reuse"
|
|
59338
|
+
});
|
|
59339
|
+
reusedPersonIds.add(personId);
|
|
59340
|
+
}
|
|
59341
|
+
const reused = reusedPersonIds.size;
|
|
59342
|
+
const hash2 = planHash([
|
|
59343
|
+
...[...groups.values()].flatMap((g2) => g2.txnIds),
|
|
59344
|
+
...directLinks.map((d2) => d2.transactionId)
|
|
59345
|
+
]);
|
|
59202
59346
|
if (!opts.dryRun && opts.expectedPlanHash !== undefined && opts.expectedPlanHash !== hash2) {
|
|
59203
59347
|
return {
|
|
59204
59348
|
dryRun: false,
|
|
@@ -59240,6 +59384,7 @@ async function backfillPersons(client, crypto3, opts = {}) {
|
|
|
59240
59384
|
for (const txnId of g2.txnIds)
|
|
59241
59385
|
links.push({ transactionId: txnId, personId });
|
|
59242
59386
|
}
|
|
59387
|
+
links.push(...directLinks);
|
|
59243
59388
|
}
|
|
59244
59389
|
if (!opts.dryRun && links.length > 0) {
|
|
59245
59390
|
const CHUNK = 25;
|
|
@@ -59380,7 +59525,13 @@ async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
|
|
|
59380
59525
|
}
|
|
59381
59526
|
for (const g2 of hits) {
|
|
59382
59527
|
claimed.add(g2);
|
|
59383
|
-
plan.push({
|
|
59528
|
+
plan.push({
|
|
59529
|
+
group: g2,
|
|
59530
|
+
categoryId: cat.id,
|
|
59531
|
+
slug: cat.slug,
|
|
59532
|
+
learn: a2.learn !== false,
|
|
59533
|
+
confirmMerchant: !!a2.confirmMerchant
|
|
59534
|
+
});
|
|
59384
59535
|
}
|
|
59385
59536
|
}
|
|
59386
59537
|
result.planHash = planHash(plan.flatMap((p2) => p2.group.rows.map((r2) => r2.id)));
|
|
@@ -59414,10 +59565,10 @@ async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
|
|
|
59414
59565
|
const learnPlans = [];
|
|
59415
59566
|
const directPlans = [];
|
|
59416
59567
|
for (const p2 of plan) {
|
|
59417
|
-
const { group, learn } = p2;
|
|
59568
|
+
const { group, learn, confirmMerchant } = p2;
|
|
59418
59569
|
if (!learn || group.pattern === UNPARSED) {
|
|
59419
59570
|
directPlans.push({ p: p2, reason: learn ? UNPARSED : undefined });
|
|
59420
|
-
} else if (crypto3 && group.rows.some((r2) => isPersonLikeNarration(r2.description))) {
|
|
59571
|
+
} else if (crypto3 && !confirmMerchant && group.rows.some((r2) => isPersonLikeNarration(r2.description))) {
|
|
59421
59572
|
directPlans.push({ p: p2, reason: `${group.pattern} (private mode: person-like)` });
|
|
59422
59573
|
} else if (taught.has(group.pattern) && taught.get(group.pattern) !== p2.categoryId) {
|
|
59423
59574
|
directPlans.push({ p: p2, reason: `${group.pattern} (conflicting category for same pattern)` });
|
|
@@ -59723,7 +59874,8 @@ function transactionTools(client, crypto3) {
|
|
|
59723
59874
|
pattern: exports_external.string().describe("Exactly as returned"),
|
|
59724
59875
|
type: exports_external.enum(["debit", "credit"]).optional(),
|
|
59725
59876
|
categorySlug: exports_external.string(),
|
|
59726
|
-
learn: exports_external.boolean().optional().describe("false = no rule (short/generic patterns)")
|
|
59877
|
+
learn: exports_external.boolean().optional().describe("false = no rule (short/generic patterns)"),
|
|
59878
|
+
confirmMerchant: exports_external.boolean().optional().describe("true = you've confirmed this pattern is a real business (brand lookup, web search, etc), not a person — skips the private-mode person-like block that would otherwise refuse to save a merchant rule")
|
|
59727
59879
|
})).min(1).max(100),
|
|
59728
59880
|
startDate: exports_external.string().optional().describe("Same range as the listing"),
|
|
59729
59881
|
endDate: exports_external.string().optional(),
|
|
@@ -60809,7 +60961,14 @@ function scrubCiphertext(value) {
|
|
|
60809
60961
|
}
|
|
60810
60962
|
async function runTool(tool, args, mcp) {
|
|
60811
60963
|
const input2 = tool.input ? await tool.input.parseAsync(args) : args;
|
|
60812
|
-
const ctx = {
|
|
60964
|
+
const ctx = {
|
|
60965
|
+
tool: tool.name,
|
|
60966
|
+
signal: mcp.mcpReq.signal,
|
|
60967
|
+
mcp,
|
|
60968
|
+
confirmSupport: { getClientCapabilities: () => {
|
|
60969
|
+
return;
|
|
60970
|
+
} }
|
|
60971
|
+
};
|
|
60813
60972
|
return tool.handler(input2, ctx);
|
|
60814
60973
|
}
|
|
60815
60974
|
function jsonContents(uri, value) {
|
|
@@ -60844,7 +61003,7 @@ function registerResources2(server2, tools) {
|
|
|
60844
61003
|
// package.json
|
|
60845
61004
|
var package_default = {
|
|
60846
61005
|
name: "paisa-mcp",
|
|
60847
|
-
version: "0.4.
|
|
61006
|
+
version: "0.4.2",
|
|
60848
61007
|
repository: {
|
|
60849
61008
|
type: "git",
|
|
60850
61009
|
url: "git+https://github.com/nimit9/paisa.git",
|
|
@@ -60873,7 +61032,7 @@ var package_default = {
|
|
|
60873
61032
|
"@modelcontextprotocol/server": "^2.1.0",
|
|
60874
61033
|
"@nimit9/signet-ai": "^0.1.11",
|
|
60875
61034
|
"@nimit9/signet-lib": "^0.1.15",
|
|
60876
|
-
"@nimit9/signet-server": "^0.2.
|
|
61035
|
+
"@nimit9/signet-server": "^0.2.7",
|
|
60877
61036
|
"@paisa/parsers": "workspace:*",
|
|
60878
61037
|
"@paisa/reconciliation": "workspace:*",
|
|
60879
61038
|
"@paisa/types": "workspace:*",
|
|
@@ -60899,17 +61058,14 @@ function paisaErrorResult(err) {
|
|
|
60899
61058
|
content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }]
|
|
60900
61059
|
});
|
|
60901
61060
|
}
|
|
61061
|
+
var CONFIRM_KEY2 = randomBytes(32);
|
|
60902
61062
|
function buildServer(client, crypto3) {
|
|
60903
|
-
let initializeCapabilities = () => {
|
|
60904
|
-
return;
|
|
60905
|
-
};
|
|
60906
61063
|
const tools = applyAnnotations(REGISTRATIONS.flatMap((group) => group.tools(client, crypto3)).map((t2) => {
|
|
60907
|
-
const caps = () => initializeCapabilities();
|
|
60908
61064
|
const describe3 = CONFIRMED_TOOLS[t2.name];
|
|
60909
61065
|
if (describe3)
|
|
60910
|
-
return withConfirmation(t2, describe3
|
|
61066
|
+
return withConfirmation(t2, describe3);
|
|
60911
61067
|
const count = BULK_CONFIRMED_TOOLS[t2.name];
|
|
60912
|
-
return count ? withBulkConfirmation(t2, count
|
|
61068
|
+
return count ? withBulkConfirmation(t2, count) : t2;
|
|
60913
61069
|
}));
|
|
60914
61070
|
for (const t2 of tools)
|
|
60915
61071
|
if (t2.input)
|
|
@@ -60918,9 +61074,9 @@ function buildServer(client, crypto3) {
|
|
|
60918
61074
|
name: "paisa",
|
|
60919
61075
|
version: VERSION2,
|
|
60920
61076
|
tools,
|
|
60921
|
-
errorResult: paisaErrorResult
|
|
61077
|
+
errorResult: paisaErrorResult,
|
|
61078
|
+
confirm: { key: CONFIRM_KEY2 }
|
|
60922
61079
|
});
|
|
60923
|
-
initializeCapabilities = () => server2.server.getClientCapabilities();
|
|
60924
61080
|
registerResources2(server2, tools);
|
|
60925
61081
|
registerPrompts2(server2);
|
|
60926
61082
|
return server2;
|
|
@@ -60947,5 +61103,5 @@ main().catch((err) => {
|
|
|
60947
61103
|
process.exit(1);
|
|
60948
61104
|
});
|
|
60949
61105
|
|
|
60950
|
-
//# debugId=
|
|
61106
|
+
//# debugId=24F552E300AC132164756E2164756E21
|
|
60951
61107
|
//# sourceMappingURL=index.js.map
|