@codespar/mcp-open-finance 0.1.0 → 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 ADDED
@@ -0,0 +1,120 @@
1
+ # @codespar/mcp-open-finance
2
+
3
+ > MCP server for **Open Finance Brasil** — open banking standard for accounts, transactions, and consents
4
+
5
+ [![npm](https://img.shields.io/npm/v/@codespar/mcp-open-finance)](https://www.npmjs.com/package/@codespar/mcp-open-finance)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Quick Start
9
+
10
+ ### Claude Desktop
11
+
12
+ Add to `~/.config/claude/claude_desktop_config.json`:
13
+
14
+ ```json
15
+ {
16
+ "mcpServers": {
17
+ "open-finance": {
18
+ "command": "npx",
19
+ "args": ["-y", "@codespar/mcp-open-finance"],
20
+ "env": {
21
+ "OPEN_FINANCE_BASE_URL": "https://api.institution.com.br",
22
+ "OPEN_FINANCE_CLIENT_ID": "your-client-id",
23
+ "OPEN_FINANCE_CLIENT_SECRET": "your-client-secret"
24
+ }
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ ### Claude Code
31
+
32
+ ```bash
33
+ claude mcp add open-finance -- npx @codespar/mcp-open-finance
34
+ ```
35
+
36
+ ### Cursor / VS Code
37
+
38
+ Add to `.cursor/mcp.json` or `.vscode/mcp.json`:
39
+
40
+ ```json
41
+ {
42
+ "servers": {
43
+ "open-finance": {
44
+ "command": "npx",
45
+ "args": ["-y", "@codespar/mcp-open-finance"],
46
+ "env": {
47
+ "OPEN_FINANCE_BASE_URL": "https://api.institution.com.br",
48
+ "OPEN_FINANCE_CLIENT_ID": "your-client-id",
49
+ "OPEN_FINANCE_CLIENT_SECRET": "your-client-secret"
50
+ }
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Tools
57
+
58
+ | Tool | Description |
59
+ |------|-------------|
60
+ | `list_accounts` | List customer bank accounts via Open Finance |
61
+ | `get_account_balance` | Get account balance via Open Finance |
62
+ | `list_transactions` | List account transactions via Open Finance |
63
+ | `get_consent` | Get consent details by ID |
64
+ | `create_consent` | Create a new consent request for data access |
65
+ | `list_credit_cards` | List credit card accounts via Open Finance |
66
+ | `get_credit_card_transactions` | Get credit card transactions via Open Finance |
67
+ | `list_investments` | List investment products via Open Finance |
68
+
69
+ ## Authentication
70
+
71
+ Open Finance Brasil uses OAuth2 client credentials. Each financial institution provides its own base URL and credentials.
72
+
73
+ ## Sandbox / Testing
74
+
75
+ Sandbox availability varies by institution. Contact your financial institution for Open Finance sandbox access.
76
+
77
+ ### Get your credentials
78
+
79
+ 1. Go to [Open Finance Brasil](https://openfinancebrasil.org.br)
80
+ 2. Register with a participating financial institution
81
+ 3. Obtain your OAuth2 client credentials
82
+ 4. Set the environment variables
83
+
84
+ ## Environment Variables
85
+
86
+ | Variable | Required | Description |
87
+ |----------|----------|-------------|
88
+ | `OPEN_FINANCE_BASE_URL` | Yes | Institution API base URL |
89
+ | `OPEN_FINANCE_CLIENT_ID` | Yes | OAuth2 client ID |
90
+ | `OPEN_FINANCE_CLIENT_SECRET` | Yes | OAuth2 client secret |
91
+
92
+ ## Roadmap
93
+
94
+ ### v0.2 (planned)
95
+ - `revoke_consent` — Revoke a data sharing consent
96
+ - `list_payments` — List initiated payments
97
+ - `create_payment_consent` — Create a payment initiation consent
98
+ - `initiate_payment` — Initiate a payment via Open Finance
99
+ - `get_payment_status` — Get payment initiation status
100
+
101
+ ### v0.3 (planned)
102
+ - `insurance_products` — List insurance products from institutions
103
+ - `pension_products` — List pension products from institutions
104
+
105
+ Want to contribute? [Open a PR](https://github.com/codespar/mcp-dev-brasil) or [request a tool](https://github.com/codespar/mcp-dev-brasil/issues).
106
+
107
+ ## Links
108
+
109
+ - [Open Finance Brasil](https://openfinancebrasil.org.br)
110
+ - [Open Finance Brasil Developer Portal](https://openfinancebrasil.atlassian.net)
111
+ - [MCP Dev Brasil](https://github.com/codespar/mcp-dev-brasil)
112
+ - [Landing Page](https://codespar.dev/mcp)
113
+
114
+ ## Enterprise
115
+
116
+ Need governance, budget limits, and audit trails for agent payments? [CodeSpar Enterprise](https://codespar.dev/enterprise) adds policy engine, payment routing, and compliance templates on top of these MCP servers.
117
+
118
+ ## License
119
+
120
+ MIT
package/dist/index.js CHANGED
@@ -6,11 +6,21 @@
6
6
  * - list_accounts: List customer accounts
7
7
  * - get_account_balance: Get account balance
8
8
  * - list_transactions: List account transactions
9
+ * - get_account_overdraft_limits: Get account overdraft limits
9
10
  * - get_consent: Get consent details
10
11
  * - create_consent: Create a new consent request
12
+ * - revoke_consent: Revoke an existing consent
11
13
  * - list_credit_cards: List credit card accounts
14
+ * - get_credit_card_bills: Get credit card bills
12
15
  * - get_credit_card_transactions: Get credit card transactions
16
+ * - list_loans: List loan contracts
17
+ * - get_loan_payments: Get loan payment schedule
18
+ * - list_financings: List financing contracts
13
19
  * - list_investments: List investment products
20
+ * - create_payment_consent: Create payment-initiation consent
21
+ * - create_payment: Initiate a payment
22
+ * - get_personal_qualifications: Get personal customer qualifications
23
+ * - get_business_qualifications: Get business customer qualifications
14
24
  *
15
25
  * Environment:
16
26
  * OPEN_FINANCE_BASE_URL — Institution API base URL
@@ -19,6 +29,8 @@
19
29
  */
20
30
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
21
31
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
32
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
33
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
22
34
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
23
35
  const BASE_URL = process.env.OPEN_FINANCE_BASE_URL || "";
24
36
  const CLIENT_ID = process.env.OPEN_FINANCE_CLIENT_ID || "";
@@ -35,7 +47,7 @@ async function getAccessToken() {
35
47
  grant_type: "client_credentials",
36
48
  client_id: CLIENT_ID,
37
49
  client_secret: CLIENT_SECRET,
38
- scope: "openid accounts credit-cards-accounts resources consents investments",
50
+ scope: "openid accounts credit-cards-accounts resources consents investments loans financings customers payments",
39
51
  }),
40
52
  });
41
53
  if (!res.ok) {
@@ -63,7 +75,7 @@ async function openFinanceRequest(method, path, body) {
63
75
  }
64
76
  return res.json();
65
77
  }
66
- const server = new Server({ name: "mcp-open-finance", version: "0.1.0" }, { capabilities: { tools: {} } });
78
+ const server = new Server({ name: "mcp-open-finance", version: "0.2.0" }, { capabilities: { tools: {} } });
67
79
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
68
80
  tools: [
69
81
  {
@@ -107,6 +119,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
107
119
  required: ["consentId", "accountId"],
108
120
  },
109
121
  },
122
+ {
123
+ name: "get_account_overdraft_limits",
124
+ description: "Get account overdraft (limites) via Open Finance",
125
+ inputSchema: {
126
+ type: "object",
127
+ properties: {
128
+ consentId: { type: "string", description: "Consent ID" },
129
+ accountId: { type: "string", description: "Account ID" },
130
+ },
131
+ required: ["consentId", "accountId"],
132
+ },
133
+ },
110
134
  {
111
135
  name: "get_consent",
112
136
  description: "Get consent details by ID",
@@ -136,6 +160,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
136
160
  required: ["permissions", "expirationDateTime"],
137
161
  },
138
162
  },
163
+ {
164
+ name: "revoke_consent",
165
+ description: "Revoke an existing consent (data or payment)",
166
+ inputSchema: {
167
+ type: "object",
168
+ properties: {
169
+ consentId: { type: "string", description: "Consent ID to revoke" },
170
+ consentType: { type: "string", enum: ["data", "payment"], description: "Consent type (default: data)" },
171
+ },
172
+ required: ["consentId"],
173
+ },
174
+ },
139
175
  {
140
176
  name: "list_credit_cards",
141
177
  description: "List credit card accounts via Open Finance",
@@ -149,6 +185,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
149
185
  required: ["consentId"],
150
186
  },
151
187
  },
188
+ {
189
+ name: "get_credit_card_bills",
190
+ description: "Get credit card bills (faturas) via Open Finance",
191
+ inputSchema: {
192
+ type: "object",
193
+ properties: {
194
+ consentId: { type: "string", description: "Consent ID" },
195
+ creditCardAccountId: { type: "string", description: "Credit card account ID" },
196
+ fromDueDate: { type: "string", description: "Start due date (YYYY-MM-DD)" },
197
+ toDueDate: { type: "string", description: "End due date (YYYY-MM-DD)" },
198
+ page: { type: "number", description: "Page number" },
199
+ pageSize: { type: "number", description: "Items per page" },
200
+ },
201
+ required: ["consentId", "creditCardAccountId"],
202
+ },
203
+ },
152
204
  {
153
205
  name: "get_credit_card_transactions",
154
206
  description: "Get credit card transactions via Open Finance",
@@ -165,6 +217,46 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
165
217
  required: ["consentId", "creditCardAccountId"],
166
218
  },
167
219
  },
220
+ {
221
+ name: "list_loans",
222
+ description: "List loan contracts (empréstimos) via Open Finance",
223
+ inputSchema: {
224
+ type: "object",
225
+ properties: {
226
+ consentId: { type: "string", description: "Consent ID" },
227
+ page: { type: "number", description: "Page number" },
228
+ pageSize: { type: "number", description: "Items per page" },
229
+ },
230
+ required: ["consentId"],
231
+ },
232
+ },
233
+ {
234
+ name: "get_loan_payments",
235
+ description: "Get loan payment schedule via Open Finance",
236
+ inputSchema: {
237
+ type: "object",
238
+ properties: {
239
+ consentId: { type: "string", description: "Consent ID" },
240
+ contractId: { type: "string", description: "Loan contract ID" },
241
+ page: { type: "number", description: "Page number" },
242
+ pageSize: { type: "number", description: "Items per page" },
243
+ },
244
+ required: ["consentId", "contractId"],
245
+ },
246
+ },
247
+ {
248
+ name: "list_financings",
249
+ description: "List financing contracts (financiamentos) via Open Finance",
250
+ inputSchema: {
251
+ type: "object",
252
+ properties: {
253
+ consentId: { type: "string", description: "Consent ID" },
254
+ page: { type: "number", description: "Page number" },
255
+ pageSize: { type: "number", description: "Items per page" },
256
+ },
257
+ required: ["consentId"],
258
+ },
259
+ },
168
260
  {
169
261
  name: "list_investments",
170
262
  description: "List investment products via Open Finance",
@@ -179,10 +271,71 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
179
271
  required: ["consentId"],
180
272
  },
181
273
  },
274
+ {
275
+ name: "create_payment_consent",
276
+ description: "Create payment-initiation consent (e.g., PIX) via Open Finance",
277
+ inputSchema: {
278
+ type: "object",
279
+ properties: {
280
+ loggedUserCpf: { type: "string", description: "Logged user CPF" },
281
+ creditorName: { type: "string", description: "Creditor name" },
282
+ creditorCpfCnpj: { type: "string", description: "Creditor CPF/CNPJ" },
283
+ paymentAmount: { type: "string", description: "Payment amount (e.g., '100.00')" },
284
+ paymentCurrency: { type: "string", description: "ISO 4217 currency (default: BRL)" },
285
+ localInstrument: { type: "string", enum: ["MANU", "DICT", "QRDN", "QRES", "INIC"], description: "PIX local instrument (default: DICT)" },
286
+ paymentType: { type: "string", enum: ["PIX", "TED", "TEF", "BOLETO"], description: "Payment type (default: PIX)" },
287
+ expirationDateTime: { type: "string", description: "Consent expiration (ISO 8601)" },
288
+ },
289
+ required: ["loggedUserCpf", "creditorName", "creditorCpfCnpj", "paymentAmount"],
290
+ },
291
+ },
292
+ {
293
+ name: "create_payment",
294
+ description: "Initiate a payment using an authorized payment consent",
295
+ inputSchema: {
296
+ type: "object",
297
+ properties: {
298
+ consentId: { type: "string", description: "Authorized payment consent ID" },
299
+ creditorAccountIspb: { type: "string", description: "Creditor account ISPB" },
300
+ creditorAccountIssuer: { type: "string", description: "Creditor account issuer (agência)" },
301
+ creditorAccountNumber: { type: "string", description: "Creditor account number" },
302
+ creditorAccountType: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"], description: "Creditor account type" },
303
+ paymentAmount: { type: "string", description: "Payment amount (e.g., '100.00')" },
304
+ paymentCurrency: { type: "string", description: "ISO 4217 currency (default: BRL)" },
305
+ remittanceInformation: { type: "string", description: "Free-text remittance info" },
306
+ qrCode: { type: "string", description: "PIX QR Code payload (optional)" },
307
+ proxy: { type: "string", description: "PIX key/proxy (optional)" },
308
+ },
309
+ required: ["consentId", "creditorAccountIspb", "creditorAccountIssuer", "creditorAccountNumber", "creditorAccountType", "paymentAmount"],
310
+ },
311
+ },
312
+ {
313
+ name: "get_personal_qualifications",
314
+ description: "Get personal customer qualifications (income, occupation) via Open Finance",
315
+ inputSchema: {
316
+ type: "object",
317
+ properties: {
318
+ consentId: { type: "string", description: "Consent ID" },
319
+ },
320
+ required: ["consentId"],
321
+ },
322
+ },
323
+ {
324
+ name: "get_business_qualifications",
325
+ description: "Get business customer qualifications via Open Finance",
326
+ inputSchema: {
327
+ type: "object",
328
+ properties: {
329
+ consentId: { type: "string", description: "Consent ID" },
330
+ },
331
+ required: ["consentId"],
332
+ },
333
+ },
182
334
  ],
183
335
  }));
184
336
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
185
- const { name, arguments: args } = request.params;
337
+ const { name, arguments: rawArgs } = request.params;
338
+ const args = rawArgs;
186
339
  try {
187
340
  switch (name) {
188
341
  case "list_accounts": {
@@ -207,6 +360,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
207
360
  params.set("page-size", String(args.pageSize));
208
361
  return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/accounts/v2/accounts/${args?.accountId}/transactions?${params}`), null, 2) }] };
209
362
  }
363
+ case "get_account_overdraft_limits":
364
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/accounts/v2/accounts/${args?.accountId}/overdraft-limits`), null, 2) }] };
210
365
  case "get_consent":
