bankmcp 0.1.3 → 0.1.4
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/lib/local.js +34 -4
- package/dist/lib/mcp.js +1 -1
- package/dist/lib/server.js +9 -0
- package/dist/lib/store.js +5 -2
- package/dist/lib/tools.js +6 -2
- package/package.json +1 -1
package/dist/lib/local.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// page and receives the bank redirect. Enable Banking requires https redirect
|
|
3
3
|
// URLs, so a self-signed certificate is created on first run.
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { X509Certificate } from "node:crypto";
|
|
5
6
|
import { join } from "node:path";
|
|
6
7
|
import { createServer as createHttpsServer } from "node:https";
|
|
7
8
|
import selfsigned from "selfsigned";
|
|
@@ -9,17 +10,46 @@ import { config } from "./config.js";
|
|
|
9
10
|
import { createApp } from "./app.js";
|
|
10
11
|
let server;
|
|
11
12
|
let starting;
|
|
13
|
+
/** Browsers reject server certificates valid for more than 398 days; earlier versions issued 10-year ones. */
|
|
14
|
+
export function certificateStillGood(pem, now = Date.now()) {
|
|
15
|
+
try {
|
|
16
|
+
const x = new X509Certificate(pem);
|
|
17
|
+
const from = Date.parse(x.validFrom);
|
|
18
|
+
const to = Date.parse(x.validTo);
|
|
19
|
+
const day = 86_400_000;
|
|
20
|
+
return to - from <= 398 * day && to - now > 30 * day;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
12
26
|
async function certificate() {
|
|
13
27
|
const certPath = join(config.dataDir, "localhost-cert.pem");
|
|
14
28
|
const keyPath = join(config.dataDir, "localhost-key.pem");
|
|
15
|
-
if (existsSync(certPath) && existsSync(keyPath))
|
|
16
|
-
|
|
29
|
+
if (existsSync(certPath) && existsSync(keyPath)) {
|
|
30
|
+
const cert = readFileSync(certPath, "utf8");
|
|
31
|
+
if (certificateStillGood(cert))
|
|
32
|
+
return { cert, key: readFileSync(keyPath, "utf8") };
|
|
33
|
+
console.error("[bank] replacing the stored localhost certificate (too long-lived or about to expire)");
|
|
34
|
+
}
|
|
35
|
+
// Apple caps TLS server certificate lifetime at 398 days; Chrome on macOS
|
|
36
|
+
// defers to the system verifier and rejects anything longer as ERR_CERT_INVALID,
|
|
37
|
+
// which offers no click-through. Stay just under the limit.
|
|
17
38
|
const notAfterDate = new Date();
|
|
18
|
-
notAfterDate.
|
|
39
|
+
notAfterDate.setDate(notAfterDate.getDate() + 397);
|
|
19
40
|
const pems = await selfsigned.generate([{ name: "commonName", value: "localhost" }], {
|
|
20
41
|
keySize: 2048,
|
|
21
42
|
notAfterDate,
|
|
22
|
-
|
|
43
|
+
// selfsigned defaults to sha1, which browsers reject outright as
|
|
44
|
+
// ERR_CERT_INVALID with no click-through. macOS additionally requires
|
|
45
|
+
// basicConstraints and an extendedKeyUsage of serverAuth.
|
|
46
|
+
algorithm: "sha256",
|
|
47
|
+
extensions: [
|
|
48
|
+
{ name: "basicConstraints", cA: false, critical: true },
|
|
49
|
+
{ name: "keyUsage", digitalSignature: true, keyEncipherment: true, critical: true },
|
|
50
|
+
{ name: "extKeyUsage", serverAuth: true },
|
|
51
|
+
{ name: "subjectAltName", altNames: [{ type: 2, value: "localhost" }, { type: 7, ip: "127.0.0.1" }] },
|
|
52
|
+
],
|
|
23
53
|
});
|
|
24
54
|
mkdirSync(config.dataDir, { recursive: true });
|
|
25
55
|
writeFileSync(certPath, pems.cert, { mode: 0o600 });
|
package/dist/lib/mcp.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { registerTools } from "./tools.js";
|
|
3
3
|
import { registerPrompts } from "./prompts.js";
|
|
4
|
-
export const VERSION = "0.1.
|
|
4
|
+
export const VERSION = "0.1.4";
|
|
5
5
|
export function createServer() {
|
|
6
6
|
const server = new McpServer({ name: "bank", version: VERSION }, {
|
|
7
7
|
instructions: [
|
package/dist/lib/server.js
CHANGED
|
@@ -6,6 +6,15 @@ import { createApp } from "./app.js";
|
|
|
6
6
|
import { setupAvailable } from "./setup.js";
|
|
7
7
|
const app = createApp({ remote: true });
|
|
8
8
|
const tls = tlsOptions();
|
|
9
|
+
// In local mode config.baseUrl is https://localhost:PORT, but this entry point
|
|
10
|
+
// only speaks TLS when a certificate is configured. Serving plain http while
|
|
11
|
+
// advertising https leaves the setup page and the bank redirect unreachable,
|
|
12
|
+
// so say what is wrong instead of starting into a broken state.
|
|
13
|
+
if (config.localMode && !tls) {
|
|
14
|
+
console.error(`[bank] BANKMCP_LOCAL=1 makes the public URL ${config.baseUrl}, but no TLS certificate is configured, so this process can only serve http.\n` +
|
|
15
|
+
`[bank] Run the stdio entry point instead (npx bankmcp), which terminates TLS itself, or set TLS_CERT_PATH and TLS_KEY_PATH.`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
9
18
|
const httpServer = tls ? createHttpsServer(tls, app) : createHttpServer(app);
|
|
10
19
|
httpServer.listen(config.port, () => {
|
|
11
20
|
console.log(`[bank ${new Date().toISOString()}] listening on ${tls ? "https" : "http"}://0.0.0.0:${config.port}, public URL ${config.baseUrl}`);
|
package/dist/lib/store.js
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
import { mkdirSync, readFileSync, renameSync, writeFileSync, existsSync } from "node:fs";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { config } from "./config.js";
|
|
7
|
+
/** How long a started-but-unfinished bank login stays interesting. */
|
|
8
|
+
export const PENDING_AUTH_TTL_MS = 60 * 60 * 1000;
|
|
9
|
+
/** True while a pending authorization is recent enough to still be completed. */
|
|
10
|
+
export const pendingAuthIsLive = (p, now = Date.now()) => Date.parse(p.started) >= now - PENDING_AUTH_TTL_MS;
|
|
7
11
|
const empty = () => ({
|
|
8
12
|
version: 1,
|
|
9
13
|
sessions: {},
|
|
@@ -109,9 +113,8 @@ export class Store {
|
|
|
109
113
|
// --- Pending bank authorizations ---
|
|
110
114
|
addPendingAuth(p) {
|
|
111
115
|
this.update((d) => {
|
|
112
|
-
const cutoff = Date.now() - 60 * 60 * 1000;
|
|
113
116
|
for (const [k, v] of Object.entries(d.pending_auth))
|
|
114
|
-
if (
|
|
117
|
+
if (!pendingAuthIsLive(v))
|
|
115
118
|
delete d.pending_auth[k];
|
|
116
119
|
d.pending_auth[p.state] = p;
|
|
117
120
|
});
|
package/dist/lib/tools.js
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { config, isConfigured } from "./config.js";
|
|
4
4
|
import { eb, EnableBankingError } from "./enablebanking.js";
|
|
5
|
-
import { store } from "./store.js";
|
|
5
|
+
import { pendingAuthIsLive, store } from "./store.js";
|
|
6
6
|
import { daysAgo, daysLeft, describeAccount, isoDate, simplifyBalances, simplifyTransaction } from "./data.js";
|
|
7
7
|
import { runWatches } from "./watcher.js";
|
|
8
8
|
const MAX_CONSENT_DAYS = 180;
|
|
@@ -135,7 +135,11 @@ export function registerTools(server) {
|
|
|
135
135
|
accounts: s.accounts().filter((a) => a.session_id === session.id).length,
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
|
-
|
|
138
|
+
// Expired ones are only swept when the next login starts, so filter here
|
|
139
|
+
// too; listing logins that can no longer be completed just misleads.
|
|
140
|
+
const pending = Object.values(s.data.pending_auth)
|
|
141
|
+
.filter((p) => pendingAuthIsLive(p))
|
|
142
|
+
.map((p) => ({ bank: p.bank.name, started: p.started }));
|
|
139
143
|
return json({ banks, pending_logins: pending, hint: banks.length ? undefined : "No bank connected yet. Use start_consent." });
|
|
140
144
|
}));
|
|
141
145
|
server.registerTool("disconnect_bank", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bankmcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "BankMCP™: self-hosted, read-only MCP server that lets any AI assistant (Claude, ChatGPT, Mistral, Cursor, or a local model) answer questions about your own bank accounts via open banking (Enable Banking, PSD2)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|