@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 +120 -0
- package/dist/index.js +286 -8
- package/package.json +4 -3
- package/server.json +30 -0
- package/src/__tests__/index.test.ts +53 -0
- package/src/index.ts +262 -9
- package/tsconfig.json +1 -0
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
|
+
[](https://www.npmjs.com/package/@codespar/mcp-open-finance)
|
|
6
|
+
[](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.
|
|
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:
|
|
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 (
|
|
262
|
-
|
|
263
|
-
|
|
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.
|
|
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.
|
|
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:
|
|
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 (
|
|
264
|
-
|
|
265
|
-
|
|
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);
|