211
366
  return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/consents/v2/consents/${args?.consentId}`), null, 2) }] };
212
367
  case "create_consent": {
@@ -220,6 +375,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
220
375
  };
221
376
  return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("POST", "/open-banking/consents/v2/consents", payload), null, 2) }] };
222
377
  }
378
+ case "revoke_consent": {
379
+ const consentType = args?.consentType || "data";
380
+ const path = consentType === "payment"
381
+ ? `/open-banking/payments/v3/consents/${args?.consentId}`
382
+ : `/open-banking/consents/v2/consents/${args?.consentId}`;
383
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("DELETE", path), null, 2) }] };
384
+ }
223
385
  case "list_credit_cards": {
224
386
  const params = new URLSearchParams();
225
387
  if (args?.page)
@@ -228,6 +390,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
228
390
  params.set("page-size", String(args.pageSize));
229
391
  return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/credit-cards-accounts/v2/accounts?${params}`), null, 2) }] };
230
392
  }
393
+ case "get_credit_card_bills": {
394
+ const params = new URLSearchParams();
395
+ if (args?.fromDueDate)
396
+ params.set("fromDueDate", String(args.fromDueDate));
397
+ if (args?.toDueDate)
398
+ params.set("toDueDate", String(args.toDueDate));
399
+ if (args?.page)
400
+ params.set("page", String(args.page));
401
+ if (args?.pageSize)
402
+ params.set("page-size", String(args.pageSize));
403
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/credit-cards-accounts/v2/accounts/${args?.creditCardAccountId}/bills?${params}`), null, 2) }] };
404
+ }
231
405
  case "get_credit_card_transactions": {
232
406
  const params = new URLSearchParams();
233
407
  if (args?.fromDate)
@@ -240,6 +414,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
240
414
  params.set("page-size", String(args.pageSize));
241
415
  return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/credit-cards-accounts/v2/accounts/${args?.creditCardAccountId}/transactions?${params}`), null, 2) }] };
