paisa-mcp 0.4.0 → 0.4.1
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 +322 -43
- package/dist/index.js.map +16 -11
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -53442,7 +53442,24 @@ async function initCrypto(client, pin, userId) {
|
|
|
53442
53442
|
// ../../node_modules/.bun/@modelcontextprotocol+server@2.1.0/node_modules/@modelcontextprotocol/server/dist/index.mjs
|
|
53443
53443
|
var DEFAULT_MAX_REQUEST_BODY_SIZE = 4 * 1024 * 1024;
|
|
53444
53444
|
|
|
53445
|
-
// ../../node_modules/.bun/@nimit9+signet-server@0.2.
|
|
53445
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.6+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/interaction.js
|
|
53446
|
+
async function progress(ctx, done, total, message) {
|
|
53447
|
+
const token = ctx.mcp.mcpReq._meta?.progressToken;
|
|
53448
|
+
if (token === undefined)
|
|
53449
|
+
return;
|
|
53450
|
+
try {
|
|
53451
|
+
await ctx.mcp.mcpReq.notify({
|
|
53452
|
+
method: "notifications/progress",
|
|
53453
|
+
params: {
|
|
53454
|
+
progressToken: token,
|
|
53455
|
+
progress: done,
|
|
53456
|
+
...total !== undefined ? { total } : {},
|
|
53457
|
+
...message ? { message } : {}
|
|
53458
|
+
}
|
|
53459
|
+
});
|
|
53460
|
+
} catch {}
|
|
53461
|
+
}
|
|
53462
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.6+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/http/status-codes.js
|
|
53446
53463
|
var STATUS_CODES = {
|
|
53447
53464
|
400: "BAD_REQUEST",
|
|
53448
53465
|
401: "UNAUTHORIZED",
|
|
@@ -53456,14 +53473,7 @@ var STATUS_CODES = {
|
|
|
53456
53473
|
429: "RATE_LIMITED"
|
|
53457
53474
|
};
|
|
53458
53475
|
|
|
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
|
-
}
|
|
53476
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.6+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/errors.js
|
|
53467
53477
|
function isZodError(err) {
|
|
53468
53478
|
return err instanceof Error && err.name === "ZodError" && Array.isArray(err.issues);
|
|
53469
53479
|
}
|
|
@@ -53512,10 +53522,10 @@ function toolErrorResult(info) {
|
|
|
53512
53522
|
};
|
|
53513
53523
|
}
|
|
53514
53524
|
var INTERNAL_ERROR2 = { code: "INTERNAL_ERROR", message: "Internal error" };
|
|
53515
|
-
function report(opts, err,
|
|
53525
|
+
function report(opts, err, meta3, c) {
|
|
53516
53526
|
let pending;
|
|
53517
53527
|
try {
|
|
53518
|
-
pending = opts.onError?.(err,
|
|
53528
|
+
pending = opts.onError?.(err, meta3);
|
|
53519
53529
|
} catch {
|
|
53520
53530
|
return;
|
|
53521
53531
|
}
|
|
@@ -53526,9 +53536,162 @@ function report(opts, err, tool, c) {
|
|
|
53526
53536
|
c?.executionCtx.waitUntil(settled);
|
|
53527
53537
|
} catch {}
|
|
53528
53538
|
}
|
|
53529
|
-
|
|
53539
|
+
|
|
53540
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.6+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/primitives.js
|
|
53541
|
+
var RAW = Symbol.for("signet.mcp.rawResult");
|
|
53542
|
+
function protocolError(err, deps, meta3) {
|
|
53543
|
+
if (ProtocolError.isInstance(err))
|
|
53544
|
+
return err;
|
|
53545
|
+
const info = deps.options.mapError?.(err) ?? describeToolError(err);
|
|
53546
|
+
if (!info) {
|
|
53547
|
+
report(deps.options, err, meta3, deps.context.c);
|
|
53548
|
+
return new ProtocolError(ProtocolErrorCode.InternalError, "Internal error");
|
|
53549
|
+
}
|
|
53550
|
+
const status = isHttpException(err) ? err.status : isZodError(err) ? 400 : undefined;
|
|
53551
|
+
const code = status !== undefined && status >= 500 ? ProtocolErrorCode.InternalError : ProtocolErrorCode.InvalidParams;
|
|
53552
|
+
return new ProtocolError(code, `${info.code}: ${info.message}`, {
|
|
53553
|
+
success: false,
|
|
53554
|
+
error: info.message,
|
|
53555
|
+
code: info.code,
|
|
53556
|
+
...info.details ? { details: info.details } : {}
|
|
53557
|
+
});
|
|
53558
|
+
}
|
|
53559
|
+
function checkScopes(scopes, token) {
|
|
53560
|
+
if (!scopes?.length)
|
|
53561
|
+
return;
|
|
53562
|
+
const missing = scopes.filter((s) => !token?.scopes.includes(s));
|
|
53563
|
+
if (!missing.length)
|
|
53564
|
+
return;
|
|
53565
|
+
const message = `This token is missing the scope: ${missing.join(" ")}. Request a new token with it and try again.`;
|
|
53566
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidRequest, `insufficient_scope: ${message}`, {
|
|
53567
|
+
success: false,
|
|
53568
|
+
error: message,
|
|
53569
|
+
code: "insufficient_scope"
|
|
53570
|
+
});
|
|
53571
|
+
}
|
|
53572
|
+
function baseContext(mcp, deps) {
|
|
53573
|
+
const { c, token } = deps.context;
|
|
53574
|
+
return { signal: mcp.mcpReq.signal, mcp, ...c ? { c } : {}, ...token ? { token } : {} };
|
|
53575
|
+
}
|
|
53576
|
+
function toContents(uri, value, mimeType) {
|
|
53530
53577
|
if (value !== null && typeof value === "object" && RAW in value)
|
|
53531
53578
|
return value;
|
|
53579
|
+
if (typeof value === "string")
|
|
53580
|
+
return { contents: [{ uri: uri.href, mimeType: mimeType ?? "text/plain", text: value }] };
|
|
53581
|
+
if (value instanceof Uint8Array) {
|
|
53582
|
+
let bin = "";
|
|
53583
|
+
for (const b of value)
|
|
53584
|
+
bin += String.fromCharCode(b);
|
|
53585
|
+
return {
|
|
53586
|
+
contents: [
|
|
53587
|
+
{ uri: uri.href, mimeType: mimeType ?? "application/octet-stream", blob: btoa(bin) }
|
|
53588
|
+
]
|
|
53589
|
+
};
|
|
53590
|
+
}
|
|
53591
|
+
return {
|
|
53592
|
+
contents: [
|
|
53593
|
+
{ uri: uri.href, mimeType: mimeType ?? "application/json", text: JSON.stringify(value) }
|
|
53594
|
+
]
|
|
53595
|
+
};
|
|
53596
|
+
}
|
|
53597
|
+
function registerResources(server, resources, deps) {
|
|
53598
|
+
const register2 = server.registerResource.bind(server);
|
|
53599
|
+
for (const r of resources) {
|
|
53600
|
+
const meta3 = { tool: r.name, kind: "resource" };
|
|
53601
|
+
const ctxOf = (mcp) => ({
|
|
53602
|
+
resource: r.name,
|
|
53603
|
+
...baseContext(mcp, deps)
|
|
53604
|
+
});
|
|
53605
|
+
const run = async (uri, variables, mcp) => {
|
|
53606
|
+
checkScopes(r.scopes, deps.context.token);
|
|
53607
|
+
let value;
|
|
53608
|
+
try {
|
|
53609
|
+
const read = r.read;
|
|
53610
|
+
value = await read(r.uri !== undefined ? { uri } : { uri, variables }, ctxOf(mcp));
|
|
53611
|
+
} catch (err) {
|
|
53612
|
+
throw protocolError(err, deps, meta3);
|
|
53613
|
+
}
|
|
53614
|
+
if (value === null || value === undefined)
|
|
53615
|
+
throw new ResourceNotFoundError(uri.href);
|
|
53616
|
+
if (isInputRequiredResult(value))
|
|
53617
|
+
return value;
|
|
53618
|
+
return toContents(uri, value, r.mimeType);
|
|
53619
|
+
};
|
|
53620
|
+
const config2 = {
|
|
53621
|
+
...r.title ? { title: r.title } : {},
|
|
53622
|
+
...r.description ? { description: r.description } : {},
|
|
53623
|
+
...r.mimeType ? { mimeType: r.mimeType } : {}
|
|
53624
|
+
};
|
|
53625
|
+
if (r.uri !== undefined) {
|
|
53626
|
+
register2(r.name, r.uri, config2, (uri, mcp) => run(uri, {}, mcp));
|
|
53627
|
+
continue;
|
|
53628
|
+
}
|
|
53629
|
+
const list = r.list;
|
|
53630
|
+
const template = new ResourceTemplate(r.template, {
|
|
53631
|
+
list: list ? async (mcp) => {
|
|
53632
|
+
try {
|
|
53633
|
+
checkScopes(r.scopes, deps.context.token);
|
|
53634
|
+
} catch {
|
|
53635
|
+
return { resources: [] };
|
|
53636
|
+
}
|
|
53637
|
+
try {
|
|
53638
|
+
return { resources: await list(ctxOf(mcp)) };
|
|
53639
|
+
} catch (err) {
|
|
53640
|
+
throw protocolError(err, deps, meta3);
|
|
53641
|
+
}
|
|
53642
|
+
} : undefined
|
|
53643
|
+
});
|
|
53644
|
+
register2(r.name, template, config2, (uri, vars, mcp) => {
|
|
53645
|
+
const variables = {};
|
|
53646
|
+
for (const [k, v] of Object.entries(vars ?? {}))
|
|
53647
|
+
variables[k] = Array.isArray(v) ? v[0] ?? "" : v;
|
|
53648
|
+
return run(uri, variables, mcp);
|
|
53649
|
+
});
|
|
53650
|
+
}
|
|
53651
|
+
}
|
|
53652
|
+
function toPromptResult(value) {
|
|
53653
|
+
if (isInputRequiredResult(value))
|
|
53654
|
+
return value;
|
|
53655
|
+
if (typeof value === "string")
|
|
53656
|
+
return { messages: [{ role: "user", content: { type: "text", text: value } }] };
|
|
53657
|
+
if (Array.isArray(value))
|
|
53658
|
+
return {
|
|
53659
|
+
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)
|
|
53660
|
+
};
|
|
53661
|
+
return value;
|
|
53662
|
+
}
|
|
53663
|
+
function registerPrompts(server, prompts, deps) {
|
|
53664
|
+
const register2 = server.registerPrompt.bind(server);
|
|
53665
|
+
for (const p of prompts) {
|
|
53666
|
+
const meta3 = { tool: p.name, kind: "prompt" };
|
|
53667
|
+
const run = async (args, mcp) => {
|
|
53668
|
+
checkScopes(p.scopes, deps.context.token);
|
|
53669
|
+
try {
|
|
53670
|
+
const render = p.render;
|
|
53671
|
+
return toPromptResult(await render(args, { prompt: p.name, ...baseContext(mcp, deps) }));
|
|
53672
|
+
} catch (err) {
|
|
53673
|
+
throw protocolError(err, deps, meta3);
|
|
53674
|
+
}
|
|
53675
|
+
};
|
|
53676
|
+
register2(p.name, {
|
|
53677
|
+
...p.title ? { title: p.title } : {},
|
|
53678
|
+
...p.description ? { description: p.description } : {},
|
|
53679
|
+
...p.args ? { argsSchema: p.args } : {}
|
|
53680
|
+
}, p.args ? (args, mcp) => run(args, mcp) : (mcp) => run({}, mcp));
|
|
53681
|
+
}
|
|
53682
|
+
}
|
|
53683
|
+
|
|
53684
|
+
// ../../node_modules/.bun/@nimit9+signet-server@0.2.6+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/server.js
|
|
53685
|
+
var RAW2 = Symbol.for("signet.mcp.rawResult");
|
|
53686
|
+
function toolResult(result) {
|
|
53687
|
+
return Object.defineProperty({ ...result }, RAW2, { value: true, enumerable: false });
|
|
53688
|
+
}
|
|
53689
|
+
function defineTool(def) {
|
|
53690
|
+
return def;
|
|
53691
|
+
}
|
|
53692
|
+
function toResult(value, structured) {
|
|
53693
|
+
if (value !== null && typeof value === "object" && RAW2 in value)
|
|
53694
|
+
return value;
|
|
53532
53695
|
if (isInputRequiredResult(value))
|
|
53533
53696
|
return value;
|
|
53534
53697
|
const text = typeof value === "string" ? value : JSON.stringify(value ?? null) ?? "null";
|
|
@@ -53541,7 +53704,11 @@ function toResult(value, structured) {
|
|
|
53541
53704
|
}
|
|
53542
53705
|
function createMcpServer(options) {
|
|
53543
53706
|
const unlisted = new Set(options.tools.filter((t) => t.listed === false).map((t) => t.name));
|
|
53544
|
-
const toolsCapability = {
|
|
53707
|
+
const toolsCapability = {
|
|
53708
|
+
tools: { listChanged: false },
|
|
53709
|
+
...options.resources?.length ? { resources: { listChanged: false } } : {},
|
|
53710
|
+
...options.prompts?.length ? { prompts: { listChanged: false } } : {}
|
|
53711
|
+
};
|
|
53545
53712
|
const server = new McpServer({ name: options.name, version: options.version }, {
|
|
53546
53713
|
...unlisted.size ? {} : { capabilities: toolsCapability },
|
|
53547
53714
|
...options.instructions ? { instructions: options.instructions } : {}
|
|
@@ -53581,7 +53748,7 @@ function createMcpServer(options) {
|
|
|
53581
53748
|
} catch {}
|
|
53582
53749
|
const info = options.mapError?.(err) ?? describeToolError(err);
|
|
53583
53750
|
if (!info)
|
|
53584
|
-
report(options, err, tool.name, c);
|
|
53751
|
+
report(options, err, { tool: tool.name }, c);
|
|
53585
53752
|
if (custom2)
|
|
53586
53753
|
return custom2;
|
|
53587
53754
|
return toolErrorResult(info ?? INTERNAL_ERROR2);
|
|
@@ -53597,6 +53764,11 @@ function createMcpServer(options) {
|
|
|
53597
53764
|
restore?.();
|
|
53598
53765
|
restore = undefined;
|
|
53599
53766
|
}
|
|
53767
|
+
const deps = { options, context: { ...c ? { c } : {}, ...token ? { token } : {} } };
|
|
53768
|
+
if (options.resources?.length)
|
|
53769
|
+
registerResources(server, options.resources, deps);
|
|
53770
|
+
if (options.prompts?.length)
|
|
53771
|
+
registerPrompts(server, options.prompts, deps);
|
|
53600
53772
|
return server;
|
|
53601
53773
|
}
|
|
53602
53774
|
function hideUnlisted(server, unlisted) {
|
|
@@ -53618,7 +53790,6 @@ function hideUnlisted(server, unlisted) {
|
|
|
53618
53790
|
throw new Error("signet mcp: could not hide unlisted tools; @modelcontextprotocol/server no longer installs tools/list through setRequestHandler");
|
|
53619
53791
|
};
|
|
53620
53792
|
}
|
|
53621
|
-
|
|
53622
53793
|
// src/annotations.ts
|
|
53623
53794
|
var RO = { readOnlyHint: true, openWorldHint: false };
|
|
53624
53795
|
var WI = {
|
|
@@ -53833,6 +54004,51 @@ var CONFIRMED_TOOLS = {
|
|
|
53833
54004
|
set_encryption_mode: describeDeletes.encryptionMode,
|
|
53834
54005
|
manage_debt: describeDeletes.settleDebt
|
|
53835
54006
|
};
|
|
54007
|
+
var BULK_CONFIRM_THRESHOLD = 20;
|
|
54008
|
+
var sum = (rows, key) => Array.isArray(rows) ? rows.reduce((n, r) => n + (Number(r?.[key]) || 0), 0) : 0;
|
|
54009
|
+
var fromDryRun = (listKey, countKey) => async (args, dryRun) => {
|
|
54010
|
+
const r = await dryRun({ ...args, dryRun: true });
|
|
54011
|
+
return {
|
|
54012
|
+
n: sum(r?.[listKey], countKey),
|
|
54013
|
+
planHash: typeof r?.planHash === "string" ? r.planHash : undefined
|
|
54014
|
+
};
|
|
54015
|
+
};
|
|
54016
|
+
var BULK_CONFIRMED_TOOLS = {
|
|
54017
|
+
categorize_groups: fromDryRun("applied", "rows"),
|
|
54018
|
+
backfill_persons: fromDryRun("plan", "txnCount"),
|
|
54019
|
+
bulk_categorize_transactions: async (args) => ({
|
|
54020
|
+
n: Array.isArray(args.links) ? args.links.length : 0
|
|
54021
|
+
}),
|
|
54022
|
+
bulk_link_entity: async (args) => ({ n: Array.isArray(args.links) ? args.links.length : 0 })
|
|
54023
|
+
};
|
|
54024
|
+
var PLAN_CHANGED = {
|
|
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) {
|
|
54029
|
+
const run = tool.handler;
|
|
54030
|
+
const runPlanned = async (args, ctx, hash2) => {
|
|
54031
|
+
const result = await run(hash2 === undefined ? args : { ...args, expectedPlanHash: hash2 }, ctx);
|
|
54032
|
+
return result?.planChanged ? PLAN_CHANGED : result;
|
|
54033
|
+
};
|
|
54034
|
+
return {
|
|
54035
|
+
...tool,
|
|
54036
|
+
handler: async (args, ctx) => {
|
|
54037
|
+
if (args.dryRun === true || !ctx?.mcp || !clientCanConfirm(ctx.mcp, initializeCapabilities))
|
|
54038
|
+
return run(args, ctx);
|
|
54039
|
+
const { n, planHash: hash2 } = await count(args, async (a) => run(a, ctx));
|
|
54040
|
+
if (n <= BULK_CONFIRM_THRESHOLD)
|
|
54041
|
+
return runPlanned(args, ctx, hash2);
|
|
54042
|
+
const message = `Update ${plural2(n, "transaction")} in one go?`;
|
|
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;
|
|
54049
|
+
}
|
|
54050
|
+
};
|
|
54051
|
+
}
|
|
53836
54052
|
// src/lib/union-tool.ts
|
|
53837
54053
|
function variant(input2, run) {
|
|
53838
54054
|
return { input: input2, run };
|
|
@@ -54025,15 +54241,15 @@ var PROMPTS = [
|
|
|
54025
54241
|
`)
|
|
54026
54242
|
}
|
|
54027
54243
|
];
|
|
54028
|
-
function
|
|
54244
|
+
function registerPrompts2(server2) {
|
|
54029
54245
|
for (const p of PROMPTS) {
|
|
54030
54246
|
const cb = (args) => ({
|
|
54031
54247
|
messages: [{ role: "user", content: { type: "text", text: p.text(args) } }]
|
|
54032
54248
|
});
|
|
54033
54249
|
if (p.args) {
|
|
54034
|
-
|
|
54250
|
+
server2.registerPrompt(p.name, { title: p.title, description: p.description, argsSchema: p.args }, (args) => cb(args));
|
|
54035
54251
|
} else {
|
|
54036
|
-
|
|
54252
|
+
server2.registerPrompt(p.name, { title: p.title, description: p.description }, () => cb({}));
|
|
54037
54253
|
}
|
|
54038
54254
|
}
|
|
54039
54255
|
}
|
|
@@ -58808,6 +59024,12 @@ function parseAmount4(raw) {
|
|
|
58808
59024
|
const num = parseFloat(cleaned);
|
|
58809
59025
|
return Number.isNaN(num) ? 0 : Math.abs(num);
|
|
58810
59026
|
}
|
|
59027
|
+
// src/lib/plan-hash.ts
|
|
59028
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
59029
|
+
function planHash(ids) {
|
|
59030
|
+
return createHash2("sha256").update([...ids].sort().join(",")).digest("hex");
|
|
59031
|
+
}
|
|
59032
|
+
|
|
58811
59033
|
// src/lib/txn-rows.ts
|
|
58812
59034
|
function toTxnRow(r2) {
|
|
58813
59035
|
return {
|
|
@@ -58976,6 +59198,20 @@ async function backfillPersons(client, crypto3, opts = {}) {
|
|
|
58976
59198
|
else
|
|
58977
59199
|
newGroups.push({ norm, displayName: g2.displayName, upiHandles });
|
|
58978
59200
|
}
|
|
59201
|
+
const hash2 = planHash([...groups.values()].flatMap((g2) => g2.txnIds));
|
|
59202
|
+
if (!opts.dryRun && opts.expectedPlanHash !== undefined && opts.expectedPlanHash !== hash2) {
|
|
59203
|
+
return {
|
|
59204
|
+
dryRun: false,
|
|
59205
|
+
truncated,
|
|
59206
|
+
candidatesScanned: candidates.length,
|
|
59207
|
+
created: 0,
|
|
59208
|
+
reused: 0,
|
|
59209
|
+
linked: 0,
|
|
59210
|
+
plan,
|
|
59211
|
+
planHash: hash2,
|
|
59212
|
+
planChanged: true
|
|
59213
|
+
};
|
|
59214
|
+
}
|
|
58979
59215
|
if (!opts.dryRun && newGroups.length > 0) {
|
|
58980
59216
|
const bodies = await Promise.all(newGroups.map(async ({ displayName, upiHandles }) => {
|
|
58981
59217
|
const fields = {
|
|
@@ -59009,6 +59245,7 @@ async function backfillPersons(client, crypto3, opts = {}) {
|
|
|
59009
59245
|
const CHUNK = 25;
|
|
59010
59246
|
for (let i2 = 0;i2 < links.length; i2 += CHUNK) {
|
|
59011
59247
|
await client.post("/api/transactions/bulk-link-entity", { links: links.slice(i2, i2 + CHUNK) });
|
|
59248
|
+
await opts.onProgress?.(Math.min(i2 + CHUNK, links.length), links.length, "Linking transactions to people");
|
|
59012
59249
|
}
|
|
59013
59250
|
}
|
|
59014
59251
|
return {
|
|
@@ -59018,7 +59255,8 @@ async function backfillPersons(client, crypto3, opts = {}) {
|
|
|
59018
59255
|
created,
|
|
59019
59256
|
reused,
|
|
59020
59257
|
linked: links.length,
|
|
59021
|
-
plan
|
|
59258
|
+
plan,
|
|
59259
|
+
planHash: hash2
|
|
59022
59260
|
};
|
|
59023
59261
|
}
|
|
59024
59262
|
|
|
@@ -59145,6 +59383,10 @@ async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
|
|
|
59145
59383
|
plan.push({ group: g2, categoryId: cat.id, slug: cat.slug, learn: a2.learn !== false });
|
|
59146
59384
|
}
|
|
59147
59385
|
}
|
|
59386
|
+
result.planHash = planHash(plan.flatMap((p2) => p2.group.rows.map((r2) => r2.id)));
|
|
59387
|
+
if (!opts.dryRun && opts.expectedPlanHash !== undefined && opts.expectedPlanHash !== result.planHash) {
|
|
59388
|
+
return { ...result, planChanged: true };
|
|
59389
|
+
}
|
|
59148
59390
|
if (opts.dryRun) {
|
|
59149
59391
|
result.applied = plan.map((p2) => ({
|
|
59150
59392
|
pattern: p2.group.pattern,
|
|
@@ -59232,7 +59474,11 @@ async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
|
|
|
59232
59474
|
}
|
|
59233
59475
|
if (result.rulesSaved > 0)
|
|
59234
59476
|
await saveBag(client, bag, version2);
|
|
59477
|
+
const total = links.length + direct.reduce((n2, d2) => n2 + d2.ids.length, 0);
|
|
59478
|
+
let done = 0;
|
|
59235
59479
|
await postLinks(client, links);
|
|
59480
|
+
done += links.length;
|
|
59481
|
+
await opts.onProgress?.(done, total, "Categorizing transactions");
|
|
59236
59482
|
for (const { ids, categoryId } of direct) {
|
|
59237
59483
|
const categoryType = catIndex.byId.get(categoryId)?.type;
|
|
59238
59484
|
for (let i2 = 0;i2 < ids.length; i2 += 200) {
|
|
@@ -59241,11 +59487,20 @@ async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
|
|
|
59241
59487
|
categoryId,
|
|
59242
59488
|
...categoryType ? { categoryType } : {}
|
|
59243
59489
|
});
|
|
59490
|
+
done += Math.min(200, ids.length - i2);
|
|
59491
|
+
await opts.onProgress?.(done, total, "Categorizing transactions");
|
|
59244
59492
|
}
|
|
59245
59493
|
}
|
|
59246
59494
|
return result;
|
|
59247
59495
|
}
|
|
59248
59496
|
|
|
59497
|
+
// src/lib/progress.ts
|
|
59498
|
+
function progressFor(ctx) {
|
|
59499
|
+
if (!ctx?.mcp)
|
|
59500
|
+
return;
|
|
59501
|
+
return (done, total, message) => progress(ctx, done, total, message);
|
|
59502
|
+
}
|
|
59503
|
+
|
|
59249
59504
|
// src/tools/transactions.ts
|
|
59250
59505
|
function dedupeKey(date5, description, amount, type) {
|
|
59251
59506
|
return `${date5}|${description}|${amount.toFixed(2)}|${type}`;
|
|
@@ -59293,6 +59548,7 @@ async function importTransactions(client, crypto3, opts) {
|
|
|
59293
59548
|
transactions: rows.slice(i2, i2 + CHUNK)
|
|
59294
59549
|
});
|
|
59295
59550
|
imported += res.imported ?? 0;
|
|
59551
|
+
await opts.onProgress?.(Math.min(i2 + CHUNK, rows.length), rows.length, "Importing transactions");
|
|
59296
59552
|
}
|
|
59297
59553
|
return { imported, duplicatesSkipped, duplicatesFlagged };
|
|
59298
59554
|
}
|
|
@@ -59473,17 +59729,27 @@ function transactionTools(client, crypto3) {
|
|
|
59473
59729
|
endDate: exports_external.string().optional(),
|
|
59474
59730
|
dryRun: exports_external.boolean().optional()
|
|
59475
59731
|
}),
|
|
59476
|
-
handler: (
|
|
59477
|
-
dryRun
|
|
59478
|
-
|
|
59479
|
-
|
|
59480
|
-
|
|
59732
|
+
handler: (args, ctx) => {
|
|
59733
|
+
const { assignments, startDate, endDate, dryRun } = args;
|
|
59734
|
+
const { expectedPlanHash } = args;
|
|
59735
|
+
return applyGroupAssignments(client, crypto3, assignments, {
|
|
59736
|
+
dryRun,
|
|
59737
|
+
startDate,
|
|
59738
|
+
endDate,
|
|
59739
|
+
expectedPlanHash,
|
|
59740
|
+
onProgress: progressFor(ctx)
|
|
59741
|
+
});
|
|
59742
|
+
}
|
|
59481
59743
|
}),
|
|
59482
59744
|
defineTool({
|
|
59483
59745
|
name: "backfill_persons",
|
|
59484
59746
|
description: "Create persons from UPI/IMPS counterparties in unlinked transactions (deduped by name) and link those transactions to them. Idempotent. dryRun previews.",
|
|
59485
59747
|
input: exports_external.object({ dryRun: exports_external.boolean().optional() }),
|
|
59486
|
-
handler: (
|
|
59748
|
+
handler: (args, ctx) => backfillPersons(client, crypto3, {
|
|
59749
|
+
onProgress: progressFor(ctx),
|
|
59750
|
+
dryRun: args.dryRun,
|
|
59751
|
+
expectedPlanHash: args.expectedPlanHash
|
|
59752
|
+
})
|
|
59487
59753
|
}),
|
|
59488
59754
|
defineTool({
|
|
59489
59755
|
name: "update_transaction",
|
|
@@ -60210,7 +60476,7 @@ async function fetchReconciliationContext(client, accountId, start, end, budget
|
|
|
60210
60476
|
const seen = new Set;
|
|
60211
60477
|
return [...left, ...right].filter((r2) => r2.id == null ? true : !seen.has(r2.id) && seen.add(r2.id));
|
|
60212
60478
|
}
|
|
60213
|
-
var
|
|
60479
|
+
var sum2 = (rows) => Math.round(rows.reduce((n2, r2) => n2 + r2.amount, 0));
|
|
60214
60480
|
async function runStatementImport(deps, args) {
|
|
60215
60481
|
const { client, crypto: crypto3 } = deps;
|
|
60216
60482
|
const accounts = (await client.get("/api/accounts")).data ?? [];
|
|
@@ -60252,8 +60518,8 @@ async function runStatementImport(deps, args) {
|
|
|
60252
60518
|
conflictCount: result.conflicts.length,
|
|
60253
60519
|
new: {
|
|
60254
60520
|
n: fresh.length,
|
|
60255
|
-
debits: { n: debits.length, total:
|
|
60256
|
-
credits: { n: credits.length, total:
|
|
60521
|
+
debits: { n: debits.length, total: sum2(debits) },
|
|
60522
|
+
credits: { n: credits.length, total: sum2(credits) }
|
|
60257
60523
|
}
|
|
60258
60524
|
};
|
|
60259
60525
|
if (!args.confirm) {
|
|
@@ -60273,6 +60539,7 @@ async function runStatementImport(deps, args) {
|
|
|
60273
60539
|
return { ...summary, dryRun: false, imported: 0 };
|
|
60274
60540
|
const savings = account.type !== "credit_card";
|
|
60275
60541
|
const imported = await importTransactions(client, crypto3, {
|
|
60542
|
+
onProgress: deps.onProgress,
|
|
60276
60543
|
accountId: account.id,
|
|
60277
60544
|
source: savings ? "bank_statement" : "cc_statement",
|
|
60278
60545
|
granularity: savings ? 1 : 2,
|
|
@@ -60321,7 +60588,13 @@ var statementSource = exports_external.enum([
|
|
|
60321
60588
|
]);
|
|
60322
60589
|
var granularity = exports_external.number().int().min(1).max(5).describe("1=bank, 2=CC, 3=manual, 4=app export, 5=telegram/AI");
|
|
60323
60590
|
function statementImportTools(client, crypto3) {
|
|
60324
|
-
const pdf = (
|
|
60591
|
+
const pdf = (confirm2) => variant(pdfInput, (args, ctx) => runStatementImport({
|
|
60592
|
+
client,
|
|
60593
|
+
crypto: crypto3,
|
|
60594
|
+
readFile: readFileAsArrayBuffer,
|
|
60595
|
+
parsers: realParsers,
|
|
60596
|
+
onProgress: progressFor(ctx)
|
|
60597
|
+
}, confirm2 ? { ...args, confirm: true } : args));
|
|
60325
60598
|
const csvParse = variant(exports_external.object({ filePath: exports_external.string() }), async ({ filePath }) => {
|
|
60326
60599
|
if (crypto3)
|
|
60327
60600
|
return CSV_PRIVATE_MODE_ERROR;
|
|
@@ -60382,8 +60655,9 @@ function statementImportTools(client, crypto3) {
|
|
|
60382
60655
|
description: exports_external.string(),
|
|
60383
60656
|
referenceNumber: exports_external.string().optional()
|
|
60384
60657
|
}))
|
|
60385
|
-
}), async ({ accountId, source, granularity: granularity2, transactions: txns }) => {
|
|
60658
|
+
}), async ({ accountId, source, granularity: granularity2, transactions: txns }, ctx) => {
|
|
60386
60659
|
const data = await importTransactions(client, crypto3, {
|
|
60660
|
+
onProgress: progressFor(ctx),
|
|
60387
60661
|
accountId,
|
|
60388
60662
|
source,
|
|
60389
60663
|
granularity: granularity2,
|
|
@@ -60397,7 +60671,7 @@ function statementImportTools(client, crypto3) {
|
|
|
60397
60671
|
source: statementSource,
|
|
60398
60672
|
granularity,
|
|
60399
60673
|
filePath: exports_external.string()
|
|
60400
|
-
}), async ({ accountId, source, granularity: granularity2, filePath }) => {
|
|
60674
|
+
}), async ({ accountId, source, granularity: granularity2, filePath }, ctx) => {
|
|
60401
60675
|
let txns;
|
|
60402
60676
|
try {
|
|
60403
60677
|
const raw = await readFile2(filePath, "utf8");
|
|
@@ -60414,6 +60688,7 @@ function statementImportTools(client, crypto3) {
|
|
|
60414
60688
|
throw new Error(`Failed to read/parse ${filePath}: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
60415
60689
|
}
|
|
60416
60690
|
const data = await importTransactions(client, crypto3, {
|
|
60691
|
+
onProgress: progressFor(ctx),
|
|
60417
60692
|
accountId,
|
|
60418
60693
|
source,
|
|
60419
60694
|
granularity: granularity2,
|
|
@@ -60540,7 +60815,7 @@ async function runTool(tool, args, mcp) {
|
|
|
60540
60815
|
function jsonContents(uri, value) {
|
|
60541
60816
|
return { contents: [{ uri: uri.href, mimeType: JSON_MIME, text: JSON.stringify(value ?? null) }] };
|
|
60542
60817
|
}
|
|
60543
|
-
function
|
|
60818
|
+
function registerResources2(server2, tools) {
|
|
60544
60819
|
const byName = new Map(tools.map((t2) => [t2.name, t2]));
|
|
60545
60820
|
const need = (name) => {
|
|
60546
60821
|
const t2 = byName.get(name);
|
|
@@ -60550,13 +60825,13 @@ function registerResources(server, tools) {
|
|
|
60550
60825
|
};
|
|
60551
60826
|
for (const r2 of RESOURCES) {
|
|
60552
60827
|
const tool = need(r2.tool);
|
|
60553
|
-
|
|
60828
|
+
server2.registerResource(r2.name, r2.uri, { title: r2.title, description: r2.description, mimeType: JSON_MIME }, async (uri, mcp) => {
|
|
60554
60829
|
const value = scrubCiphertext(await runTool(tool, r2.args, mcp));
|
|
60555
60830
|
return jsonContents(uri, r2.shape ? r2.shape(value) : value);
|
|
60556
60831
|
});
|
|
60557
60832
|
}
|
|
60558
60833
|
const getTransaction = need("get_transaction");
|
|
60559
|
-
|
|
60834
|
+
server2.registerResource("transaction", new ResourceTemplate(TRANSACTION_TEMPLATE, { list: undefined }), {
|
|
60560
60835
|
title: "Transaction",
|
|
60561
60836
|
description: "One transaction by UUID (same as get_transaction; decrypted in private mode).",
|
|
60562
60837
|
mimeType: JSON_MIME
|
|
@@ -60569,7 +60844,7 @@ function registerResources(server, tools) {
|
|
|
60569
60844
|
// package.json
|
|
60570
60845
|
var package_default = {
|
|
60571
60846
|
name: "paisa-mcp",
|
|
60572
|
-
version: "0.4.
|
|
60847
|
+
version: "0.4.1",
|
|
60573
60848
|
repository: {
|
|
60574
60849
|
type: "git",
|
|
60575
60850
|
url: "git+https://github.com/nimit9/paisa.git",
|
|
@@ -60598,7 +60873,7 @@ var package_default = {
|
|
|
60598
60873
|
"@modelcontextprotocol/server": "^2.1.0",
|
|
60599
60874
|
"@nimit9/signet-ai": "^0.1.11",
|
|
60600
60875
|
"@nimit9/signet-lib": "^0.1.15",
|
|
60601
|
-
"@nimit9/signet-server": "^0.2.
|
|
60876
|
+
"@nimit9/signet-server": "^0.2.6",
|
|
60602
60877
|
"@paisa/parsers": "workspace:*",
|
|
60603
60878
|
"@paisa/reconciliation": "workspace:*",
|
|
60604
60879
|
"@paisa/types": "workspace:*",
|
|
@@ -60629,22 +60904,26 @@ function buildServer(client, crypto3) {
|
|
|
60629
60904
|
return;
|
|
60630
60905
|
};
|
|
60631
60906
|
const tools = applyAnnotations(REGISTRATIONS.flatMap((group) => group.tools(client, crypto3)).map((t2) => {
|
|
60907
|
+
const caps = () => initializeCapabilities();
|
|
60632
60908
|
const describe3 = CONFIRMED_TOOLS[t2.name];
|
|
60633
|
-
|
|
60909
|
+
if (describe3)
|
|
60910
|
+
return withConfirmation(t2, describe3, caps);
|
|
60911
|
+
const count = BULK_CONFIRMED_TOOLS[t2.name];
|
|
60912
|
+
return count ? withBulkConfirmation(t2, count, caps) : t2;
|
|
60634
60913
|
}));
|
|
60635
60914
|
for (const t2 of tools)
|
|
60636
60915
|
if (t2.input)
|
|
60637
60916
|
withCompactJsonSchema(t2.input);
|
|
60638
|
-
const
|
|
60917
|
+
const server2 = createMcpServer({
|
|
60639
60918
|
name: "paisa",
|
|
60640
60919
|
version: VERSION2,
|
|
60641
60920
|
tools,
|
|
60642
60921
|
errorResult: paisaErrorResult
|
|
60643
60922
|
});
|
|
60644
|
-
initializeCapabilities = () =>
|
|
60645
|
-
|
|
60646
|
-
|
|
60647
|
-
return
|
|
60923
|
+
initializeCapabilities = () => server2.server.getClientCapabilities();
|
|
60924
|
+
registerResources2(server2, tools);
|
|
60925
|
+
registerPrompts2(server2);
|
|
60926
|
+
return server2;
|
|
60648
60927
|
}
|
|
60649
60928
|
|
|
60650
60929
|
// src/index.ts
|
|
@@ -60668,5 +60947,5 @@ main().catch((err) => {
|
|
|
60668
60947
|
process.exit(1);
|
|
60669
60948
|
});
|
|
60670
60949
|
|
|
60671
|
-
//# debugId=
|
|
60950
|
+
//# debugId=882108748FF621AF64756E2164756E21
|
|
60672
60951
|
//# sourceMappingURL=index.js.map
|