paisa-mcp 0.4.0 → 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 +580 -145
- package/dist/index.js.map +18 -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,170 @@ 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
|
+
}
|
|
53525
|
+
|
|
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}
|
|
53444
53553
|
|
|
53445
|
-
|
|
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
|
+
}
|
|
53578
|
+
async function progress(ctx, done, total, message) {
|
|
53579
|
+
const token = ctx.mcp.mcpReq._meta?.progressToken;
|
|
53580
|
+
if (token === undefined)
|
|
53581
|
+
return;
|
|
53582
|
+
try {
|
|
53583
|
+
await ctx.mcp.mcpReq.notify({
|
|
53584
|
+
method: "notifications/progress",
|
|
53585
|
+
params: {
|
|
53586
|
+
progressToken: token,
|
|
53587
|
+
progress: done,
|
|
53588
|
+
...total !== undefined ? { total } : {},
|
|
53589
|
+
...message ? { message } : {}
|
|
53590
|
+
}
|
|
53591
|
+
});
|
|
53592
|
+
} catch {}
|
|
53593
|
+
}
|
|
53594
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/http/status-codes.js
|
|
53446
53595
|
var STATUS_CODES = {
|
|
53447
53596
|
400: "BAD_REQUEST",
|
|
53448
53597
|
401: "UNAUTHORIZED",
|
|
@@ -53456,14 +53605,7 @@ var STATUS_CODES = {
|
|
|
53456
53605
|
429: "RATE_LIMITED"
|
|
53457
53606
|
};
|
|
53458
53607
|
|
|
53459
|
-
// ../../node_modules/.bun/@nimit9+signet-server@0.2.
|
|
53460
|
-
var RAW = Symbol.for("signet.mcp.rawResult");
|
|
53461
|
-
function toolResult(result) {
|
|
53462
|
-
return Object.defineProperty({ ...result }, RAW, { value: true, enumerable: false });
|
|
53463
|
-
}
|
|
53464
|
-
function defineTool(def) {
|
|
53465
|
-
return def;
|
|
53466
|
-
}
|
|
53608
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/errors.js
|
|
53467
53609
|
function isZodError(err) {
|
|
53468
53610
|
return err instanceof Error && err.name === "ZodError" && Array.isArray(err.issues);
|
|
53469
53611
|
}
|
|
@@ -53512,10 +53654,10 @@ function toolErrorResult(info) {
|
|
|
53512
53654
|
};
|
|
53513
53655
|
}
|
|
53514
53656
|
var INTERNAL_ERROR2 = { code: "INTERNAL_ERROR", message: "Internal error" };
|
|
53515
|
-
function report(opts, err,
|
|
53657
|
+
function report(opts, err, meta3, c) {
|
|
53516
53658
|
let pending;
|
|
53517
53659
|
try {
|
|
53518
|
-
pending = opts.onError?.(err,
|
|
53660
|
+
pending = opts.onError?.(err, meta3);
|
|
53519
53661
|
} catch {
|
|
53520
53662
|
return;
|
|
53521
53663
|
}
|
|
@@ -53526,9 +53668,168 @@ function report(opts, err, tool, c) {
|
|
|
53526
53668
|
c?.executionCtx.waitUntil(settled);
|
|
53527
53669
|
} catch {}
|
|
53528
53670
|
}
|
|
53529
|
-
|
|
53671
|
+
|
|
53672
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/primitives.js
|
|
53673
|
+
var RAW = Symbol.for("signet.mcp.rawResult");
|
|
53674
|
+
function protocolError(err, deps, meta3) {
|
|
53675
|
+
if (ProtocolError.isInstance(err))
|
|
53676
|
+
return err;
|
|
53677
|
+
const info = deps.options.mapError?.(err) ?? describeToolError(err);
|
|
53678
|
+
if (!info) {
|
|
53679
|
+
report(deps.options, err, meta3, deps.context.c);
|
|
53680
|
+
return new ProtocolError(ProtocolErrorCode.InternalError, "Internal error");
|
|
53681
|
+
}
|
|
53682
|
+
const status = isHttpException(err) ? err.status : isZodError(err) ? 400 : undefined;
|
|
53683
|
+
const code = status !== undefined && status >= 500 ? ProtocolErrorCode.InternalError : ProtocolErrorCode.InvalidParams;
|
|
53684
|
+
return new ProtocolError(code, `${info.code}: ${info.message}`, {
|
|
53685
|
+
success: false,
|
|
53686
|
+
error: info.message,
|
|
53687
|
+
code: info.code,
|
|
53688
|
+
...info.details ? { details: info.details } : {}
|
|
53689
|
+
});
|
|
53690
|
+
}
|
|
53691
|
+
function checkScopes(scopes, token) {
|
|
53692
|
+
if (!scopes?.length)
|
|
53693
|
+
return;
|
|
53694
|
+
const missing = scopes.filter((s) => !token?.scopes.includes(s));
|
|
53695
|
+
if (!missing.length)
|
|
53696
|
+
return;
|
|
53697
|
+
const message = `This token is missing the scope: ${missing.join(" ")}. Request a new token with it and try again.`;
|
|
53698
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidRequest, `insufficient_scope: ${message}`, {
|
|
53699
|
+
success: false,
|
|
53700
|
+
error: message,
|
|
53701
|
+
code: "insufficient_scope"
|
|
53702
|
+
});
|
|
53703
|
+
}
|
|
53704
|
+
function baseContext(mcp, deps) {
|
|
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
|
+
};
|
|
53713
|
+
}
|
|
53714
|
+
function toContents(uri, value, mimeType) {
|
|
53530
53715
|
if (value !== null && typeof value === "object" && RAW in value)
|
|
53531
53716
|
return value;
|
|
53717
|
+
if (typeof value === "string")
|
|
53718
|
+
return { contents: [{ uri: uri.href, mimeType: mimeType ?? "text/plain", text: value }] };
|
|
53719
|
+
if (value instanceof Uint8Array) {
|
|
53720
|
+
let bin = "";
|
|
53721
|
+
for (const b of value)
|
|
53722
|
+
bin += String.fromCharCode(b);
|
|
53723
|
+
return {
|
|
53724
|
+
contents: [
|
|
53725
|
+
{ uri: uri.href, mimeType: mimeType ?? "application/octet-stream", blob: btoa(bin) }
|
|
53726
|
+
]
|
|
53727
|
+
};
|
|
53728
|
+
}
|
|
53729
|
+
return {
|
|
53730
|
+
contents: [
|
|
53731
|
+
{ uri: uri.href, mimeType: mimeType ?? "application/json", text: JSON.stringify(value) }
|
|
53732
|
+
]
|
|
53733
|
+
};
|
|
53734
|
+
}
|
|
53735
|
+
function registerResources(server, resources, deps) {
|
|
53736
|
+
const register2 = server.registerResource.bind(server);
|
|
53737
|
+
for (const r of resources) {
|
|
53738
|
+
const meta3 = { tool: r.name, kind: "resource" };
|
|
53739
|
+
const ctxOf = (mcp) => ({
|
|
53740
|
+
resource: r.name,
|
|
53741
|
+
...baseContext(mcp, deps)
|
|
53742
|
+
});
|
|
53743
|
+
const run = async (uri, variables, mcp) => {
|
|
53744
|
+
checkScopes(r.scopes, deps.context.token);
|
|
53745
|
+
let value;
|
|
53746
|
+
try {
|
|
53747
|
+
const read = r.read;
|
|
53748
|
+
value = await read(r.uri !== undefined ? { uri } : { uri, variables }, ctxOf(mcp));
|
|
53749
|
+
} catch (err) {
|
|
53750
|
+
throw protocolError(err, deps, meta3);
|
|
53751
|
+
}
|
|
53752
|
+
if (value === null || value === undefined)
|
|
53753
|
+
throw new ResourceNotFoundError(uri.href);
|
|
53754
|
+
if (isInputRequiredResult(value))
|
|
53755
|
+
return value;
|
|
53756
|
+
return toContents(uri, value, r.mimeType);
|
|
53757
|
+
};
|
|
53758
|
+
const config2 = {
|
|
53759
|
+
...r.title ? { title: r.title } : {},
|
|
53760
|
+
...r.description ? { description: r.description } : {},
|
|
53761
|
+
...r.mimeType ? { mimeType: r.mimeType } : {}
|
|
53762
|
+
};
|
|
53763
|
+
if (r.uri !== undefined) {
|
|
53764
|
+
register2(r.name, r.uri, config2, (uri, mcp) => run(uri, {}, mcp));
|
|
53765
|
+
continue;
|
|
53766
|
+
}
|
|
53767
|
+
const list = r.list;
|
|
53768
|
+
const template = new ResourceTemplate(r.template, {
|
|
53769
|
+
list: list ? async (mcp) => {
|
|
53770
|
+
try {
|
|
53771
|
+
checkScopes(r.scopes, deps.context.token);
|
|
53772
|
+
} catch {
|
|
53773
|
+
return { resources: [] };
|
|
53774
|
+
}
|
|
53775
|
+
try {
|
|
53776
|
+
return { resources: await list(ctxOf(mcp)) };
|
|
53777
|
+
} catch (err) {
|
|
53778
|
+
throw protocolError(err, deps, meta3);
|
|
53779
|
+
}
|
|
53780
|
+
} : undefined
|
|
53781
|
+
});
|
|
53782
|
+
register2(r.name, template, config2, (uri, vars, mcp) => {
|
|
53783
|
+
const variables = {};
|
|
53784
|
+
for (const [k, v] of Object.entries(vars ?? {}))
|
|
53785
|
+
variables[k] = Array.isArray(v) ? v[0] ?? "" : v;
|
|
53786
|
+
return run(uri, variables, mcp);
|
|
53787
|
+
});
|
|
53788
|
+
}
|
|
53789
|
+
}
|
|
53790
|
+
function toPromptResult(value) {
|
|
53791
|
+
if (isInputRequiredResult(value))
|
|
53792
|
+
return value;
|
|
53793
|
+
if (typeof value === "string")
|
|
53794
|
+
return { messages: [{ role: "user", content: { type: "text", text: value } }] };
|
|
53795
|
+
if (Array.isArray(value))
|
|
53796
|
+
return {
|
|
53797
|
+
messages: value.map((m) => typeof m === "string" ? { role: "user", content: { type: "text", text: m } } : typeof m.content === "string" ? { role: m.role, content: { type: "text", text: m.content } } : m)
|
|
53798
|
+
};
|
|
53799
|
+
return value;
|
|
53800
|
+
}
|
|
53801
|
+
function registerPrompts(server, prompts, deps) {
|
|
53802
|
+
const register2 = server.registerPrompt.bind(server);
|
|
53803
|
+
for (const p of prompts) {
|
|
53804
|
+
const meta3 = { tool: p.name, kind: "prompt" };
|
|
53805
|
+
const run = async (args, mcp) => {
|
|
53806
|
+
checkScopes(p.scopes, deps.context.token);
|
|
53807
|
+
try {
|
|
53808
|
+
const render = p.render;
|
|
53809
|
+
return toPromptResult(await render(args, { prompt: p.name, ...baseContext(mcp, deps) }));
|
|
53810
|
+
} catch (err) {
|
|
53811
|
+
throw protocolError(err, deps, meta3);
|
|
53812
|
+
}
|
|
53813
|
+
};
|
|
53814
|
+
register2(p.name, {
|
|
53815
|
+
...p.title ? { title: p.title } : {},
|
|
53816
|
+
...p.description ? { description: p.description } : {},
|
|
53817
|
+
...p.args ? { argsSchema: p.args } : {}
|
|
53818
|
+
}, p.args ? (args, mcp) => run(args, mcp) : (mcp) => run({}, mcp));
|
|
53819
|
+
}
|
|
53820
|
+
}
|
|
53821
|
+
|
|
53822
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/server.js
|
|
53823
|
+
var RAW2 = Symbol.for("signet.mcp.rawResult");
|
|
53824
|
+
function toolResult(result) {
|
|
53825
|
+
return Object.defineProperty({ ...result }, RAW2, { value: true, enumerable: false });
|
|
53826
|
+
}
|
|
53827
|
+
function defineTool(def) {
|
|
53828
|
+
return def;
|
|
53829
|
+
}
|
|
53830
|
+
function toResult(value, structured) {
|
|
53831
|
+
if (value !== null && typeof value === "object" && RAW2 in value)
|
|
53832
|
+
return value;
|
|
53532
53833
|
if (isInputRequiredResult(value))
|
|
53533
53834
|
return value;
|
|
53534
53835
|
const text = typeof value === "string" ? value : JSON.stringify(value ?? null) ?? "null";
|
|
@@ -53541,10 +53842,19 @@ function toResult(value, structured) {
|
|
|
53541
53842
|
}
|
|
53542
53843
|
function createMcpServer(options) {
|
|
53543
53844
|
const unlisted = new Set(options.tools.filter((t) => t.listed === false).map((t) => t.name));
|
|
53544
|
-
const toolsCapability = {
|
|
53845
|
+
const toolsCapability = {
|
|
53846
|
+
tools: { listChanged: false },
|
|
53847
|
+
...options.resources?.length ? { resources: { listChanged: false } } : {},
|
|
53848
|
+
...options.prompts?.length ? { prompts: { listChanged: false } } : {}
|
|
53849
|
+
};
|
|
53850
|
+
const confirmCodec = options.confirm ? createRequestStateCodec({
|
|
53851
|
+
key: options.confirm.key,
|
|
53852
|
+
...options.confirm.ttlSeconds !== undefined ? { ttlSeconds: options.confirm.ttlSeconds } : {}
|
|
53853
|
+
}) : undefined;
|
|
53545
53854
|
const server = new McpServer({ name: options.name, version: options.version }, {
|
|
53546
53855
|
...unlisted.size ? {} : { capabilities: toolsCapability },
|
|
53547
|
-
...options.instructions ? { instructions: options.instructions } : {}
|
|
53856
|
+
...options.instructions ? { instructions: options.instructions } : {},
|
|
53857
|
+
...confirmCodec ? { requestState: { verify: confirmCodec.verify } } : {}
|
|
53548
53858
|
});
|
|
53549
53859
|
let restore;
|
|
53550
53860
|
if (unlisted.size) {
|
|
@@ -53559,6 +53869,10 @@ function createMcpServer(options) {
|
|
|
53559
53869
|
tool: tool.name,
|
|
53560
53870
|
signal: mcp.mcpReq.signal,
|
|
53561
53871
|
mcp,
|
|
53872
|
+
confirmSupport: {
|
|
53873
|
+
getClientCapabilities: () => server.server.getClientCapabilities(),
|
|
53874
|
+
...confirmCodec ? { mint: (payload) => confirmCodec.mint(payload, mcp) } : {}
|
|
53875
|
+
},
|
|
53562
53876
|
...c ? { c } : {},
|
|
53563
53877
|
...token ? { token } : {}
|
|
53564
53878
|
};
|
|
@@ -53581,7 +53895,7 @@ function createMcpServer(options) {
|
|
|
53581
53895
|
} catch {}
|
|
53582
53896
|
const info = options.mapError?.(err) ?? describeToolError(err);
|
|
53583
53897
|
if (!info)
|
|
53584
|
-
report(options, err, tool.name, c);
|
|
53898
|
+
report(options, err, { tool: tool.name }, c);
|
|
53585
53899
|
if (custom2)
|
|
53586
53900
|
return custom2;
|
|
53587
53901
|
return toolErrorResult(info ?? INTERNAL_ERROR2);
|
|
@@ -53597,6 +53911,18 @@ function createMcpServer(options) {
|
|
|
53597
53911
|
restore?.();
|
|
53598
53912
|
restore = undefined;
|
|
53599
53913
|
}
|
|
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
|
+
};
|
|
53922
|
+
if (options.resources?.length)
|
|
53923
|
+
registerResources(server, options.resources, deps);
|
|
53924
|
+
if (options.prompts?.length)
|
|
53925
|
+
registerPrompts(server, options.prompts, deps);
|
|
53600
53926
|
return server;
|
|
53601
53927
|
}
|
|
53602
53928
|
function hideUnlisted(server, unlisted) {
|
|
@@ -53618,7 +53944,6 @@ function hideUnlisted(server, unlisted) {
|
|
|
53618
53944
|
throw new Error("signet mcp: could not hide unlisted tools; @modelcontextprotocol/server no longer installs tools/list through setRequestHandler");
|
|
53619
53945
|
};
|
|
53620
53946
|
}
|
|
53621
|
-
|
|
53622
53947
|
// src/annotations.ts
|
|
53623
53948
|
var RO = { readOnlyHint: true, openWorldHint: false };
|
|
53624
53949
|
var WI = {
|
|
@@ -53696,84 +54021,34 @@ function applyAnnotations(tools) {
|
|
|
53696
54021
|
}
|
|
53697
54022
|
|
|
53698
54023
|
// src/lib/confirm.ts
|
|
53699
|
-
|
|
53700
|
-
var
|
|
53701
|
-
|
|
53702
|
-
|
|
53703
|
-
|
|
53704
|
-
|
|
53705
|
-
}
|
|
53706
|
-
|
|
53707
|
-
|
|
53708
|
-
|
|
53709
|
-
}
|
|
53710
|
-
|
|
53711
|
-
|
|
53712
|
-
|
|
53713
|
-
|
|
53714
|
-
|
|
53715
|
-
|
|
53716
|
-
return
|
|
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
|
-
};
|
|
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;
|
|
54032
|
+
}
|
|
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;
|
|
53764
54042
|
}
|
|
53765
|
-
function withConfirmation(tool, describe3
|
|
54043
|
+
function withConfirmation(tool, describe3) {
|
|
53766
54044
|
const run = tool.handler;
|
|
53767
54045
|
return {
|
|
53768
54046
|
...tool,
|
|
53769
|
-
handler: (args, ctx) => {
|
|
54047
|
+
handler: async (args, ctx) => {
|
|
53770
54048
|
const message = describe3(args);
|
|
53771
54049
|
if (message === null || !ctx?.mcp)
|
|
53772
54050
|
return run(args, ctx);
|
|
53773
|
-
|
|
53774
|
-
if (outcome.kind === "confirmed" || outcome.kind === "unsupported")
|
|
53775
|
-
return run(args, ctx);
|
|
53776
|
-
return outcome.result;
|
|
54051
|
+
return askThenRun(ctx, message, { tool: tool.name, args }, () => run(args, ctx));
|
|
53777
54052
|
}
|
|
53778
54053
|
};
|
|
53779
54054
|
}
|
|
@@ -53833,6 +54108,42 @@ var CONFIRMED_TOOLS = {
|
|
|
53833
54108
|
set_encryption_mode: describeDeletes.encryptionMode,
|
|
53834
54109
|
manage_debt: describeDeletes.settleDebt
|
|
53835
54110
|
};
|
|
54111
|
+
var BULK_CONFIRM_THRESHOLD = 20;
|
|
54112
|
+
var sum = (rows, key) => Array.isArray(rows) ? rows.reduce((n, r) => n + (Number(r?.[key]) || 0), 0) : 0;
|
|
54113
|
+
var fromDryRun = (listKey, countKey) => async (args, dryRun) => {
|
|
54114
|
+
const r = await dryRun({ ...args, dryRun: true });
|
|
54115
|
+
return {
|
|
54116
|
+
n: sum(r?.[listKey], countKey),
|
|
54117
|
+
planHash: typeof r?.planHash === "string" ? r.planHash : undefined
|
|
54118
|
+
};
|
|
54119
|
+
};
|
|
54120
|
+
var BULK_CONFIRMED_TOOLS = {
|
|
54121
|
+
categorize_groups: fromDryRun("applied", "rows"),
|
|
54122
|
+
backfill_persons: fromDryRun("plan", "txnCount"),
|
|
54123
|
+
bulk_categorize_transactions: async (args) => ({
|
|
54124
|
+
n: Array.isArray(args.links) ? args.links.length : 0
|
|
54125
|
+
}),
|
|
54126
|
+
bulk_link_entity: async (args) => ({ n: Array.isArray(args.links) ? args.links.length : 0 })
|
|
54127
|
+
};
|
|
54128
|
+
function withBulkConfirmation(tool, count) {
|
|
54129
|
+
const run = tool.handler;
|
|
54130
|
+
const runPlanned = async (args, ctx, hash2) => {
|
|
54131
|
+
const result = await run(hash2 === undefined ? args : { ...args, expectedPlanHash: hash2 }, ctx);
|
|
54132
|
+
return result?.planChanged ? PLAN_CHANGED : result;
|
|
54133
|
+
};
|
|
54134
|
+
return {
|
|
54135
|
+
...tool,
|
|
54136
|
+
handler: async (args, ctx) => {
|
|
54137
|
+
const canAsk = Boolean(ctx?.confirmSupport?.getClientCapabilities()?.elicitation);
|
|
54138
|
+
if (args.dryRun === true || !ctx?.mcp || !canAsk)
|
|
54139
|
+
return run(args, ctx);
|
|
54140
|
+
const { n, planHash: hash2 } = await count(args, async (a) => run(a, ctx));
|
|
54141
|
+
if (n <= BULK_CONFIRM_THRESHOLD)
|
|
54142
|
+
return runPlanned(args, ctx, hash2);
|
|
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));
|
|
54144
|
+
}
|
|
54145
|
+
};
|
|
54146
|
+
}
|
|
53836
54147
|
// src/lib/union-tool.ts
|
|
53837
54148
|
function variant(input2, run) {
|
|
53838
54149
|
return { input: input2, run };
|
|
@@ -54025,15 +54336,15 @@ var PROMPTS = [
|
|
|
54025
54336
|
`)
|
|
54026
54337
|
}
|
|
54027
54338
|
];
|
|
54028
|
-
function
|
|
54339
|
+
function registerPrompts2(server2) {
|
|
54029
54340
|
for (const p of PROMPTS) {
|
|
54030
54341
|
const cb = (args) => ({
|
|
54031
54342
|
messages: [{ role: "user", content: { type: "text", text: p.text(args) } }]
|
|
54032
54343
|
});
|
|
54033
54344
|
if (p.args) {
|
|
54034
|
-
|
|
54345
|
+
server2.registerPrompt(p.name, { title: p.title, description: p.description, argsSchema: p.args }, (args) => cb(args));
|
|
54035
54346
|
} else {
|
|
54036
|
-
|
|
54347
|
+
server2.registerPrompt(p.name, { title: p.title, description: p.description }, () => cb({}));
|
|
54037
54348
|
}
|
|
54038
54349
|
}
|
|
54039
54350
|
}
|
|
@@ -54502,7 +54813,7 @@ function goalTools(client) {
|
|
|
54502
54813
|
return [
|
|
54503
54814
|
defineTool({
|
|
54504
54815
|
name: "upsert_savings",
|
|
54505
|
-
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.",
|
|
54506
54817
|
input: exports_external.object({
|
|
54507
54818
|
type: exports_external.enum(["goal", "emi", "sinking_fund"]),
|
|
54508
54819
|
id: exports_external.string().optional(),
|
|
@@ -54523,7 +54834,8 @@ function goalTools(client) {
|
|
|
54523
54834
|
startDate: exports_external.string().optional(),
|
|
54524
54835
|
endDate: exports_external.string().optional(),
|
|
54525
54836
|
totalMonths: exports_external.number().optional(),
|
|
54526
|
-
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")
|
|
54527
54839
|
}),
|
|
54528
54840
|
handler: async ({ type, ...fields }) => {
|
|
54529
54841
|
const require2 = (keys) => {
|
|
@@ -54532,6 +54844,14 @@ function goalTools(client) {
|
|
|
54532
54844
|
throw new Error(`upsert_savings type='${type}' requires: ${missing.join(", ")}`);
|
|
54533
54845
|
}
|
|
54534
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
|
+
}
|
|
54535
54855
|
if (type === "goal") {
|
|
54536
54856
|
const {
|
|
54537
54857
|
id: id2,
|
|
@@ -54544,7 +54864,8 @@ function goalTools(client) {
|
|
|
54544
54864
|
owner,
|
|
54545
54865
|
icon,
|
|
54546
54866
|
color,
|
|
54547
|
-
notes
|
|
54867
|
+
notes,
|
|
54868
|
+
status
|
|
54548
54869
|
} = fields;
|
|
54549
54870
|
const body2 = {
|
|
54550
54871
|
type: goalType,
|
|
@@ -54556,7 +54877,8 @@ function goalTools(client) {
|
|
|
54556
54877
|
owner,
|
|
54557
54878
|
icon,
|
|
54558
54879
|
color,
|
|
54559
|
-
notes
|
|
54880
|
+
notes,
|
|
54881
|
+
status
|
|
54560
54882
|
};
|
|
54561
54883
|
if (!id2)
|
|
54562
54884
|
require2(["name", "targetAmount", "goalType"]);
|
|
@@ -54574,7 +54896,8 @@ function goalTools(client) {
|
|
|
54574
54896
|
totalMonths,
|
|
54575
54897
|
paidMonths,
|
|
54576
54898
|
interestRate,
|
|
54577
|
-
outstandingPrincipal
|
|
54899
|
+
outstandingPrincipal,
|
|
54900
|
+
status
|
|
54578
54901
|
} = fields;
|
|
54579
54902
|
const { owner } = fields;
|
|
54580
54903
|
if (!id2) {
|
|
@@ -54598,7 +54921,8 @@ function goalTools(client) {
|
|
|
54598
54921
|
paidMonths,
|
|
54599
54922
|
interestRate,
|
|
54600
54923
|
outstandingPrincipal,
|
|
54601
|
-
owner
|
|
54924
|
+
owner,
|
|
54925
|
+
status
|
|
54602
54926
|
};
|
|
54603
54927
|
const data2 = id2 ? await client.patch(`/api/goals/emis/${id2}`, body2) : await client.post("/api/goals/emis", body2);
|
|
54604
54928
|
return data2;
|
|
@@ -58808,6 +59132,12 @@ function parseAmount4(raw) {
|
|
|
58808
59132
|
const num = parseFloat(cleaned);
|
|
58809
59133
|
return Number.isNaN(num) ? 0 : Math.abs(num);
|
|
58810
59134
|
}
|
|
59135
|
+
// src/lib/plan-hash.ts
|
|
59136
|
+
import { createHash } from "node:crypto";
|
|
59137
|
+
function planHash(ids) {
|
|
59138
|
+
return createHash("sha256").update([...ids].sort().join(",")).digest("hex");
|
|
59139
|
+
}
|
|
59140
|
+
|
|
58811
59141
|
// src/lib/txn-rows.ts
|
|
58812
59142
|
function toTxnRow(r2) {
|
|
58813
59143
|
return {
|
|
@@ -58883,6 +59213,9 @@ function toLeanRow(r2, personNames) {
|
|
|
58883
59213
|
function normalizePersonName(name) {
|
|
58884
59214
|
return name.toLowerCase().trim().replace(/\s+/g, " ");
|
|
58885
59215
|
}
|
|
59216
|
+
function normalizeUpiHandle(handle) {
|
|
59217
|
+
return handle.toLowerCase().trim().split("@")[0];
|
|
59218
|
+
}
|
|
58886
59219
|
async function loadDecryptedPersons(client, crypto3) {
|
|
58887
59220
|
const res = await client.get("/api/persons");
|
|
58888
59221
|
const persons = [];
|
|
@@ -58894,7 +59227,7 @@ async function loadDecryptedPersons(client, crypto3) {
|
|
|
58894
59227
|
} catch {}
|
|
58895
59228
|
}
|
|
58896
59229
|
if (name)
|
|
58897
|
-
persons.push({ id: p2.id, name });
|
|
59230
|
+
persons.push({ id: p2.id, name, upiHandles: p2.upiHandles ?? [] });
|
|
58898
59231
|
}
|
|
58899
59232
|
return persons;
|
|
58900
59233
|
}
|
|
@@ -58934,10 +59267,15 @@ function isPersonLikeNarration(description) {
|
|
|
58934
59267
|
async function backfillPersons(client, crypto3, opts = {}) {
|
|
58935
59268
|
const existing = await loadDecryptedPersons(client, crypto3);
|
|
58936
59269
|
const personIdByNorm = new Map;
|
|
58937
|
-
|
|
59270
|
+
const personIdByHandle = new Map;
|
|
59271
|
+
for (const p2 of existing) {
|
|
58938
59272
|
personIdByNorm.set(normalizePersonName(p2.name), p2.id);
|
|
59273
|
+
for (const h2 of p2.upiHandles)
|
|
59274
|
+
personIdByHandle.set(normalizeUpiHandle(h2), p2.id);
|
|
59275
|
+
}
|
|
58939
59276
|
const { rows, truncated } = await fetchAllRowsPaged(client, crypto3, { unresolved: "true" });
|
|
58940
59277
|
const candidates = rows.filter((r2) => !r2.merchantId && !r2.personId && !r2.isTransfer && !r2.isIgnored);
|
|
59278
|
+
const directLinks = [];
|
|
58941
59279
|
const groups = new Map;
|
|
58942
59280
|
for (const t2 of candidates) {
|
|
58943
59281
|
const { name, upiId, paymentMethod } = parseNarration(t2.description);
|
|
@@ -58945,6 +59283,11 @@ async function backfillPersons(client, crypto3, opts = {}) {
|
|
|
58945
59283
|
continue;
|
|
58946
59284
|
if (classifyEntity({ paymentMethod, extractedName: name }) !== "person")
|
|
58947
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
|
+
}
|
|
58948
59291
|
const norm = normalizePersonName(name);
|
|
58949
59292
|
if (!norm)
|
|
58950
59293
|
continue;
|
|
@@ -58960,7 +59303,7 @@ async function backfillPersons(client, crypto3, opts = {}) {
|
|
|
58960
59303
|
}
|
|
58961
59304
|
const plan = [];
|
|
58962
59305
|
const newGroups = [];
|
|
58963
|
-
|
|
59306
|
+
const reusedPersonIds = new Set;
|
|
58964
59307
|
for (const [norm, g2] of groups) {
|
|
58965
59308
|
const upiHandles = [...g2.upiHandles];
|
|
58966
59309
|
const isReuse = personIdByNorm.has(norm);
|
|
@@ -58972,10 +59315,47 @@ async function backfillPersons(client, crypto3, opts = {}) {
|
|
|
58972
59315
|
action: isReuse ? "reuse" : "create"
|
|
58973
59316
|
});
|
|
58974
59317
|
if (isReuse)
|
|
58975
|
-
|
|
59318
|
+
reusedPersonIds.add(personIdByNorm.get(norm));
|
|
58976
59319
|
else
|
|
58977
59320
|
newGroups.push({ norm, displayName: g2.displayName, upiHandles });
|
|
58978
59321
|
}
|
|
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
|
+
]);
|
|
59346
|
+
if (!opts.dryRun && opts.expectedPlanHash !== undefined && opts.expectedPlanHash !== hash2) {
|
|
59347
|
+
return {
|
|
59348
|
+
dryRun: false,
|
|
59349
|
+
truncated,
|
|
59350
|
+
candidatesScanned: candidates.length,
|
|
59351
|
+
created: 0,
|
|
59352
|
+
reused: 0,
|
|
59353
|
+
linked: 0,
|
|
59354
|
+
plan,
|
|
59355
|
+
planHash: hash2,
|
|
59356
|
+
planChanged: true
|
|
59357
|
+
};
|
|
59358
|
+
}
|
|
58979
59359
|
if (!opts.dryRun && newGroups.length > 0) {
|
|
58980
59360
|
const bodies = await Promise.all(newGroups.map(async ({ displayName, upiHandles }) => {
|
|
58981
59361
|
const fields = {
|
|
@@ -59004,11 +59384,13 @@ async function backfillPersons(client, crypto3, opts = {}) {
|
|
|
59004
59384
|
for (const txnId of g2.txnIds)
|
|
59005
59385
|
links.push({ transactionId: txnId, personId });
|
|
59006
59386
|
}
|
|
59387
|
+
links.push(...directLinks);
|
|
59007
59388
|
}
|
|
59008
59389
|
if (!opts.dryRun && links.length > 0) {
|
|
59009
59390
|
const CHUNK = 25;
|
|
59010
59391
|
for (let i2 = 0;i2 < links.length; i2 += CHUNK) {
|
|
59011
59392
|
await client.post("/api/transactions/bulk-link-entity", { links: links.slice(i2, i2 + CHUNK) });
|
|
59393
|
+
await opts.onProgress?.(Math.min(i2 + CHUNK, links.length), links.length, "Linking transactions to people");
|
|
59012
59394
|
}
|
|
59013
59395
|
}
|
|
59014
59396
|
return {
|
|
@@ -59018,7 +59400,8 @@ async function backfillPersons(client, crypto3, opts = {}) {
|
|
|
59018
59400
|
created,
|
|
59019
59401
|
reused,
|
|
59020
59402
|
linked: links.length,
|
|
59021
|
-
plan
|
|
59403
|
+
plan,
|
|
59404
|
+
planHash: hash2
|
|
59022
59405
|
};
|
|
59023
59406
|
}
|
|
59024
59407
|
|
|
@@ -59142,9 +59525,19 @@ async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
|
|
|
59142
59525
|
}
|
|
59143
59526
|
for (const g2 of hits) {
|
|
59144
59527
|
claimed.add(g2);
|
|
59145
|
-
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
|
+
});
|
|
59146
59535
|
}
|
|
59147
59536
|
}
|
|
59537
|
+
result.planHash = planHash(plan.flatMap((p2) => p2.group.rows.map((r2) => r2.id)));
|
|
59538
|
+
if (!opts.dryRun && opts.expectedPlanHash !== undefined && opts.expectedPlanHash !== result.planHash) {
|
|
59539
|
+
return { ...result, planChanged: true };
|
|
59540
|
+
}
|
|
59148
59541
|
if (opts.dryRun) {
|
|
59149
59542
|
result.applied = plan.map((p2) => ({
|
|
59150
59543
|
pattern: p2.group.pattern,
|
|
@@ -59172,10 +59565,10 @@ async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
|
|
|
59172
59565
|
const learnPlans = [];
|
|
59173
59566
|
const directPlans = [];
|
|
59174
59567
|
for (const p2 of plan) {
|
|
59175
|
-
const { group, learn } = p2;
|
|
59568
|
+
const { group, learn, confirmMerchant } = p2;
|
|
59176
59569
|
if (!learn || group.pattern === UNPARSED) {
|
|
59177
59570
|
directPlans.push({ p: p2, reason: learn ? UNPARSED : undefined });
|
|
59178
|
-
} else if (crypto3 && group.rows.some((r2) => isPersonLikeNarration(r2.description))) {
|
|
59571
|
+
} else if (crypto3 && !confirmMerchant && group.rows.some((r2) => isPersonLikeNarration(r2.description))) {
|
|
59179
59572
|
directPlans.push({ p: p2, reason: `${group.pattern} (private mode: person-like)` });
|
|
59180
59573
|
} else if (taught.has(group.pattern) && taught.get(group.pattern) !== p2.categoryId) {
|
|
59181
59574
|
directPlans.push({ p: p2, reason: `${group.pattern} (conflicting category for same pattern)` });
|
|
@@ -59232,7 +59625,11 @@ async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
|
|
|
59232
59625
|
}
|
|
59233
59626
|
if (result.rulesSaved > 0)
|
|
59234
59627
|
await saveBag(client, bag, version2);
|
|
59628
|
+
const total = links.length + direct.reduce((n2, d2) => n2 + d2.ids.length, 0);
|
|
59629
|
+
let done = 0;
|
|
59235
59630
|
await postLinks(client, links);
|
|
59631
|
+
done += links.length;
|
|
59632
|
+
await opts.onProgress?.(done, total, "Categorizing transactions");
|
|
59236
59633
|
for (const { ids, categoryId } of direct) {
|
|
59237
59634
|
const categoryType = catIndex.byId.get(categoryId)?.type;
|
|
59238
59635
|
for (let i2 = 0;i2 < ids.length; i2 += 200) {
|
|
@@ -59241,11 +59638,20 @@ async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
|
|
|
59241
59638
|
categoryId,
|
|
59242
59639
|
...categoryType ? { categoryType } : {}
|
|
59243
59640
|
});
|
|
59641
|
+
done += Math.min(200, ids.length - i2);
|
|
59642
|
+
await opts.onProgress?.(done, total, "Categorizing transactions");
|
|
59244
59643
|
}
|
|
59245
59644
|
}
|
|
59246
59645
|
return result;
|
|
59247
59646
|
}
|
|
59248
59647
|
|
|
59648
|
+
// src/lib/progress.ts
|
|
59649
|
+
function progressFor(ctx) {
|
|
59650
|
+
if (!ctx?.mcp)
|
|
59651
|
+
return;
|
|
59652
|
+
return (done, total, message) => progress(ctx, done, total, message);
|
|
59653
|
+
}
|
|
59654
|
+
|
|
59249
59655
|
// src/tools/transactions.ts
|
|
59250
59656
|
function dedupeKey(date5, description, amount, type) {
|
|
59251
59657
|
return `${date5}|${description}|${amount.toFixed(2)}|${type}`;
|
|
@@ -59293,6 +59699,7 @@ async function importTransactions(client, crypto3, opts) {
|
|
|
59293
59699
|
transactions: rows.slice(i2, i2 + CHUNK)
|
|
59294
59700
|
});
|
|
59295
59701
|
imported += res.imported ?? 0;
|
|
59702
|
+
await opts.onProgress?.(Math.min(i2 + CHUNK, rows.length), rows.length, "Importing transactions");
|
|
59296
59703
|
}
|
|
59297
59704
|
return { imported, duplicatesSkipped, duplicatesFlagged };
|
|
59298
59705
|
}
|
|
@@ -59467,23 +59874,34 @@ function transactionTools(client, crypto3) {
|
|
|
59467
59874
|
pattern: exports_external.string().describe("Exactly as returned"),
|
|
59468
59875
|
type: exports_external.enum(["debit", "credit"]).optional(),
|
|
59469
59876
|
categorySlug: exports_external.string(),
|
|
59470
|
-
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")
|
|
59471
59879
|
})).min(1).max(100),
|
|
59472
59880
|
startDate: exports_external.string().optional().describe("Same range as the listing"),
|
|
59473
59881
|
endDate: exports_external.string().optional(),
|
|
59474
59882
|
dryRun: exports_external.boolean().optional()
|
|
59475
59883
|
}),
|
|
59476
|
-
handler: (
|
|
59477
|
-
dryRun
|
|
59478
|
-
|
|
59479
|
-
|
|
59480
|
-
|
|
59884
|
+
handler: (args, ctx) => {
|
|
59885
|
+
const { assignments, startDate, endDate, dryRun } = args;
|
|
59886
|
+
const { expectedPlanHash } = args;
|
|
59887
|
+
return applyGroupAssignments(client, crypto3, assignments, {
|
|
59888
|
+
dryRun,
|
|
59889
|
+
startDate,
|
|
59890
|
+
endDate,
|
|
59891
|
+
expectedPlanHash,
|
|
59892
|
+
onProgress: progressFor(ctx)
|
|
59893
|
+
});
|
|
59894
|
+
}
|
|
59481
59895
|
}),
|
|
59482
59896
|
defineTool({
|
|
59483
59897
|
name: "backfill_persons",
|
|
59484
59898
|
description: "Create persons from UPI/IMPS counterparties in unlinked transactions (deduped by name) and link those transactions to them. Idempotent. dryRun previews.",
|
|
59485
59899
|
input: exports_external.object({ dryRun: exports_external.boolean().optional() }),
|
|
59486
|
-
handler: (
|
|
59900
|
+
handler: (args, ctx) => backfillPersons(client, crypto3, {
|
|
59901
|
+
onProgress: progressFor(ctx),
|
|
59902
|
+
dryRun: args.dryRun,
|
|
59903
|
+
expectedPlanHash: args.expectedPlanHash
|
|
59904
|
+
})
|
|
59487
59905
|
}),
|
|
59488
59906
|
defineTool({
|
|
59489
59907
|
name: "update_transaction",
|
|
@@ -60210,7 +60628,7 @@ async function fetchReconciliationContext(client, accountId, start, end, budget
|
|
|
60210
60628
|
const seen = new Set;
|
|
60211
60629
|
return [...left, ...right].filter((r2) => r2.id == null ? true : !seen.has(r2.id) && seen.add(r2.id));
|
|
60212
60630
|
}
|
|
60213
|
-
var
|
|
60631
|
+
var sum2 = (rows) => Math.round(rows.reduce((n2, r2) => n2 + r2.amount, 0));
|
|
60214
60632
|
async function runStatementImport(deps, args) {
|
|
60215
60633
|
const { client, crypto: crypto3 } = deps;
|
|
60216
60634
|
const accounts = (await client.get("/api/accounts")).data ?? [];
|
|
@@ -60252,8 +60670,8 @@ async function runStatementImport(deps, args) {
|
|
|
60252
60670
|
conflictCount: result.conflicts.length,
|
|
60253
60671
|
new: {
|
|
60254
60672
|
n: fresh.length,
|
|
60255
|
-
debits: { n: debits.length, total:
|
|
60256
|
-
credits: { n: credits.length, total:
|
|
60673
|
+
debits: { n: debits.length, total: sum2(debits) },
|
|
60674
|
+
credits: { n: credits.length, total: sum2(credits) }
|
|
60257
60675
|
}
|
|
60258
60676
|
};
|
|
60259
60677
|
if (!args.confirm) {
|
|
@@ -60273,6 +60691,7 @@ async function runStatementImport(deps, args) {
|
|
|
60273
60691
|
return { ...summary, dryRun: false, imported: 0 };
|
|
60274
60692
|
const savings = account.type !== "credit_card";
|
|
60275
60693
|
const imported = await importTransactions(client, crypto3, {
|
|
60694
|
+
onProgress: deps.onProgress,
|
|
60276
60695
|
accountId: account.id,
|
|
60277
60696
|
source: savings ? "bank_statement" : "cc_statement",
|
|
60278
60697
|
granularity: savings ? 1 : 2,
|
|
@@ -60321,7 +60740,13 @@ var statementSource = exports_external.enum([
|
|
|
60321
60740
|
]);
|
|
60322
60741
|
var granularity = exports_external.number().int().min(1).max(5).describe("1=bank, 2=CC, 3=manual, 4=app export, 5=telegram/AI");
|
|
60323
60742
|
function statementImportTools(client, crypto3) {
|
|
60324
|
-
const pdf = (
|
|
60743
|
+
const pdf = (confirm2) => variant(pdfInput, (args, ctx) => runStatementImport({
|
|
60744
|
+
client,
|
|
60745
|
+
crypto: crypto3,
|
|
60746
|
+
readFile: readFileAsArrayBuffer,
|
|
60747
|
+
parsers: realParsers,
|
|
60748
|
+
onProgress: progressFor(ctx)
|
|
60749
|
+
}, confirm2 ? { ...args, confirm: true } : args));
|
|
60325
60750
|
const csvParse = variant(exports_external.object({ filePath: exports_external.string() }), async ({ filePath }) => {
|
|
60326
60751
|
if (crypto3)
|
|
60327
60752
|
return CSV_PRIVATE_MODE_ERROR;
|
|
@@ -60382,8 +60807,9 @@ function statementImportTools(client, crypto3) {
|
|
|
60382
60807
|
description: exports_external.string(),
|
|
60383
60808
|
referenceNumber: exports_external.string().optional()
|
|
60384
60809
|
}))
|
|
60385
|
-
}), async ({ accountId, source, granularity: granularity2, transactions: txns }) => {
|
|
60810
|
+
}), async ({ accountId, source, granularity: granularity2, transactions: txns }, ctx) => {
|
|
60386
60811
|
const data = await importTransactions(client, crypto3, {
|
|
60812
|
+
onProgress: progressFor(ctx),
|
|
60387
60813
|
accountId,
|
|
60388
60814
|
source,
|
|
60389
60815
|
granularity: granularity2,
|
|
@@ -60397,7 +60823,7 @@ function statementImportTools(client, crypto3) {
|
|
|
60397
60823
|
source: statementSource,
|
|
60398
60824
|
granularity,
|
|
60399
60825
|
filePath: exports_external.string()
|
|
60400
|
-
}), async ({ accountId, source, granularity: granularity2, filePath }) => {
|
|
60826
|
+
}), async ({ accountId, source, granularity: granularity2, filePath }, ctx) => {
|
|
60401
60827
|
let txns;
|
|
60402
60828
|
try {
|
|
60403
60829
|
const raw = await readFile2(filePath, "utf8");
|
|
@@ -60414,6 +60840,7 @@ function statementImportTools(client, crypto3) {
|
|
|
60414
60840
|
throw new Error(`Failed to read/parse ${filePath}: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
60415
60841
|
}
|
|
60416
60842
|
const data = await importTransactions(client, crypto3, {
|
|
60843
|
+
onProgress: progressFor(ctx),
|
|
60417
60844
|
accountId,
|
|
60418
60845
|
source,
|
|
60419
60846
|
granularity: granularity2,
|
|
@@ -60534,13 +60961,20 @@ function scrubCiphertext(value) {
|
|
|
60534
60961
|
}
|
|
60535
60962
|
async function runTool(tool, args, mcp) {
|
|
60536
60963
|
const input2 = tool.input ? await tool.input.parseAsync(args) : args;
|
|
60537
|
-
const ctx = {
|
|
60964
|
+
const ctx = {
|
|
60965
|
+
tool: tool.name,
|
|
60966
|
+
signal: mcp.mcpReq.signal,
|
|
60967
|
+
mcp,
|
|
60968
|
+
confirmSupport: { getClientCapabilities: () => {
|
|
60969
|
+
return;
|
|
60970
|
+
} }
|
|
60971
|
+
};
|
|
60538
60972
|
return tool.handler(input2, ctx);
|
|
60539
60973
|
}
|
|
60540
60974
|
function jsonContents(uri, value) {
|
|
60541
60975
|
return { contents: [{ uri: uri.href, mimeType: JSON_MIME, text: JSON.stringify(value ?? null) }] };
|
|
60542
60976
|
}
|
|
60543
|
-
function
|
|
60977
|
+
function registerResources2(server2, tools) {
|
|
60544
60978
|
const byName = new Map(tools.map((t2) => [t2.name, t2]));
|
|
60545
60979
|
const need = (name) => {
|
|
60546
60980
|
const t2 = byName.get(name);
|
|
@@ -60550,13 +60984,13 @@ function registerResources(server, tools) {
|
|
|
60550
60984
|
};
|
|
60551
60985
|
for (const r2 of RESOURCES) {
|
|
60552
60986
|
const tool = need(r2.tool);
|
|
60553
|
-
|
|
60987
|
+
server2.registerResource(r2.name, r2.uri, { title: r2.title, description: r2.description, mimeType: JSON_MIME }, async (uri, mcp) => {
|
|
60554
60988
|
const value = scrubCiphertext(await runTool(tool, r2.args, mcp));
|
|
60555
60989
|
return jsonContents(uri, r2.shape ? r2.shape(value) : value);
|
|
60556
60990
|
});
|
|
60557
60991
|
}
|
|
60558
60992
|
const getTransaction = need("get_transaction");
|
|
60559
|
-
|
|
60993
|
+
server2.registerResource("transaction", new ResourceTemplate(TRANSACTION_TEMPLATE, { list: undefined }), {
|
|
60560
60994
|
title: "Transaction",
|
|
60561
60995
|
description: "One transaction by UUID (same as get_transaction; decrypted in private mode).",
|
|
60562
60996
|
mimeType: JSON_MIME
|
|
@@ -60569,7 +61003,7 @@ function registerResources(server, tools) {
|
|
|
60569
61003
|
// package.json
|
|
60570
61004
|
var package_default = {
|
|
60571
61005
|
name: "paisa-mcp",
|
|
60572
|
-
version: "0.4.
|
|
61006
|
+
version: "0.4.2",
|
|
60573
61007
|
repository: {
|
|
60574
61008
|
type: "git",
|
|
60575
61009
|
url: "git+https://github.com/nimit9/paisa.git",
|
|
@@ -60598,7 +61032,7 @@ var package_default = {
|
|
|
60598
61032
|
"@modelcontextprotocol/server": "^2.1.0",
|
|
60599
61033
|
"@nimit9/signet-ai": "^0.1.11",
|
|
60600
61034
|
"@nimit9/signet-lib": "^0.1.15",
|
|
60601
|
-
"@nimit9/signet-server": "^0.2.
|
|
61035
|
+
"@nimit9/signet-server": "^0.2.7",
|
|
60602
61036
|
"@paisa/parsers": "workspace:*",
|
|
60603
61037
|
"@paisa/reconciliation": "workspace:*",
|
|
60604
61038
|
"@paisa/types": "workspace:*",
|
|
@@ -60624,27 +61058,28 @@ function paisaErrorResult(err) {
|
|
|
60624
61058
|
content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }]
|
|
60625
61059
|
});
|
|
60626
61060
|
}
|
|
61061
|
+
var CONFIRM_KEY2 = randomBytes(32);
|
|
60627
61062
|
function buildServer(client, crypto3) {
|
|
60628
|
-
let initializeCapabilities = () => {
|
|
60629
|
-
return;
|
|
60630
|
-
};
|
|
60631
61063
|
const tools = applyAnnotations(REGISTRATIONS.flatMap((group) => group.tools(client, crypto3)).map((t2) => {
|
|
60632
61064
|
const describe3 = CONFIRMED_TOOLS[t2.name];
|
|
60633
|
-
|
|
61065
|
+
if (describe3)
|
|
61066
|
+
return withConfirmation(t2, describe3);
|
|
61067
|
+
const count = BULK_CONFIRMED_TOOLS[t2.name];
|
|
61068
|
+
return count ? withBulkConfirmation(t2, count) : t2;
|
|
60634
61069
|
}));
|
|
60635
61070
|
for (const t2 of tools)
|
|
60636
61071
|
if (t2.input)
|
|
60637
61072
|
withCompactJsonSchema(t2.input);
|
|
60638
|
-
const
|
|
61073
|
+
const server2 = createMcpServer({
|
|
60639
61074
|
name: "paisa",
|
|
60640
61075
|
version: VERSION2,
|
|
60641
61076
|
tools,
|
|
60642
|
-
errorResult: paisaErrorResult
|
|
61077
|
+
errorResult: paisaErrorResult,
|
|
61078
|
+
confirm: { key: CONFIRM_KEY2 }
|
|
60643
61079
|
});
|
|
60644
|
-
|
|
60645
|
-
|
|
60646
|
-
|
|
60647
|
-
return server;
|
|
61080
|
+
registerResources2(server2, tools);
|
|
61081
|
+
registerPrompts2(server2);
|
|
61082
|
+
return server2;
|
|
60648
61083
|
}
|
|
60649
61084
|
|
|
60650
61085
|
// src/index.ts
|
|
@@ -60668,5 +61103,5 @@ main().catch((err) => {
|
|
|
60668
61103
|
process.exit(1);
|
|
60669
61104
|
});
|
|
60670
61105
|
|
|
60671
|
-
//# debugId=
|
|
61106
|
+
//# debugId=24F552E300AC132164756E2164756E21
|
|
60672
61107
|
//# sourceMappingURL=index.js.map
|