242
416
  }
417
+ case "list_loans": {
418
+ const params = new URLSearchParams();
419
+ if (args?.page)
420
+ params.set("page", String(args.page));
421
+ if (args?.pageSize)
422
+ params.set("page-size", String(args.pageSize));
423
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/loans/v2/contracts?${params}`), null, 2) }] };
424
+ }
425
+ case "get_loan_payments": {
426
+ const params = new URLSearchParams();
427
+ if (args?.page)
428
+ params.set("page", String(args.page));
429
+ if (args?.pageSize)
430
+ params.set("page-size", String(args.pageSize));
431
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/loans/v2/contracts/${args?.contractId}/payments?${params}`), null, 2) }] };
432
+ }
433
+ case "list_financings": {
434
+ const params = new URLSearchParams();
435
+ if (args?.page)
436
+ params.set("page", String(args.page));
437
+ if (args?.pageSize)
438
+ params.set("page-size", String(args.pageSize));
439
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/financings/v2/contracts?${params}`), null, 2) }] };
440
+ }
243
441
  case "list_investments": {
244
442
  const investmentType = args?.investmentType || "BANK_FIXED_INCOMES";
245
443
  const params = new URLSearchParams();
@@ -249,6 +447,51 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
249
447
  params.set("page-size", String(args.pageSize));
250
448
  return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/investments/v1/${investmentType.toLowerCase().replace(/_/g, "-")}?${params}`), null, 2) }] };
