financy 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -1
- package/dist/bin.js +122 -50
- package/dist/{server-5G6DYQRD.js → chunk-74AQOXZZ.js} +23 -42
- package/dist/{chunk-XODXLY7E.js → chunk-QYWDNIM2.js} +155 -61
- package/dist/lib.d.ts +50 -0
- package/dist/lib.js +712 -0
- package/dist/remote/start.js +179 -0
- package/dist/server-N5ZPO432.js +37 -0
- package/package.json +13 -2
- package/skills/financy-setup/SKILL.md +26 -5
package/README.md
CHANGED
|
@@ -50,7 +50,25 @@ financy setup --no-input # read FINANCY_CLIENT_ID / _SECRET / _USER_ID from
|
|
|
50
50
|
|
|
51
51
|
`setup` validates the credentials against the live API before saving, so an
|
|
52
52
|
unregistered/free account is caught immediately (exit `3` for bad credentials,
|
|
53
|
-
`4` for an ineligible plan).
|
|
53
|
+
`4` for an ineligible plan). Validation happens **before** anything is written —
|
|
54
|
+
a failed `setup` saves nothing and says so.
|
|
55
|
+
|
|
56
|
+
### Windows
|
|
57
|
+
|
|
58
|
+
The secret prompt echoes one `*` per accepted character, so you can see the value
|
|
59
|
+
arriving. If you paste and no `*`s appear, your console did not paste: the legacy
|
|
60
|
+
Windows console does not accept **Ctrl+V** at a prompt — use **right-click** or
|
|
61
|
+
**Ctrl+Shift+V**. A silently dropped paste used to be submitted as a mangled
|
|
62
|
+
secret and come back as a `401`.
|
|
63
|
+
|
|
64
|
+
The config file lives at `%USERPROFILE%\.config\financy\config.json`. Windows does
|
|
65
|
+
not enforce Unix file modes, so it is not restricted to your user the way it is on
|
|
66
|
+
macOS/Linux — treat it as readable by anything running as you.
|
|
67
|
+
|
|
68
|
+
If you hand-edit that file, note that PowerShell 5.1's `>` and `Out-File` write
|
|
69
|
+
UTF-16 and `Set-Content` writes a UTF-8 BOM. The CLI now reads all three, but
|
|
70
|
+
`financy config` is the way to check: it reports the file as `ok`, `missing`,
|
|
71
|
+
`malformed`, or `unreadable` rather than just showing blank credentials.
|
|
54
72
|
|
|
55
73
|
## Commands
|
|
56
74
|
|
package/dist/bin.js
CHANGED
|
@@ -18,10 +18,12 @@ import {
|
|
|
18
18
|
getConnections,
|
|
19
19
|
loadConfig,
|
|
20
20
|
mintToken,
|
|
21
|
+
notConfigured,
|
|
21
22
|
readCredentialsFile,
|
|
22
23
|
resolveEndpoints,
|
|
24
|
+
sanitizeCredential,
|
|
23
25
|
writeCredentialsFile
|
|
24
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-QYWDNIM2.js";
|
|
25
27
|
|
|
26
28
|
// src/run.ts
|
|
27
29
|
import { Command, CommanderError } from "commander";
|
|
@@ -110,12 +112,17 @@ async function setupCommand(ctx) {
|
|
|
110
112
|
} catch (error) {
|
|
111
113
|
if (error instanceof CliError && error.code === "NOT_AVAILABLE_ON_PLAN") {
|
|
112
114
|
await writeCredentialsFile(endpoints.configDir, credentials);
|
|
115
|
+
} else {
|
|
116
|
+
ctx.err(
|
|
117
|
+
"\u2717 Nothing was saved. financy setup writes the config file only after the\n credentials validate against the API. The failure was:\n"
|
|
118
|
+
);
|
|
113
119
|
}
|
|
114
120
|
throw error;
|
|
115
121
|
}
|
|
116
122
|
await writeCredentialsFile(endpoints.configDir, credentials);
|
|
123
|
+
const perms = (ctx.platform ?? process.platform) === "win32" ? "" : " (permissions 600)";
|
|
117
124
|
ctx.out(
|
|
118
|
-
`\u2713 Saved to ${join(endpoints.configDir, "config.json")}
|
|
125
|
+
`\u2713 Saved to ${join(endpoints.configDir, "config.json")}${perms}
|
|
119
126
|
|
|
120
127
|
Try: financy status
|
|
121
128
|
`
|
|
@@ -123,9 +130,9 @@ Try: financy status
|
|
|
123
130
|
return EXIT.OK;
|
|
124
131
|
}
|
|
125
132
|
function credentialsFromEnv(env) {
|
|
126
|
-
const clientId = env.FINANCY_CLIENT_ID;
|
|
127
|
-
const clientSecret = env.FINANCY_CLIENT_SECRET;
|
|
128
|
-
const userId = env.FINANCY_USER_ID;
|
|
133
|
+
const clientId = sanitizeCredential(env.FINANCY_CLIENT_ID ?? "");
|
|
134
|
+
const clientSecret = sanitizeCredential(env.FINANCY_CLIENT_SECRET ?? "");
|
|
135
|
+
const userId = sanitizeCredential(env.FINANCY_USER_ID ?? "");
|
|
129
136
|
if (!clientId || !clientSecret || !userId) {
|
|
130
137
|
throw new CliError(
|
|
131
138
|
EXIT.USAGE,
|
|
@@ -149,11 +156,13 @@ function maskId(v) {
|
|
|
149
156
|
}
|
|
150
157
|
async function configCommand(ctx) {
|
|
151
158
|
const endpoints = resolveEndpoints(ctx.env);
|
|
152
|
-
const
|
|
159
|
+
const fileResult = await readCredentialsFile(endpoints.configDir);
|
|
160
|
+
const file = fileResult.status === "ok" ? fileResult.credentials : {};
|
|
161
|
+
const fileStatus = fileResult.status === "ok" ? "ok" : fileResult.status === "missing" ? "not created yet \u2014 run financy setup" : `${fileResult.status}: ${fileResult.detail}`;
|
|
153
162
|
const resolve = (envKey, fileVal) => {
|
|
154
163
|
const envVal = ctx.env[envKey];
|
|
155
|
-
if (envVal) return { value: envVal, source: "env" };
|
|
156
|
-
if (fileVal) return { value: fileVal, source: "file" };
|
|
164
|
+
if (envVal) return { value: sanitizeCredential(envVal), source: "env" };
|
|
165
|
+
if (fileVal) return { value: sanitizeCredential(fileVal), source: "file" };
|
|
157
166
|
return { source: "unset" };
|
|
158
167
|
};
|
|
159
168
|
const clientId = resolve("FINANCY_CLIENT_ID", file.clientId);
|
|
@@ -164,6 +173,7 @@ async function configCommand(ctx) {
|
|
|
164
173
|
JSON.stringify(
|
|
165
174
|
{
|
|
166
175
|
configDir: endpoints.configDir,
|
|
176
|
+
configFileStatus: fileStatus,
|
|
167
177
|
authTokenUrl: endpoints.authTokenUrl,
|
|
168
178
|
apiBaseUrl: endpoints.apiBaseUrl,
|
|
169
179
|
chatBaseUrl: endpoints.chatBaseUrl,
|
|
@@ -171,6 +181,9 @@ async function configCommand(ctx) {
|
|
|
171
181
|
clientIdSource: clientId.source,
|
|
172
182
|
clientSecretSet: clientSecret.source !== "unset",
|
|
173
183
|
clientSecretSource: clientSecret.source,
|
|
184
|
+
// Length only — enough to spot a truncated or half-pasted secret, which
|
|
185
|
+
// is otherwise indistinguishable from a wrong one behind a 401.
|
|
186
|
+
clientSecretLength: clientSecret.value?.length ?? 0,
|
|
174
187
|
userId: userId.value ?? null,
|
|
175
188
|
userIdSource: userId.source
|
|
176
189
|
},
|
|
@@ -184,12 +197,13 @@ async function configCommand(ctx) {
|
|
|
184
197
|
"financy configuration",
|
|
185
198
|
"",
|
|
186
199
|
` config dir ${endpoints.configDir}`,
|
|
200
|
+
` config file ${fileStatus}`,
|
|
187
201
|
` auth url ${endpoints.authTokenUrl}`,
|
|
188
202
|
` api url ${endpoints.apiBaseUrl}`,
|
|
189
203
|
` chat url ${endpoints.chatBaseUrl}`,
|
|
190
204
|
"",
|
|
191
205
|
` clientId ${maskId(clientId.value)} (${clientId.source})`,
|
|
192
|
-
` clientSecret ${clientSecret.
|
|
206
|
+
` clientSecret ${clientSecret.value ? `\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 (set, ${clientSecret.value.length} chars)` : "(unset)"} (${clientSecret.source})`,
|
|
193
207
|
` userId ${userId.value ?? "(unset)"} (${userId.source})`
|
|
194
208
|
];
|
|
195
209
|
ctx.out(lines.join("\n") + "\n");
|
|
@@ -293,13 +307,7 @@ function renderGet(ctx, envelope) {
|
|
|
293
307
|
// src/commands/read-commands.ts
|
|
294
308
|
async function loadOrThrow(env) {
|
|
295
309
|
const result = await loadConfig(env);
|
|
296
|
-
if (!result.ok)
|
|
297
|
-
throw new CliError(
|
|
298
|
-
EXIT.AUTH,
|
|
299
|
-
"NOT_CONFIGURED",
|
|
300
|
-
`missing ${result.missing.join(", ")} \u2014 run financy setup`
|
|
301
|
-
);
|
|
302
|
-
}
|
|
310
|
+
if (!result.ok) throw notConfigured(result);
|
|
303
311
|
return result.config;
|
|
304
312
|
}
|
|
305
313
|
var ils = (n) => {
|
|
@@ -338,7 +346,10 @@ var TRANSACTION_COLUMNS = [
|
|
|
338
346
|
{ header: "DESCRIPTION", value: (t) => dash(t.merchantName ?? t.description?.description) },
|
|
339
347
|
{ header: "CATEGORY", value: (t) => `${dash(t.category?.main)}/${dash(t.category?.sub)}` },
|
|
340
348
|
{ header: "TYPE", value: (t) => dash(t.type) },
|
|
341
|
-
|
|
349
|
+
// The SK, not the id: this column exists to be pasted into `transactions get`,
|
|
350
|
+
// and that route is keyed by SK. Matches accounts/connections, whose ID column
|
|
351
|
+
// likewise prints the value their `get` accepts.
|
|
352
|
+
{ header: "SK", value: (t) => t.SK ?? t.id }
|
|
342
353
|
];
|
|
343
354
|
var PROVIDER_COLUMNS = [
|
|
344
355
|
{ header: "ID", value: (p) => dash(p.providerFriendlyId ?? p.id) },
|
|
@@ -380,9 +391,9 @@ function registerReadCommands(program, base, setExit) {
|
|
|
380
391
|
const envelope = await fetchTransactions(config, base.now, opts);
|
|
381
392
|
setExit(renderList(ctx, envelope, TRANSACTION_COLUMNS));
|
|
382
393
|
});
|
|
383
|
-
transactions.command("get <
|
|
394
|
+
transactions.command("get <sk>").description("Read one transaction by its SK (the SK column of `transactions list`)").option("--json", "machine-readable output").action(async (sk) => {
|
|
384
395
|
const config = await loadOrThrow(base.env);
|
|
385
|
-
setExit(renderGet(ctx, await fetchTransaction(config, base.now,
|
|
396
|
+
setExit(renderGet(ctx, await fetchTransaction(config, base.now, sk)));
|
|
386
397
|
});
|
|
387
398
|
program.command("categories").description("The category taxonomy (English + Hebrew)").option("--json", "machine-readable output").action(async () => {
|
|
388
399
|
const config = await loadOrThrow(base.env);
|
|
@@ -533,26 +544,99 @@ async function exists(path) {
|
|
|
533
544
|
|
|
534
545
|
// src/prompt.ts
|
|
535
546
|
import { createInterface } from "readline";
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
}
|
|
549
|
-
rl.question(`${label}: `, (answer) => {
|
|
547
|
+
var CTRL_C = String.fromCharCode(3);
|
|
548
|
+
var CTRL_U = String.fromCharCode(21);
|
|
549
|
+
var ESC = String.fromCharCode(27);
|
|
550
|
+
var BACKSPACE = String.fromCharCode(8);
|
|
551
|
+
var DEL = String.fromCharCode(127);
|
|
552
|
+
var SIGINT_EXIT = 130;
|
|
553
|
+
var PASTE_HINT = "note: a keystroke that entered no text was ignored. If you meant to paste and\nnothing appeared, this console does not paste with Ctrl+V \u2014 use right-click or\nCtrl+Shift+V, or type the value.\n";
|
|
554
|
+
function readLine(prompt, input, output) {
|
|
555
|
+
if (prompt) output.write(prompt);
|
|
556
|
+
const rl = createInterface({ input, terminal: false });
|
|
557
|
+
return new Promise((resolve) => {
|
|
558
|
+
rl.question("", (answer) => {
|
|
550
559
|
rl.close();
|
|
551
|
-
|
|
552
|
-
resolve(answer.trim());
|
|
560
|
+
resolve(answer);
|
|
553
561
|
});
|
|
554
562
|
});
|
|
555
563
|
}
|
|
564
|
+
function readMasked(prompt, input, output) {
|
|
565
|
+
output.write(prompt);
|
|
566
|
+
input.setRawMode?.(true);
|
|
567
|
+
input.setEncoding("utf8");
|
|
568
|
+
input.resume();
|
|
569
|
+
return new Promise((resolve) => {
|
|
570
|
+
let value = "";
|
|
571
|
+
let ignoredControl = false;
|
|
572
|
+
let inEscape = false;
|
|
573
|
+
const cleanup = () => {
|
|
574
|
+
input.removeListener("data", onData);
|
|
575
|
+
input.setRawMode?.(false);
|
|
576
|
+
input.pause();
|
|
577
|
+
};
|
|
578
|
+
const erase = (count) => {
|
|
579
|
+
if (count > 0) output.write(`${BACKSPACE} ${BACKSPACE}`.repeat(count));
|
|
580
|
+
};
|
|
581
|
+
const onData = (chunk) => {
|
|
582
|
+
for (const ch of String(chunk)) {
|
|
583
|
+
if (inEscape) {
|
|
584
|
+
if (/[A-Za-z~]/.test(ch)) inEscape = false;
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
if (ch === "\r" || ch === "\n") {
|
|
588
|
+
cleanup();
|
|
589
|
+
output.write("\n");
|
|
590
|
+
if (ignoredControl) output.write(PASTE_HINT);
|
|
591
|
+
resolve(value);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
if (ch === CTRL_C) {
|
|
595
|
+
cleanup();
|
|
596
|
+
output.write("\n");
|
|
597
|
+
process.exit(SIGINT_EXIT);
|
|
598
|
+
}
|
|
599
|
+
if (ch === DEL || ch === BACKSPACE) {
|
|
600
|
+
if (value.length > 0) {
|
|
601
|
+
value = value.slice(0, -1);
|
|
602
|
+
erase(1);
|
|
603
|
+
}
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (ch === CTRL_U) {
|
|
607
|
+
erase(value.length);
|
|
608
|
+
value = "";
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
if (ch === ESC) {
|
|
612
|
+
inEscape = true;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (isControlChar(ch)) {
|
|
616
|
+
ignoredControl = true;
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
value += ch;
|
|
620
|
+
output.write("*");
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
input.on("data", onData);
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
function isControlChar(ch) {
|
|
627
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
628
|
+
return code < 32 || code === 127;
|
|
629
|
+
}
|
|
630
|
+
function createDefaultPrompt(input = process.stdin, output = process.stdout) {
|
|
631
|
+
return async ({ label, secret }) => {
|
|
632
|
+
if (!secret) return sanitizeCredential(await readLine(`${label}: `, input, output));
|
|
633
|
+
if (!input.isTTY) {
|
|
634
|
+
const prompt = `${label} (not masked \u2014 this terminal cannot hide input): `;
|
|
635
|
+
return sanitizeCredential(await readLine(prompt, input, output));
|
|
636
|
+
}
|
|
637
|
+
return sanitizeCredential(await readMasked(`${label}: `, input, output));
|
|
638
|
+
};
|
|
639
|
+
}
|
|
556
640
|
|
|
557
641
|
// src/run.ts
|
|
558
642
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -635,13 +719,7 @@ async function run(argv, io = {}) {
|
|
|
635
719
|
let exitCode = EXIT.OK;
|
|
636
720
|
program.command("status").description("Are my connections fresh? One-line-per-bank rollup").option("--json", "machine-readable output (stable schema, errors as JSON on stderr)").action(async () => {
|
|
637
721
|
const result = await loadConfig(env);
|
|
638
|
-
if (!result.ok)
|
|
639
|
-
throw new CliError(
|
|
640
|
-
EXIT.AUTH,
|
|
641
|
-
"NOT_CONFIGURED",
|
|
642
|
-
`missing ${result.missing.join(", ")} \u2014 run financy setup`
|
|
643
|
-
);
|
|
644
|
-
}
|
|
722
|
+
if (!result.ok) throw notConfigured(result);
|
|
645
723
|
exitCode = await statusCommand({ config: result.config, json, now, out, err });
|
|
646
724
|
});
|
|
647
725
|
program.command("setup").description("Save your API credentials (Financy \u2192 Settings \u2192 API; requires a paid plan)").option("--no-input", "read credentials from FINANCY_* env vars (for agents/CI)").action(async (opts) => {
|
|
@@ -655,20 +733,14 @@ async function run(argv, io = {}) {
|
|
|
655
733
|
});
|
|
656
734
|
program.command("refresh").description("Trigger an on-demand refresh of all connections (20 credits)").option("--json", "machine-readable output").action(async () => {
|
|
657
735
|
const result = await loadConfig(env);
|
|
658
|
-
if (!result.ok)
|
|
659
|
-
throw new CliError(
|
|
660
|
-
EXIT.AUTH,
|
|
661
|
-
"NOT_CONFIGURED",
|
|
662
|
-
`missing ${result.missing.join(", ")} \u2014 run financy setup`
|
|
663
|
-
);
|
|
664
|
-
}
|
|
736
|
+
if (!result.ok) throw notConfigured(result);
|
|
665
737
|
exitCode = await refreshCommand({ config: result.config, json, now, out });
|
|
666
738
|
});
|
|
667
739
|
program.command("config").description("Show the resolved endpoints and credential sources (secret masked)").option("--json", "machine-readable output").action(async () => {
|
|
668
740
|
exitCode = await configCommand({ env, json, out });
|
|
669
741
|
});
|
|
670
742
|
program.command("mcp").description("Run the embedded MCP server (stdio) exposing the CLI as agent tools").action(async () => {
|
|
671
|
-
const { startMcpServer } = await import("./server-
|
|
743
|
+
const { startMcpServer } = await import("./server-N5ZPO432.js");
|
|
672
744
|
await startMcpServer(env);
|
|
673
745
|
});
|
|
674
746
|
const skills = program.command("skills").description("Agent skills bundled with financy (Claude Code and other agents)");
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import {
|
|
3
3
|
CliError,
|
|
4
4
|
EXIT,
|
|
5
|
-
VERSION,
|
|
6
5
|
fetchAccount,
|
|
7
6
|
fetchAccounts,
|
|
8
7
|
fetchBranches,
|
|
@@ -14,16 +13,9 @@ import {
|
|
|
14
13
|
fetchStatus,
|
|
15
14
|
fetchTransaction,
|
|
16
15
|
fetchTransactions,
|
|
17
|
-
loadConfig
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// src/mcp/server.ts
|
|
21
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
22
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
23
|
-
import {
|
|
24
|
-
ListToolsRequestSchema,
|
|
25
|
-
CallToolRequestSchema
|
|
26
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
16
|
+
loadConfig,
|
|
17
|
+
notConfigured
|
|
18
|
+
} from "./chunk-QYWDNIM2.js";
|
|
27
19
|
|
|
28
20
|
// src/mcp/tools.ts
|
|
29
21
|
var str = (v) => v == null ? void 0 : String(v);
|
|
@@ -89,7 +81,7 @@ var TOOLS = [
|
|
|
89
81
|
},
|
|
90
82
|
{
|
|
91
83
|
name: "list_transactions",
|
|
92
|
-
description: "List transactions, most useful with filters: from/to (YYYY-MM-DD), account, connection, type (BANK|CARD). Page with limit/cursor or fetch everything with all. Hebrew merchant names are returned as-is.",
|
|
84
|
+
description: "List transactions, most useful with filters: from/to (YYYY-MM-DD), account, connection, type (BANK|CARD). Page with limit/cursor or fetch everything with all. Hebrew merchant names are returned as-is. To then read one transaction, pass its `SK` (not its `id`) to get_transaction.",
|
|
93
85
|
inputSchema: {
|
|
94
86
|
type: "object",
|
|
95
87
|
properties: {
|
|
@@ -112,8 +104,17 @@ var TOOLS = [
|
|
|
112
104
|
},
|
|
113
105
|
{
|
|
114
106
|
name: "get_transaction",
|
|
115
|
-
description: "Read a single transaction by
|
|
116
|
-
inputSchema:
|
|
107
|
+
description: "Read a single transaction by its `SK` \u2014 the composite key from list_transactions, NOT the short `id` field, which this route does not accept. Errors TRANSACTION_NOT_FOUND if not in scope.",
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
id: {
|
|
112
|
+
type: "string",
|
|
113
|
+
description: 'the transaction\'s `SK` from list_transactions, e.g. "TRANSACTION#TYPE#CHECKING#PROVIDER#leumi#RESOURCE<uuid>#<ulid>". The short `id` field of the same transaction will NOT resolve.'
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
required: ["id"]
|
|
117
|
+
},
|
|
117
118
|
run: (a, ctx) => fetchTransaction(ctx.config, ctx.now, requireId(a))
|
|
118
119
|
},
|
|
119
120
|
{
|
|
@@ -161,39 +162,19 @@ async function callTool(name, args, io) {
|
|
|
161
162
|
if (!tool) {
|
|
162
163
|
throw new CliError(EXIT.USAGE, "UNKNOWN_TOOL", `no tool named '${name}'`);
|
|
163
164
|
}
|
|
164
|
-
|
|
165
|
-
if (!
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
`missing ${result.missing.join(", ")} \u2014 run financy setup`
|
|
170
|
-
);
|
|
165
|
+
let config = io.config;
|
|
166
|
+
if (!config) {
|
|
167
|
+
const result = await loadConfig(io.env);
|
|
168
|
+
if (!result.ok) throw notConfigured(result);
|
|
169
|
+
config = result.config;
|
|
171
170
|
}
|
|
172
|
-
return await tool.run(args ?? {}, { config
|
|
171
|
+
return await tool.run(args ?? {}, { config, now: io.now ?? /* @__PURE__ */ new Date() });
|
|
173
172
|
} catch (error) {
|
|
174
173
|
return errorEnvelope(error);
|
|
175
174
|
}
|
|
176
175
|
}
|
|
177
176
|
|
|
178
|
-
// src/mcp/server.ts
|
|
179
|
-
async function startMcpServer(env) {
|
|
180
|
-
const server = new Server(
|
|
181
|
-
{ name: "financy", version: VERSION },
|
|
182
|
-
{ capabilities: { tools: {} } }
|
|
183
|
-
);
|
|
184
|
-
server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
185
|
-
tools: TOOLS.map((t) => ({
|
|
186
|
-
name: t.name,
|
|
187
|
-
description: t.description,
|
|
188
|
-
inputSchema: t.inputSchema
|
|
189
|
-
}))
|
|
190
|
-
}));
|
|
191
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
192
|
-
const result = await callTool(request.params.name, request.params.arguments ?? {}, { env });
|
|
193
|
-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
194
|
-
});
|
|
195
|
-
await server.connect(new StdioServerTransport());
|
|
196
|
-
}
|
|
197
177
|
export {
|
|
198
|
-
|
|
178
|
+
TOOLS,
|
|
179
|
+
callTool
|
|
199
180
|
};
|