251
449
  }
450
+ case "create_payment_consent": {
451
+ const payload = {
452
+ data: {
453
+ loggedUser: { document: { identification: args?.loggedUserCpf, rel: "CPF" } },
454
+ creditor: {
455
+ personType: String(args?.creditorCpfCnpj).length > 11 ? "PESSOA_JURIDICA" : "PESSOA_NATURAL",
456
+ cpfCnpj: args?.creditorCpfCnpj,
457
+ name: args?.creditorName,
458
+ },
459
+ payment: {
460
+ type: args?.paymentType || "PIX",
461
+ currency: args?.paymentCurrency || "BRL",
462
+ amount: args?.paymentAmount,
463
+ details: { localInstrument: args?.localInstrument || "DICT" },
464
+ },
465
+ expirationDateTime: args?.expirationDateTime,
466
+ },
467
+ };
468
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("POST", "/open-banking/payments/v3/consents", payload), null, 2) }] };
469
+ }
470
+ case "create_payment": {
471
+ const payload = {
472
+ data: {
473
+ consentId: args?.consentId,
474
+ creditorAccount: {
475
+ ispb: args?.creditorAccountIspb,
476
+ issuer: args?.creditorAccountIssuer,
477
+ number: args?.creditorAccountNumber,
478
+ accountType: args?.creditorAccountType,
479
+ },
480
+ payment: {
481
+ currency: args?.paymentCurrency || "BRL",
482
+ amount: args?.paymentAmount,
483
+ },
484
+ remittanceInformation: args?.remittanceInformation,
485
+ qrCode: args?.qrCode,
486
+ proxy: args?.proxy,
487
+ },
488
+ };
489
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("POST", "/open-banking/payments/v3/pix/payments", payload), null, 2) }] };
490
+ }
491
+ case "get_personal_qualifications":
492
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", "/open-banking/customers/v2/personal/qualifications"), null, 2) }] };
493
+ case "get_business_qualifications":
494
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", "/open-banking/customers/v2/business/qualifications"), null, 2) }] };
252
495
  default:
253
496
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
254
497
  }
@@ -258,11 +501,46 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
258
501
  }
259
502
  });
260
503
  async function main() {
261
- if (!BASE_URL || !CLIENT_ID || !CLIENT_SECRET) {
262
- console.error("OPEN_FINANCE_BASE_URL, OPEN_FINANCE_CLIENT_ID, and OPEN_FINANCE_CLIENT_SECRET environment variables are required");
263
- process.exit(1);
504
+ if (process.argv.includes("--http") || process.env.MCP_HTTP === "true") {
505
+ const { default: express } = await import("express");
506
+ const { randomUUID } = await import("node:crypto");
507
+ const app = express();
508
+ app.use(express.json());
509
+ const transports = new Map();
510
+ app.get("/health", (_req, res) => res.json({ status: "ok", sessions: transports.size }));
511
+ app.post("/mcp", async (req, res) => {
512
+ const sid = req.headers["mcp-session-id"];
513
+ if (sid && transports.has(sid)) {
514
+ await transports.get(sid).handleRequest(req, res, req.body);
515
+ return;
516
+ }
517
+ if (!sid && isInitializeRequest(req.body)) {
518
+ const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
519
+ t.onclose = () => { if (t.sessionId)
520
+ transports.delete(t.sessionId); };
521
+ const s = new Server({ name: "mcp-open-finance", version: "0.2.0" }, { capabilities: { tools: {} } });
522
+ server._requestHandlers.forEach((v, k) => s._requestHandlers.set(k, v));
523
+ server._notificationHandlers?.forEach((v, k) => s._notificationHandlers.set(k, v));
524
+ await s.connect(t);
525
+ await t.handleRequest(req, res, req.body);
526
+ return;
527
+ }
528
+ res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });
529
+ });
530
+ app.get("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
531
+ await transports.get(sid).handleRequest(req, res);
532
+ else
533
+ res.status(400).send("Invalid session"); });
534
+ app.delete("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
535
+ await transports.get(sid).handleRequest(req, res);
536
+ else
537
+ res.status(400).send("Invalid session"); });
538
+ const port = Number(process.env.MCP_PORT) || 3000;
539
+ app.listen(port, () => { console.error(`MCP HTTP server on http://localhost:${port}/mcp`); });
540
+ }
541
+ else {
542
+ const transport = new StdioServerTransport();
543
+ await server.connect(transport);
264
544
  }
265
- const transport = new StdioServerTransport();
266
- await server.connect(transport);
267
545
  }
268
546
  main().catch(console.error);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@codespar/mcp-open-finance",
3
- "version": "0.1.0",
4
- "description": "MCP server for Open Finance Brasil — accounts, transactions, consents, investments",
3
+ "version": "0.2.0",
4
+ "description": "MCP server for Open Finance Brasil — accounts, transactions, consents, credit cards, loans, financings, investments, payment-initiation, customer qualifications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "bin": {
@@ -26,5 +26,6 @@
26
26
  "pix",
27
27
  "accounts",
28
28
  "brazil"
29
- ]
29
+ ],
30
+ "mcpName": "io.github.codespar/mcp-open-finance"
30
31
  }
package/server.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.codespar/mcp-open-finance",
4
+ "description": "MCP server for Open Finance Brasil — accounts, transactions, consents, credit cards, loans, financings, investments, payment-initiation, customer qualifications",
5
+ "repository": {
6
+ "url": "https://github.com/codespar/mcp-dev-brasil",
7
+ "source": "github",
8
+ "subfolder": "packages/banking/open-finance"
9
+ },
10
+ "version": "0.2.0",
11
+ "packages": [
12
+ {
13
+ "registryType": "npm",
14
+ "identifier": "@codespar/mcp-open-finance",
15
+ "version": "0.2.0",
16
+ "transport": {
17
+ "type": "stdio"
18
+ },
19
+ "environmentVariables": [
20
+ {
21
+ "name": "OPEN_FINANCE_CLIENT_SECRET",
22
+ "description": "API key for open-finance",
23
+ "isRequired": true,
24
+ "format": "string",
25
+ "isSecret": true
26
+ }
27
+ ]
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,53 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ let listToolsHandler: Function;
4
+ let callToolHandler: Function;
5
+
6
+ vi.mock("@modelcontextprotocol/sdk/server/index.js", () => {
7
+ class FakeServer {
8
+ constructor() {}
9
+ setRequestHandler(schema: any, handler: Function) {
10
+ if (JSON.stringify(schema).includes("tools/list")) listToolsHandler = handler;
11
+ if (JSON.stringify(schema).includes("tools/call")) callToolHandler = handler;
12
+ }
13
+ connect() { return Promise.resolve(); }
14
+ }
15
+ return { Server: FakeServer };
16
+ });
17
+
18
+ vi.mock("@modelcontextprotocol/sdk/server/stdio.js", () => ({ StdioServerTransport: class {} }));
19
+
20
+ process.env.OPEN_FINANCE_BASE_URL = "https://api.bank.example.com";
21
+ process.env.OPEN_FINANCE_CLIENT_ID = "test-id";
22
+ process.env.OPEN_FINANCE_CLIENT_SECRET = "test-secret";
23
+
24
+ const mockFetch = vi.fn();
25
+ global.fetch = mockFetch as any;
26
+
27
+ beforeEach(async () => {
28
+ vi.resetModules();
29
+ listToolsHandler = undefined as any;
30
+ callToolHandler = undefined as any;
31
+ mockFetch.mockReset();
32
+ global.fetch = mockFetch as any;
33
+ mockFetch.mockResolvedValue({ ok: true, json: () => Promise.resolve({ access_token: "tok", expires_in: 3600 }) });
34
+ await import("../index.js");
35
+ });
36
+
37
+ describe("mcp-open-finance", () => {
38
+ it("should register 8 tools", async () => {
39
+ const result = await listToolsHandler();
40
+ expect(result.tools).toHaveLength(8);
41
+ });
42
+
43
+ it("should call correct API endpoint for get_account_balance", async () => {
44
+ mockFetch
45
+ .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ access_token: "tok", expires_in: 3600 }) })
46
+ .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ data: { availableAmount: 1000 } }) });
47
+
48
+ await callToolHandler({ params: { name: "get_account_balance", arguments: { accountId: "acc_123" } } });
49
+
50
+ const lastCall = mockFetch.mock.calls[mockFetch.mock.calls.length - 1];
51
+ expect(lastCall[0]).toContain("/open-banking/accounts/v2/accounts/acc_123/balances");
52
+ });
53
+ });
package/src/index.ts CHANGED
@@ -7,11 +7,21 @@
7
7
  * - list_accounts: List customer accounts
8
8
  * - get_account_balance: Get account balance
9
9
  * - list_transactions: List account transactions
10
+ * - get_account_overdraft_limits: Get account overdraft limits
10
11
  * - get_consent: Get consent details
11
12
  * - create_consent: Create a new consent request
13
+ * - revoke_consent: Revoke an existing consent
12
14
  * - list_credit_cards: List credit card accounts
15
+ * - get_credit_card_bills: Get credit card bills
13
16
  * - get_credit_card_transactions: Get credit card transactions
17
+ * - list_loans: List loan contracts
18
+ * - get_loan_payments: Get loan payment schedule
19
+ * - list_financings: List financing contracts
14
20
  * - list_investments: List investment products
21
+ * - create_payment_consent: Create payment-initiation consent
22
+ * - create_payment: Initiate a payment
23
+ * - get_personal_qualifications: Get personal customer qualifications
24
+ * - get_business_qualifications: Get business customer qualifications
15
25
  *
16
26
  * Environment:
17
27
  * OPEN_FINANCE_BASE_URL — Institution API base URL
@@ -21,6 +31,8 @@
21
31
 
22
32
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
23
33
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
34
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
35
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
24
36
  import {
25
37
  CallToolRequestSchema,
26
38
  ListToolsRequestSchema,
@@ -43,7 +55,7 @@ async function getAccessToken(): Promise<string> {
43
55
  grant_type: "client_credentials",
44
56
  client_id: CLIENT_ID,
45
57
  client_secret: CLIENT_SECRET,
46
- scope: "openid accounts credit-cards-accounts resources consents investments",
58
+ scope: "openid accounts credit-cards-accounts resources consents investments loans financings customers payments",
47
59
  }),
48
60
  });
49
61
  if (!res.ok) {
@@ -74,7 +86,7 @@ async function openFinanceRequest(method: string, path: string, body?: unknown):
74
86
  }
75
87
 
76
88
  const server = new Server(
77
- { name: "mcp-open-finance", version: "0.1.0" },
89
+ { name: "mcp-open-finance", version: "0.2.0" },
78
90
  { capabilities: { tools: {} } }
79
91
  );
80
92
 
@@ -121,6 +133,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
121
133
  required: ["consentId", "accountId"],
122
134
  },
123
135
  },
136
+ {
137
+ name: "get_account_overdraft_limits",
138
+ description: "Get account overdraft (limites) via Open Finance",
139
+ inputSchema: {
140
+ type: "object",
141
+ properties: {
142
+ consentId: { type: "string", description: "Consent ID" },
143
+ accountId: { type: "string", description: "Account ID" },
144
+ },
145
+ required: ["consentId", "accountId"],
146
+ },
147
+ },
124
148
  {
125
149
  name: "get_consent",
126
150
  description: "Get consent details by ID",
@@ -150,6 +174,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
150
174
  required: ["permissions", "expirationDateTime"],
151
175
  },
152
176
  },
177
+ {
178
+ name: "revoke_consent",
179
+ description: "Revoke an existing consent (data or payment)",
180
+ inputSchema: {
181
+ type: "object",
182
+ properties: {
183
+ consentId: { type: "string", description: "Consent ID to revoke" },
184
+ consentType: { type: "string", enum: ["data", "payment"], description: "Consent type (default: data)" },
185
+ },
186
+ required: ["consentId"],
187
+ },
188
+ },
153
189
  {
154
190
  name: "list_credit_cards",
155
191
  description: "List credit card accounts via Open Finance",
@@ -163,6 +199,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
163
199
  required: ["consentId"],
164
200
  },
165
201
  },
202
+ {
203
+ name: "get_credit_card_bills",
204
+ description: "Get credit card bills (faturas) via Open Finance",
205
+ inputSchema: {
206
+ type: "object",
207
+ properties: {
208
+ consentId: { type: "string", description: "Consent ID" },
209
+ creditCardAccountId: { type: "string", description: "Credit card account ID" },
210
+ fromDueDate: { type: "string", description: "Start due date (YYYY-MM-DD)" },
211
+ toDueDate: { type: "string", description: "End due date (YYYY-MM-DD)" },
212
+ page: { type: "number", description: "Page number" },
213
+ pageSize: { type: "number", description: "Items per page" },
214
+ },
215
+ required: ["consentId", "creditCardAccountId"],
216
+ },
217
+ },
166
218
  {
167
219
  name: "get_credit_card_transactions",
168
220
  description: "Get credit card transactions via Open Finance",
@@ -179,6 +231,46 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
179
231
  required: ["consentId", "creditCardAccountId"],
180
232
  },
181
233
  },
234
+ {
235
+ name: "list_loans",
236
+ description: "List loan contracts (empréstimos) via Open Finance",
237
+ inputSchema: {
238
+ type: "object",
239
+ properties: {
240
+ consentId: { type: "string", description: "Consent ID" },
241
+ page: { type: "number", description: "Page number" },
242
+ pageSize: { type: "number", description: "Items per page" },
243
+ },
244
+ required: ["consentId"],
245
+ },
246
+ },
247
+ {
248
+ name: "get_loan_payments",
249
+ description: "Get loan payment schedule via Open Finance",
250
+ inputSchema: {
251
+ type: "object",
252
+ properties: {
253
+ consentId: { type: "string", description: "Consent ID" },
254
+ contractId: { type: "string", description: "Loan contract ID" },
255
+ page: { type: "number", description: "Page number" },
256
+ pageSize: { type: "number", description: "Items per page" },
257
+ },
258
+ required: ["consentId", "contractId"],
259
+ },
260
+ },
261
+ {
262
+ name: "list_financings",
263
+ description: "List financing contracts (financiamentos) via Open Finance",
264
+ inputSchema: {
265
+ type: "object",
266
+ properties: {
267
+ consentId: { type: "string", description: "Consent ID" },
268
+ page: { type: "number", description: "Page number" },
269
+ pageSize: { type: "number", description: "Items per page" },
270
+ },
271
+ required: ["consentId"],
272
+ },
273
+ },
182
274
  {
183
275
  name: "list_investments",
184
276
  description: "List investment products via Open Finance",
@@ -193,11 +285,72 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
193
285
  required: ["consentId"],
194
286
  },
195
287
  },
288
+ {
289
+ name: "create_payment_consent",
290
+ description: "Create payment-initiation consent (e.g., PIX) via Open Finance",
291
+ inputSchema: {
292
+ type: "object",
293
+ properties: {
294
+ loggedUserCpf: { type: "string", description: "Logged user CPF" },
295
+ creditorName: { type: "string", description: "Creditor name" },
296
+ creditorCpfCnpj: { type: "string", description: "Creditor CPF/CNPJ" },
297
+ paymentAmount: { type: "string", description: "Payment amount (e.g., '100.00')" },
298
+ paymentCurrency: { type: "string", description: "ISO 4217 currency (default: BRL)" },
299
+ localInstrument: { type: "string", enum: ["MANU", "DICT", "QRDN", "QRES", "INIC"], description: "PIX local instrument (default: DICT)" },
300
+ paymentType: { type: "string", enum: ["PIX", "TED", "TEF", "BOLETO"], description: "Payment type (default: PIX)" },
301
+ expirationDateTime: { type: "string", description: "Consent expiration (ISO 8601)" },
302
+ },
303
+ required: ["loggedUserCpf", "creditorName", "creditorCpfCnpj", "paymentAmount"],
304
+ },
305
+ },
306
+ {
307
+ name: "create_payment",
308
+ description: "Initiate a payment using an authorized payment consent",
309
+ inputSchema: {
310
+ type: "object",
311
+ properties: {
312
+ consentId: { type: "string", description: "Authorized payment consent ID" },
313
+ creditorAccountIspb: { type: "string", description: "Creditor account ISPB" },
314
+ creditorAccountIssuer: { type: "string", description: "Creditor account issuer (agência)" },
315
+ creditorAccountNumber: { type: "string", description: "Creditor account number" },
316
+ creditorAccountType: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"], description: "Creditor account type" },
317
+ paymentAmount: { type: "string", description: "Payment amount (e.g., '100.00')" },
318
+ paymentCurrency: { type: "string", description: "ISO 4217 currency (default: BRL)" },
319
+ remittanceInformation: { type: "string", description: "Free-text remittance info" },
320
+ qrCode: { type: "string", description: "PIX QR Code payload (optional)" },
321
+ proxy: { type: "string", description: "PIX key/proxy (optional)" },
322
+ },
323
+ required: ["consentId", "creditorAccountIspb", "creditorAccountIssuer", "creditorAccountNumber", "creditorAccountType", "paymentAmount"],
324
+ },
325
+ },
326
+ {
327
+ name: "get_personal_qualifications",
328
+ description: "Get personal customer qualifications (income, occupation) via Open Finance",
329
+ inputSchema: {
330
+ type: "object",
331
+ properties: {
332
+ consentId: { type: "string", description: "Consent ID" },
333
+ },
334
+ required: ["consentId"],
335
+ },
336
+ },
337
+ {
338
+ name: "get_business_qualifications",
339
+ description: "Get business customer qualifications via Open Finance",
340
+ inputSchema: {
341
+ type: "object",
342
+ properties: {
343
+ consentId: { type: "string", description: "Consent ID" },
344
+ },
345
+ required: ["consentId"],
346
+ },
347
+ },
196
348
  ],
197
349
  }));
198
350
 
199
351
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
200
- const { name, arguments: args } = request.params;
352
+ const { name, arguments: rawArgs } = request.params;
353
+ const args = rawArgs as Record<string, unknown> | undefined;
201
354
 
202
355
  try {
203
356
  switch (name) {
@@ -217,6 +370,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
217
370
  if (args?.pageSize) params.set("page-size", String(args.pageSize));
218
371
  return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/accounts/v2/accounts/${args?.accountId}/transactions?${params}`), null, 2) }] };
219
372
  }
373
+ case "get_account_overdraft_limits":
374
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/accounts/v2/accounts/${args?.accountId}/overdraft-limits`), null, 2) }] };
220
375
  case "get_consent":
221
376
  return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/consents/v2/consents/${args?.consentId}`), null, 2) }] };
222
377
  case "create_consent": {
@@ -230,12 +385,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
230
385
  };
231
386
  return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("POST", "/open-banking/consents/v2/consents", payload), null, 2) }] };
232
387
  }
388
+ case "revoke_consent": {
389
+ const consentType = (args?.consentType as string) || "data";
390
+ const path = consentType === "payment"
391
+ ? `/open-banking/payments/v3/consents/${args?.consentId}`
392
+ : `/open-banking/consents/v2/consents/${args?.consentId}`;
393
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("DELETE", path), null, 2) }] };
394
+ }
233
395
  case "list_credit_cards": {
234
396
  const params = new URLSearchParams();
235
397
  if (args?.page) params.set("page", String(args.page));
236
398
  if (args?.pageSize) params.set("page-size", String(args.pageSize));
237
399
  return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/credit-cards-accounts/v2/accounts?${params}`), null, 2) }] };
238
400
  }
401
+ case "get_credit_card_bills": {
402
+ const params = new URLSearchParams();
403
+ if (args?.fromDueDate) params.set("fromDueDate", String(args.fromDueDate));
404
+ if (args?.toDueDate) params.set("toDueDate", String(args.toDueDate));
405
+ if (args?.page) params.set("page", String(args.page));
406
+ if (args?.pageSize) params.set("page-size", String(args.pageSize));
407
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/credit-cards-accounts/v2/accounts/${args?.creditCardAccountId}/bills?${params}`), null, 2) }] };
408
+ }
239
409
  case "get_credit_card_transactions": {
240
410
  const params = new URLSearchParams();
241
411
  if (args?.fromDate) params.set("fromTransactionDate", String(args.fromDate));
@@ -244,13 +414,76 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
244
414
  if (args?.pageSize) params.set("page-size", String(args.pageSize));
245
415
  return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/credit-cards-accounts/v2/accounts/${args?.creditCardAccountId}/transactions?${params}`), null, 2) }] };
246
416
  }
417
+ case "list_loans": {
418
+ const params = new URLSearchParams();
419
+ if (args?.page) params.set("page", String(args.page));
420
+ if (args?.pageSize) params.set("page-size", String(args.pageSize));
421
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/loans/v2/contracts?${params}`), null, 2) }] };
422
+ }
423
+ case "get_loan_payments": {
424
+ const params = new URLSearchParams();
425
+ if (args?.page) params.set("page", String(args.page));
426
+ if (args?.pageSize) params.set("page-size", String(args.pageSize));
427
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/loans/v2/contracts/${args?.contractId}/payments?${params}`), null, 2) }] };
428
+ }
429
+ case "list_financings": {
430
+ const params = new URLSearchParams();
431
+ if (args?.page) params.set("page", String(args.page));
432
+ if (args?.pageSize) params.set("page-size", String(args.pageSize));
433
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/financings/v2/contracts?${params}`), null, 2) }] };
434
+ }
247
435
  case "list_investments": {
248
- const investmentType = args?.investmentType || "BANK_FIXED_INCOMES";
436
+ const investmentType = (args?.investmentType as string) || "BANK_FIXED_INCOMES";
249
437
  const params = new URLSearchParams();
250
438
  if (args?.page) params.set("page", String(args.page));
251
439
  if (args?.pageSize) params.set("page-size", String(args.pageSize));
252
440
  return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", `/open-banking/investments/v1/${investmentType.toLowerCase().replace(/_/g, "-")}?${params}`), null, 2) }] };
253
441
  }
442
+ case "create_payment_consent": {
443
+ const payload = {
444
+ data: {
445
+ loggedUser: { document: { identification: args?.loggedUserCpf, rel: "CPF" } },
446
+ creditor: {
447
+ personType: String(args?.creditorCpfCnpj).length > 11 ? "PESSOA_JURIDICA" : "PESSOA_NATURAL",
448
+ cpfCnpj: args?.creditorCpfCnpj,
449
+ name: args?.creditorName,
450
+ },
451
+ payment: {
452
+ type: (args?.paymentType as string) || "PIX",
453
+ currency: (args?.paymentCurrency as string) || "BRL",
454
+ amount: args?.paymentAmount,
455
+ details: { localInstrument: (args?.localInstrument as string) || "DICT" },
456
+ },
457
+ expirationDateTime: args?.expirationDateTime,
458
+ },
459
+ };
460
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("POST", "/open-banking/payments/v3/consents", payload), null, 2) }] };
461
+ }
462
+ case "create_payment": {
463
+ const payload = {
464
+ data: {
465
+ consentId: args?.consentId,
466
+ creditorAccount: {
467
+ ispb: args?.creditorAccountIspb,
468
+ issuer: args?.creditorAccountIssuer,
469
+ number: args?.creditorAccountNumber,
470
+ accountType: args?.creditorAccountType,
471
+ },
472
+ payment: {
473
+ currency: (args?.paymentCurrency as string) || "BRL",
474
+ amount: args?.paymentAmount,
475
+ },
476
+ remittanceInformation: args?.remittanceInformation,
477
+ qrCode: args?.qrCode,
478
+ proxy: args?.proxy,
479
+ },
480
+ };
481
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("POST", "/open-banking/payments/v3/pix/payments", payload), null, 2) }] };
482
+ }
483
+ case "get_personal_qualifications":
484
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", "/open-banking/customers/v2/personal/qualifications"), null, 2) }] };
485
+ case "get_business_qualifications":
486
+ return { content: [{ type: "text", text: JSON.stringify(await openFinanceRequest("GET", "/open-banking/customers/v2/business/qualifications"), null, 2) }] };
254
487
  default:
255
488
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
256
489
  }
@@ -260,12 +493,32 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
260
493
  });
261
494
 
262
495
  async function main() {
263
- if (!BASE_URL || !CLIENT_ID || !CLIENT_SECRET) {
264
- console.error("OPEN_FINANCE_BASE_URL, OPEN_FINANCE_CLIENT_ID, and OPEN_FINANCE_CLIENT_SECRET environment variables are required");
265
- process.exit(1);
496
+ if (process.argv.includes("--http") || process.env.MCP_HTTP === "true") {
497
+ const { default: express } = await import("express");
498
+ const { randomUUID } = await import("node:crypto");
499
+ const app = express();
500
+ app.use(express.json());
501
+ const transports = new Map<string, StreamableHTTPServerTransport>();
502
+ app.get("/health", (_req: any, res: any) => res.json({ status: "ok", sessions: transports.size }));
503
+ app.post("/mcp", async (req: any, res: any) => {
504
+ const sid = req.headers["mcp-session-id"] as string | undefined;
505
+ if (sid && transports.has(sid)) { await transports.get(sid)!.handleRequest(req, res, req.body); return; }
506
+ if (!sid && isInitializeRequest(req.body)) {
507
+ const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
508
+ t.onclose = () => { if (t.sessionId) transports.delete(t.sessionId); };
509
+ const s = new Server({ name: "mcp-open-finance", version: "0.2.0" }, { capabilities: { tools: {} } }); (server as any)._requestHandlers.forEach((v: any, k: any) => (s as any)._requestHandlers.set(k, v)); (server as any)._notificationHandlers?.forEach((v: any, k: any) => (s as any)._notificationHandlers.set(k, v)); await s.connect(t);
510
+ await t.handleRequest(req, res, req.body); return;
511
+ }
512
+ res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });
513
+ });
514
+ app.get("/mcp", async (req: any, res: any) => { const sid = req.headers["mcp-session-id"] as string; if (sid && transports.has(sid)) await transports.get(sid)!.handleRequest(req, res); else res.status(400).send("Invalid session"); });
515
+ app.delete("/mcp", async (req: any, res: any) => { const sid = req.headers["mcp-session-id"] as string; if (sid && transports.has(sid)) await transports.get(sid)!.handleRequest(req, res); else res.status(400).send("Invalid session"); });
516
+ const port = Number(process.env.MCP_PORT) || 3000;
517
+ app.listen(port, () => { console.error(`MCP HTTP server on http://localhost:${port}/mcp`); });
518
+ } else {
519
+ const transport = new StdioServerTransport();
520
+ await server.connect(transport);
266
521
  }
267
- const transport = new StdioServerTransport();
268
- await server.connect(transport);
269
522
  }
270
523
 
271
524
  main().catch(console.error);
package/tsconfig.json CHANGED
@@ -6,6 +6,7 @@
6
6
  "outDir": "./dist",
7
7
  "rootDir": "./src",
8
8
  "strict": true,
9
+ "skipLibCheck": true,
9
10
  "esModuleInterop": true,
10
11
  "declaration": true
11
12
  },