@codespar/mcp-banco-do-brasil 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -0
- package/dist/index.js +560 -0
- package/package.json +35 -0
- package/server.json +65 -0
- package/src/index.ts +586 -0
- package/tsconfig.json +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @codespar/mcp-banco-do-brasil
|
|
2
|
+
|
|
3
|
+
MCP server for [Banco do Brasil](https://developers.bb.com.br) — Brazil's top public bank.
|
|
4
|
+
|
|
5
|
+
BB exposes one of the broadest public-bank API surfaces in the country across Pix, Cobranças, Conta-Corrente, Open Finance, and Arrecadação. Merchants doing high-volume Pix and boleto operations integrate directly with BB instead of going through a PSP.
|
|
6
|
+
|
|
7
|
+
## Status: alpha (`0.1.0-alpha.1`)
|
|
8
|
+
|
|
9
|
+
BB's Developer Portal is **contract-gated** — the full OpenAPI specs are visible only after merchant onboarding. Pix paths follow the BACEN Pix v2 standard; boleto, account, and statement paths are best-guesses based on BB's public marketing pages and conventions shared with peers (Itaú, Santander, Bradesco). Every unverified path is flagged `TODO(verify)` in `src/index.ts`. Pin to exact versions during `0.1.x`.
|
|
10
|
+
|
|
11
|
+
## Tools (10)
|
|
12
|
+
|
|
13
|
+
| Tool | Purpose |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `create_pix_cob` | Create immediate Pix charge (cob) |
|
|
16
|
+
| `get_pix_cob` | Get an immediate Pix charge by txid |
|
|
17
|
+
| `list_pix_cob` | List immediate Pix charges by date range |
|
|
18
|
+
| `create_pix_devolucao` | Refund a received Pix (devolução) |
|
|
19
|
+
| `get_pix_devolucao` | Retrieve a devolução by id |
|
|
20
|
+
| `resolve_dict_key` | Resolve a DICT key to account data |
|
|
21
|
+
| `register_dict_key` | Register a DICT key on a BB account |
|
|
22
|
+
| `delete_dict_key` | Delete a DICT key owned by the merchant |
|
|
23
|
+
| `register_boleto` | Issue a boleto via BB Cobranças |
|
|
24
|
+
| `get_boleto` | Retrieve a boleto by nosso_numero |
|
|
25
|
+
| `cancel_boleto` | Cancel (baixa) an outstanding boleto |
|
|
26
|
+
| `get_account_balance` | Conta-corrente balance |
|
|
27
|
+
| `get_statement` | Account statement transactions |
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @codespar/mcp-banco-do-brasil@0.1.0-alpha.1
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Environment
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
BB_CLIENT_ID="..." # OAuth client_id from developers.bb.com.br
|
|
39
|
+
BB_CLIENT_SECRET="..." # OAuth client_secret
|
|
40
|
+
BB_DEVELOPER_APP_KEY="..." # gw-dev-app-key — required on most calls
|
|
41
|
+
BB_CERT_PATH="/abs/path/client.crt" # mTLS cert (production only)
|
|
42
|
+
BB_KEY_PATH="/abs/path/client.key" # mTLS key (production only)
|
|
43
|
+
BB_ENV="sandbox" # or "production" (default: sandbox)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Authentication
|
|
47
|
+
|
|
48
|
+
- **OAuth2 `client_credentials`** — token endpoint at `oauth.{env}.bb.com.br`. Bearer cached until ~60s before expiry.
|
|
49
|
+
- **mTLS** — required by BACEN for Pix v2 in production. Sandbox typically allows TLS-only; cert/key are optional in sandbox and required in production.
|
|
50
|
+
- **`gw-dev-app-key`** — BB API gateway key, appended as a query param on all calls.
|
|
51
|
+
|
|
52
|
+
## Run
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx @codespar/mcp-banco-do-brasil
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Server for Banco do Brasil — Brazil's top public bank.
|
|
4
|
+
*
|
|
5
|
+
* BB exposes one of the broadest bank API surfaces in the country, covering
|
|
6
|
+
* Pix, Cobranças (boleto), Conta-Corrente, Open Finance, and Arrecadação
|
|
7
|
+
* via the Developer Portal at developers.bb.com.br.
|
|
8
|
+
*
|
|
9
|
+
* Tools (13):
|
|
10
|
+
* create_pix_cob — create immediate Pix charge (cob) with QR
|
|
11
|
+
* get_pix_cob — retrieve an immediate Pix charge by txid
|
|
12
|
+
* list_pix_cob — list immediate Pix charges by date range
|
|
13
|
+
* create_pix_devolucao — refund (devolução) a received Pix
|
|
14
|
+
* get_pix_devolucao — retrieve a devolução by id
|
|
15
|
+
* resolve_dict_key — resolve a DICT key to account data
|
|
16
|
+
* register_dict_key — register a DICT key on a BB account
|
|
17
|
+
* delete_dict_key — delete a DICT key owned by the merchant
|
|
18
|
+
* register_boleto — issue a boleto via BB Cobranças
|
|
19
|
+
* get_boleto — retrieve a boleto by nosso_numero
|
|
20
|
+
* cancel_boleto — cancel (baixa) an outstanding boleto
|
|
21
|
+
* get_account_balance — Conta-Corrente balance
|
|
22
|
+
* get_statement — Conta-Corrente statement / transactions
|
|
23
|
+
*
|
|
24
|
+
* Authentication
|
|
25
|
+
* OAuth 2.0 client_credentials + mandatory mTLS in production. BACEN
|
|
26
|
+
* requires mTLS for Pix v2; BB enforces it in production across product
|
|
27
|
+
* families. Sandbox typically accepts TLS-only — cert/key envs are
|
|
28
|
+
* therefore optional but recommended.
|
|
29
|
+
*
|
|
30
|
+
* Additionally, BB requires a developer-app-key (`gw-dev-app-key`)
|
|
31
|
+
* query param on every API call for traffic accounting.
|
|
32
|
+
*
|
|
33
|
+
* Version: 0.1.0-alpha.1
|
|
34
|
+
* developers.bb.com.br is contract-gated — full OpenAPI specs are visible
|
|
35
|
+
* only to onboarded merchants. Pix paths follow BACEN Pix v2 standard.
|
|
36
|
+
* Boleto / Conta-Corrente paths are best-guesses based on BB public docs
|
|
37
|
+
* and conventions shared with peers (Itaú, Santander, Bradesco). Every
|
|
38
|
+
* unverified path is marked TODO(verify). Treat 0.1.x as alpha and pin
|
|
39
|
+
* to exact versions.
|
|
40
|
+
*
|
|
41
|
+
* Environment
|
|
42
|
+
* BB_CLIENT_ID OAuth client id
|
|
43
|
+
* BB_CLIENT_SECRET OAuth client secret
|
|
44
|
+
* BB_DEVELOPER_APP_KEY gw-dev-app-key query param
|
|
45
|
+
* BB_CERT_PATH path to mTLS client cert (production)
|
|
46
|
+
* BB_KEY_PATH path to mTLS private key (production)
|
|
47
|
+
* BB_ENV "sandbox" | "production" (default: sandbox)
|
|
48
|
+
*
|
|
49
|
+
* Docs: https://developers.bb.com.br
|
|
50
|
+
*/
|
|
51
|
+
import { readFileSync } from "node:fs";
|
|
52
|
+
import { Agent as HttpsAgent } from "node:https";
|
|
53
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
54
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
55
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
56
|
+
const CLIENT_ID = process.env.BB_CLIENT_ID || "";
|
|
57
|
+
const CLIENT_SECRET = process.env.BB_CLIENT_SECRET || "";
|
|
58
|
+
const DEVELOPER_APP_KEY = process.env.BB_DEVELOPER_APP_KEY || "";
|
|
59
|
+
const CERT_PATH = process.env.BB_CERT_PATH || "";
|
|
60
|
+
const KEY_PATH = process.env.BB_KEY_PATH || "";
|
|
61
|
+
const BB_ENV = (process.env.BB_ENV || "sandbox").toLowerCase();
|
|
62
|
+
const IS_PROD = BB_ENV === "production";
|
|
63
|
+
// TODO(verify): production base hostnames. BB documents distinct hosts per
|
|
64
|
+
// product family (api.bb.com.br for some, api-pix.bb.com.br for Pix). The
|
|
65
|
+
// sandbox subdomain is api.sandbox.bb.com.br for most products.
|
|
66
|
+
const BASE_URL = IS_PROD ? "https://api.bb.com.br" : "https://api.sandbox.bb.com.br";
|
|
67
|
+
const OAUTH_URL = IS_PROD
|
|
68
|
+
? "https://oauth.bb.com.br/oauth/token"
|
|
69
|
+
: "https://oauth.sandbox.bb.com.br/oauth/token";
|
|
70
|
+
// Lazy-load the mTLS agent so `--help` / schema introspection doesn't crash
|
|
71
|
+
// when certs are missing. mTLS is required by BACEN in production but BB
|
|
72
|
+
// sandbox typically accepts TLS-only.
|
|
73
|
+
let mtlsAgent = null;
|
|
74
|
+
function getMtlsAgent() {
|
|
75
|
+
if (mtlsAgent)
|
|
76
|
+
return mtlsAgent;
|
|
77
|
+
if (!CERT_PATH || !KEY_PATH) {
|
|
78
|
+
if (IS_PROD) {
|
|
79
|
+
throw new Error("BB mTLS certificates are required in production. Set BB_CERT_PATH and BB_KEY_PATH " +
|
|
80
|
+
"to the client cert and private-key files issued by developers.bb.com.br.");
|
|
81
|
+
}
|
|
82
|
+
return null; // sandbox: fall back to system TLS
|
|
83
|
+
}
|
|
84
|
+
mtlsAgent = new HttpsAgent({
|
|
85
|
+
cert: readFileSync(CERT_PATH),
|
|
86
|
+
key: readFileSync(KEY_PATH),
|
|
87
|
+
keepAlive: true,
|
|
88
|
+
});
|
|
89
|
+
return mtlsAgent;
|
|
90
|
+
}
|
|
91
|
+
let tokenCache = null;
|
|
92
|
+
async function getAccessToken() {
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
if (tokenCache && tokenCache.expiresAt > now + 60_000) {
|
|
95
|
+
return tokenCache.accessToken;
|
|
96
|
+
}
|
|
97
|
+
const basic = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64");
|
|
98
|
+
const res = await fetchWithMtls(OAUTH_URL, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: {
|
|
101
|
+
Authorization: `Basic ${basic}`,
|
|
102
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
103
|
+
},
|
|
104
|
+
// TODO(verify): scope. BB scopes are product-specific (cob.write,
|
|
105
|
+
// cob.read, pix.write, pix.read, dict.read, etc.). The sandbox
|
|
106
|
+
// commonly accepts a blank/all scope for client_credentials.
|
|
107
|
+
body: "grant_type=client_credentials",
|
|
108
|
+
});
|
|
109
|
+
if (!res.ok) {
|
|
110
|
+
throw new Error(`BB OAuth ${res.status}: ${await res.text()}`);
|
|
111
|
+
}
|
|
112
|
+
const data = (await res.json());
|
|
113
|
+
tokenCache = {
|
|
114
|
+
accessToken: data.access_token,
|
|
115
|
+
expiresAt: now + data.expires_in * 1000,
|
|
116
|
+
};
|
|
117
|
+
return data.access_token;
|
|
118
|
+
}
|
|
119
|
+
// Node's global fetch does not honour an https.Agent for mTLS; we drop down
|
|
120
|
+
// to node:https manually so the client cert is presented on every call.
|
|
121
|
+
async function fetchWithMtls(url, init) {
|
|
122
|
+
const agent = getMtlsAgent();
|
|
123
|
+
const { request } = await import("node:https");
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
const u = new URL(url);
|
|
126
|
+
const req = request({
|
|
127
|
+
...(agent ? { agent } : {}),
|
|
128
|
+
method: init.method,
|
|
129
|
+
hostname: u.hostname,
|
|
130
|
+
port: u.port || 443,
|
|
131
|
+
path: u.pathname + u.search,
|
|
132
|
+
headers: init.headers ?? {},
|
|
133
|
+
}, (res) => {
|
|
134
|
+
const chunks = [];
|
|
135
|
+
res.on("data", (c) => chunks.push(c));
|
|
136
|
+
res.on("end", () => {
|
|
137
|
+
const buf = Buffer.concat(chunks);
|
|
138
|
+
const status = res.statusCode ?? 0;
|
|
139
|
+
resolve({
|
|
140
|
+
ok: status >= 200 && status < 300,
|
|
141
|
+
status,
|
|
142
|
+
text: async () => buf.toString("utf8"),
|
|
143
|
+
json: async () => JSON.parse(buf.toString("utf8")),
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
req.on("error", reject);
|
|
148
|
+
if (init.body)
|
|
149
|
+
req.write(init.body);
|
|
150
|
+
req.end();
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function appendAppKey(path) {
|
|
154
|
+
if (!DEVELOPER_APP_KEY)
|
|
155
|
+
return path;
|
|
156
|
+
const sep = path.includes("?") ? "&" : "?";
|
|
157
|
+
return `${path}${sep}gw-dev-app-key=${encodeURIComponent(DEVELOPER_APP_KEY)}`;
|
|
158
|
+
}
|
|
159
|
+
async function bbRequest(method, path, body) {
|
|
160
|
+
const token = await getAccessToken();
|
|
161
|
+
const fullPath = appendAppKey(path);
|
|
162
|
+
const res = await fetchWithMtls(`${BASE_URL}${fullPath}`, {
|
|
163
|
+
method,
|
|
164
|
+
headers: {
|
|
165
|
+
"Content-Type": "application/json",
|
|
166
|
+
Authorization: `Bearer ${token}`,
|
|
167
|
+
},
|
|
168
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
169
|
+
});
|
|
170
|
+
if (!res.ok) {
|
|
171
|
+
throw new Error(`BB API ${res.status}: ${await res.text()}`);
|
|
172
|
+
}
|
|
173
|
+
return res.json();
|
|
174
|
+
}
|
|
175
|
+
const server = new Server({ name: "mcp-banco-do-brasil", version: "0.1.0-alpha.1" }, { capabilities: { tools: {} } });
|
|
176
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
177
|
+
tools: [
|
|
178
|
+
{
|
|
179
|
+
name: "create_pix_cob",
|
|
180
|
+
description: "Create an immediate Pix charge (cob) with QR code. Returns txid, EMV copy-paste payload, and location URL. BACEN Pix v2 standard.",
|
|
181
|
+
inputSchema: {
|
|
182
|
+
type: "object",
|
|
183
|
+
properties: {
|
|
184
|
+
amount: { type: "string", description: "Amount in BRL major units, e.g. '99.90'" },
|
|
185
|
+
payer: {
|
|
186
|
+
type: "object",
|
|
187
|
+
description: "Payer identification (CPF/CNPJ + name)",
|
|
188
|
+
properties: {
|
|
189
|
+
document: { type: "string", description: "CPF or CNPJ digits only" },
|
|
190
|
+
name: { type: "string" },
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
expires_in: { type: "number", description: "QR lifetime in seconds (default 3600)" },
|
|
194
|
+
description: { type: "string", description: "Payer-visible description (solicitacaoPagador)" },
|
|
195
|
+
dict_key: { type: "string", description: "Recebedor DICT key — must be a key owned by the merchant" },
|
|
196
|
+
txid: { type: "string", description: "Optional merchant-supplied txid (26-35 alphanumeric). Omit to have BCB assign one." },
|
|
197
|
+
},
|
|
198
|
+
required: ["amount", "dict_key"],
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "get_pix_cob",
|
|
203
|
+
description: "Retrieve an immediate Pix charge by its txid.",
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: "object",
|
|
206
|
+
properties: {
|
|
207
|
+
txid: { type: "string", description: "BACEN txid (26-35 alphanumeric)" },
|
|
208
|
+
},
|
|
209
|
+
required: ["txid"],
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "list_pix_cob",
|
|
214
|
+
description: "List immediate Pix charges (cob) by date range. Paginated per BACEN Pix v2.",
|
|
215
|
+
inputSchema: {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: {
|
|
218
|
+
from: { type: "string", description: "Start date-time ISO-8601 (inicio)" },
|
|
219
|
+
to: { type: "string", description: "End date-time ISO-8601 (fim)" },
|
|
220
|
+
status: { type: "string", description: "Status filter: ATIVA | CONCLUIDA | REMOVIDA_PELO_USUARIO_RECEBEDOR | REMOVIDA_PELO_PSP" },
|
|
221
|
+
page: { type: "number", description: "paginacao.paginaAtual" },
|
|
222
|
+
page_size: { type: "number", description: "paginacao.itensPorPagina" },
|
|
223
|
+
},
|
|
224
|
+
required: ["from", "to"],
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: "create_pix_devolucao",
|
|
229
|
+
description: "Refund (devolução) a previously received Pix. Must reference the original endToEndId and a merchant-side refund id.",
|
|
230
|
+
inputSchema: {
|
|
231
|
+
type: "object",
|
|
232
|
+
properties: {
|
|
233
|
+
end_to_end_id: { type: "string", description: "Original Pix endToEndId" },
|
|
234
|
+
refund_id: { type: "string", description: "Merchant-side refund id (id da devolução, alphanumeric up to 35)" },
|
|
235
|
+
amount: { type: "string", description: "Refund amount in BRL major units. Omit for full refund." },
|
|
236
|
+
reason: { type: "string", description: "Free-text reason (descricao)" },
|
|
237
|
+
},
|
|
238
|
+
required: ["end_to_end_id", "refund_id"],
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: "get_pix_devolucao",
|
|
243
|
+
description: "Retrieve a Pix devolução by its endToEndId + refund id.",
|
|
244
|
+
inputSchema: {
|
|
245
|
+
type: "object",
|
|
246
|
+
properties: {
|
|
247
|
+
end_to_end_id: { type: "string", description: "Original Pix endToEndId" },
|
|
248
|
+
refund_id: { type: "string", description: "Merchant-side refund id" },
|
|
249
|
+
},
|
|
250
|
+
required: ["end_to_end_id", "refund_id"],
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "resolve_dict_key",
|
|
255
|
+
description: "Resolve a DICT key (CPF, CNPJ, email, phone, EVP) to the owner's account data before sending a Pix. Subject to BCB rate limits per consenting payer.",
|
|
256
|
+
inputSchema: {
|
|
257
|
+
type: "object",
|
|
258
|
+
properties: {
|
|
259
|
+
key: { type: "string", description: "DICT key — CPF, CNPJ, email, phone (+55...), or EVP UUID" },
|
|
260
|
+
payer_document: { type: "string", description: "End-payer CPF/CNPJ for BCB audit logging" },
|
|
261
|
+
},
|
|
262
|
+
required: ["key"],
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "register_dict_key",
|
|
267
|
+
description: "Register a DICT key on a BB account owned by the merchant. Some key types (email/phone) require BCB confirmation flows.",
|
|
268
|
+
inputSchema: {
|
|
269
|
+
type: "object",
|
|
270
|
+
properties: {
|
|
271
|
+
key_type: { type: "string", description: "DICT key type: CPF | CNPJ | EMAIL | PHONE | EVP" },
|
|
272
|
+
key: { type: "string", description: "Key value. Omit for EVP (BCB generates UUID)." },
|
|
273
|
+
account: {
|
|
274
|
+
type: "object",
|
|
275
|
+
properties: {
|
|
276
|
+
branch: { type: "string", description: "Agência" },
|
|
277
|
+
account: { type: "string", description: "Conta" },
|
|
278
|
+
account_type: { type: "string", description: "CACC | SVGS | SLRY | TRAN" },
|
|
279
|
+
},
|
|
280
|
+
required: ["branch", "account", "account_type"],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
required: ["key_type", "account"],
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: "delete_dict_key",
|
|
288
|
+
description: "Delete a DICT key owned by the merchant. Irreversible — key becomes available for re-registration after BCB lockout window.",
|
|
289
|
+
inputSchema: {
|
|
290
|
+
type: "object",
|
|
291
|
+
properties: {
|
|
292
|
+
key: { type: "string", description: "DICT key value to delete" },
|
|
293
|
+
},
|
|
294
|
+
required: ["key"],
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: "register_boleto",
|
|
299
|
+
description: "Issue a boleto via BB Cobranças. Returns nosso_numero, linha digitável, barcode, and PDF URL.",
|
|
300
|
+
inputSchema: {
|
|
301
|
+
type: "object",
|
|
302
|
+
properties: {
|
|
303
|
+
amount: { type: "string", description: "Amount in BRL major units, e.g. '150.00'" },
|
|
304
|
+
due_date: { type: "string", description: "Due date ISO-8601 (YYYY-MM-DD)" },
|
|
305
|
+
convenio: { type: "string", description: "BB convênio (numeroConvenio) registered for the merchant" },
|
|
306
|
+
payer: {
|
|
307
|
+
type: "object",
|
|
308
|
+
properties: {
|
|
309
|
+
name: { type: "string" },
|
|
310
|
+
document: { type: "string", description: "CPF or CNPJ digits only" },
|
|
311
|
+
address: {
|
|
312
|
+
type: "object",
|
|
313
|
+
properties: {
|
|
314
|
+
street: { type: "string" },
|
|
315
|
+
number: { type: "string" },
|
|
316
|
+
district: { type: "string" },
|
|
317
|
+
city: { type: "string" },
|
|
318
|
+
state: { type: "string", description: "2-letter UF code" },
|
|
319
|
+
postal_code: { type: "string", description: "CEP digits only" },
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
required: ["name", "document"],
|
|
324
|
+
},
|
|
325
|
+
our_number: { type: "string", description: "Nosso_numero. Omit to have BB assign one." },
|
|
326
|
+
fine: { type: "object", description: "Multa: { percentage?, amount?, days_after_due? }" },
|
|
327
|
+
interest: { type: "object", description: "Juros: { percentage?, amount? }" },
|
|
328
|
+
discount: { type: "object", description: "Desconto: { percentage?, amount?, until? }" },
|
|
329
|
+
},
|
|
330
|
+
required: ["amount", "due_date", "convenio", "payer"],
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: "get_boleto",
|
|
335
|
+
description: "Retrieve a boleto by nosso_numero.",
|
|
336
|
+
inputSchema: {
|
|
337
|
+
type: "object",
|
|
338
|
+
properties: {
|
|
339
|
+
nosso_numero: { type: "string", description: "BB nosso_numero" },
|
|
340
|
+
convenio: { type: "string", description: "BB convênio" },
|
|
341
|
+
},
|
|
342
|
+
required: ["nosso_numero", "convenio"],
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: "cancel_boleto",
|
|
347
|
+
description: "Cancel (baixa) an outstanding boleto before payment.",
|
|
348
|
+
inputSchema: {
|
|
349
|
+
type: "object",
|
|
350
|
+
properties: {
|
|
351
|
+
nosso_numero: { type: "string", description: "BB nosso_numero" },
|
|
352
|
+
convenio: { type: "string", description: "BB convênio" },
|
|
353
|
+
reason: { type: "string", description: "Cancellation reason code or free text" },
|
|
354
|
+
},
|
|
355
|
+
required: ["nosso_numero", "convenio"],
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: "get_account_balance",
|
|
360
|
+
description: "Retrieve the current balance of a BB conta-corrente (checking) account.",
|
|
361
|
+
inputSchema: {
|
|
362
|
+
type: "object",
|
|
363
|
+
properties: {
|
|
364
|
+
branch: { type: "string", description: "Agência (4 digits)" },
|
|
365
|
+
account: { type: "string", description: "Conta (digits, no DV)" },
|
|
366
|
+
account_dv: { type: "string", description: "Conta DV (1 digit)" },
|
|
367
|
+
},
|
|
368
|
+
required: ["branch", "account"],
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: "get_statement",
|
|
373
|
+
description: "Retrieve account statement transactions for a BB conta-corrente over a date range. Paginated.",
|
|
374
|
+
inputSchema: {
|
|
375
|
+
type: "object",
|
|
376
|
+
properties: {
|
|
377
|
+
branch: { type: "string", description: "Agência" },
|
|
378
|
+
account: { type: "string", description: "Conta" },
|
|
379
|
+
from: { type: "string", description: "Start date ISO-8601 (YYYY-MM-DD)" },
|
|
380
|
+
to: { type: "string", description: "End date ISO-8601 (YYYY-MM-DD)" },
|
|
381
|
+
page: { type: "number", description: "Page number (1-indexed)" },
|
|
382
|
+
page_size: { type: "number", description: "Items per page (default 50)" },
|
|
383
|
+
},
|
|
384
|
+
required: ["branch", "account", "from", "to"],
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
}));
|
|
389
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
390
|
+
const { name, arguments: args } = request.params;
|
|
391
|
+
const a = (args ?? {});
|
|
392
|
+
try {
|
|
393
|
+
switch (name) {
|
|
394
|
+
case "create_pix_cob": {
|
|
395
|
+
// TODO(verify): path. BACEN Pix v2 standard is PUT /pix/v2/cob/{txid}
|
|
396
|
+
// when txid is supplied, POST /pix/v2/cob otherwise. BB hosts Pix
|
|
397
|
+
// under api-pix.bb.com.br in production with /pix/v2 prefix.
|
|
398
|
+
const txid = a.txid ? `/${encodeURIComponent(String(a.txid))}` : "";
|
|
399
|
+
const method = a.txid ? "PUT" : "POST";
|
|
400
|
+
return {
|
|
401
|
+
content: [
|
|
402
|
+
{ type: "text", text: JSON.stringify(await bbRequest(method, `/pix/v2/cob${txid}`, a), null, 2) },
|
|
403
|
+
],
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
case "get_pix_cob": {
|
|
407
|
+
const txid = encodeURIComponent(String(a.txid ?? ""));
|
|
408
|
+
return {
|
|
409
|
+
content: [
|
|
410
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/pix/v2/cob/${txid}`), null, 2) },
|
|
411
|
+
],
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
case "list_pix_cob": {
|
|
415
|
+
const params = new URLSearchParams();
|
|
416
|
+
if (a.from !== undefined)
|
|
417
|
+
params.set("inicio", String(a.from));
|
|
418
|
+
if (a.to !== undefined)
|
|
419
|
+
params.set("fim", String(a.to));
|
|
420
|
+
if (a.status !== undefined)
|
|
421
|
+
params.set("status", String(a.status));
|
|
422
|
+
if (a.page !== undefined)
|
|
423
|
+
params.set("paginacao.paginaAtual", String(a.page));
|
|
424
|
+
if (a.page_size !== undefined)
|
|
425
|
+
params.set("paginacao.itensPorPagina", String(a.page_size));
|
|
426
|
+
return {
|
|
427
|
+
content: [
|
|
428
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/pix/v2/cob?${params}`), null, 2) },
|
|
429
|
+
],
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
case "create_pix_devolucao": {
|
|
433
|
+
const e2e = encodeURIComponent(String(a.end_to_end_id ?? ""));
|
|
434
|
+
const rid = encodeURIComponent(String(a.refund_id ?? ""));
|
|
435
|
+
const body = {};
|
|
436
|
+
if (a.amount !== undefined)
|
|
437
|
+
body.valor = a.amount;
|
|
438
|
+
if (a.reason !== undefined)
|
|
439
|
+
body.descricao = a.reason;
|
|
440
|
+
// TODO(verify): path. BACEN standard PUT /pix/v2/pix/{e2e}/devolucao/{id}.
|
|
441
|
+
return {
|
|
442
|
+
content: [
|
|
443
|
+
{ type: "text", text: JSON.stringify(await bbRequest("PUT", `/pix/v2/pix/${e2e}/devolucao/${rid}`, body), null, 2) },
|
|
444
|
+
],
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
case "get_pix_devolucao": {
|
|
448
|
+
const e2e = encodeURIComponent(String(a.end_to_end_id ?? ""));
|
|
449
|
+
const rid = encodeURIComponent(String(a.refund_id ?? ""));
|
|
450
|
+
return {
|
|
451
|
+
content: [
|
|
452
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/pix/v2/pix/${e2e}/devolucao/${rid}`), null, 2) },
|
|
453
|
+
],
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
case "resolve_dict_key": {
|
|
457
|
+
const key = encodeURIComponent(String(a.key ?? ""));
|
|
458
|
+
const qs = a.payer_document ? `&payerDocument=${encodeURIComponent(String(a.payer_document))}` : "";
|
|
459
|
+
// TODO(verify): path. BACEN DICT consulta is GET /dict/v2/keys/{key}; BB
|
|
460
|
+
// may also expose it under /pix/v2/dict for client convenience.
|
|
461
|
+
return {
|
|
462
|
+
content: [
|
|
463
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/dict/v2/keys/${key}${qs ? `?${qs.slice(1)}` : ""}`), null, 2) },
|
|
464
|
+
],
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
case "register_dict_key": {
|
|
468
|
+
// TODO(verify): path. BACEN DICT registration is POST /dict/v2/keys
|
|
469
|
+
// with a chaveEntries body; BB may add a confirmação flow for email/phone.
|
|
470
|
+
return {
|
|
471
|
+
content: [
|
|
472
|
+
{ type: "text", text: JSON.stringify(await bbRequest("POST", "/dict/v2/keys", a), null, 2) },
|
|
473
|
+
],
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
case "delete_dict_key": {
|
|
477
|
+
const key = encodeURIComponent(String(a.key ?? ""));
|
|
478
|
+
// TODO(verify): path. BACEN DICT delete is DELETE /dict/v2/keys/{key}.
|
|
479
|
+
return {
|
|
480
|
+
content: [
|
|
481
|
+
{ type: "text", text: JSON.stringify(await bbRequest("DELETE", `/dict/v2/keys/${key}`), null, 2) },
|
|
482
|
+
],
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
case "register_boleto": {
|
|
486
|
+
// TODO(verify): path. BB Cobranças v2 registration is commonly
|
|
487
|
+
// POST /cobrancas/v2/boletos with numeroConvenio carried in the body.
|
|
488
|
+
return {
|
|
489
|
+
content: [
|
|
490
|
+
{ type: "text", text: JSON.stringify(await bbRequest("POST", "/cobrancas/v2/boletos", a), null, 2) },
|
|
491
|
+
],
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
case "get_boleto": {
|
|
495
|
+
const nn = encodeURIComponent(String(a.nosso_numero ?? ""));
|
|
496
|
+
const conv = encodeURIComponent(String(a.convenio ?? ""));
|
|
497
|
+
// TODO(verify): path. BB exposes detail at GET /cobrancas/v2/boletos/{nosso_numero}?numeroConvenio=...
|
|
498
|
+
return {
|
|
499
|
+
content: [
|
|
500
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/cobrancas/v2/boletos/${nn}?numeroConvenio=${conv}`), null, 2) },
|
|
501
|
+
],
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
case "cancel_boleto": {
|
|
505
|
+
const nn = encodeURIComponent(String(a.nosso_numero ?? ""));
|
|
506
|
+
const conv = encodeURIComponent(String(a.convenio ?? ""));
|
|
507
|
+
const body = { numeroConvenio: a.convenio };
|
|
508
|
+
if (a.reason !== undefined)
|
|
509
|
+
body.motivoBaixa = a.reason;
|
|
510
|
+
// TODO(verify): path. BB baixa is POST /cobrancas/v2/boletos/{nn}/baixar.
|
|
511
|
+
return {
|
|
512
|
+
content: [
|
|
513
|
+
{ type: "text", text: JSON.stringify(await bbRequest("POST", `/cobrancas/v2/boletos/${nn}/baixar?numeroConvenio=${conv}`, body), null, 2) },
|
|
514
|
+
],
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
case "get_account_balance": {
|
|
518
|
+
const branch = encodeURIComponent(String(a.branch ?? ""));
|
|
519
|
+
const account = encodeURIComponent(String(a.account ?? ""));
|
|
520
|
+
const dv = a.account_dv ? `&digitoConta=${encodeURIComponent(String(a.account_dv))}` : "";
|
|
521
|
+
// TODO(verify): path. BB Conta-Corrente saldo: GET /conta-corrente/v1/saldo?agencia=&conta=
|
|
522
|
+
return {
|
|
523
|
+
content: [
|
|
524
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/conta-corrente/v1/saldo?agencia=${branch}&conta=${account}${dv}`), null, 2) },
|
|
525
|
+
],
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
case "get_statement": {
|
|
529
|
+
const params = new URLSearchParams();
|
|
530
|
+
params.set("agencia", String(a.branch ?? ""));
|
|
531
|
+
params.set("conta", String(a.account ?? ""));
|
|
532
|
+
params.set("dataInicio", String(a.from ?? ""));
|
|
533
|
+
params.set("dataFim", String(a.to ?? ""));
|
|
534
|
+
if (a.page !== undefined)
|
|
535
|
+
params.set("pagina", String(a.page));
|
|
536
|
+
if (a.page_size !== undefined)
|
|
537
|
+
params.set("tamanhoPagina", String(a.page_size));
|
|
538
|
+
// TODO(verify): path. BB extrato: GET /conta-corrente/v1/extrato.
|
|
539
|
+
return {
|
|
540
|
+
content: [
|
|
541
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/conta-corrente/v1/extrato?${params}`), null, 2) },
|
|
542
|
+
],
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
default:
|
|
546
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
catch (err) {
|
|
550
|
+
return {
|
|
551
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
552
|
+
isError: true,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
async function main() {
|
|
557
|
+
const transport = new StdioServerTransport();
|
|
558
|
+
await server.connect(transport);
|
|
559
|
+
}
|
|
560
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@codespar/mcp-banco-do-brasil",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "MCP server for Banco do Brasil — Brazil's top public bank. Pix, Cobrança (boleto), Conta-Corrente, and Arrecadação via BB's Developer Portal APIs (OAuth2 + mTLS).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-banco-do-brasil": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
16
|
+
"zod": "^3.23.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^25.5.0",
|
|
20
|
+
"typescript": "^5.8.0"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"keywords": [
|
|
24
|
+
"mcp",
|
|
25
|
+
"banco-do-brasil",
|
|
26
|
+
"bb",
|
|
27
|
+
"banking",
|
|
28
|
+
"pix",
|
|
29
|
+
"boleto",
|
|
30
|
+
"cobranca",
|
|
31
|
+
"open-finance",
|
|
32
|
+
"brazil"
|
|
33
|
+
],
|
|
34
|
+
"mcpName": "io.github.codespar/banco-do-brasil"
|
|
35
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.codespar/banco-do-brasil",
|
|
4
|
+
"description": "MCP server for Banco do Brasil — Brazil's top public bank. Pix, Cobrança (boleto), Conta-Corrente, and Arrecadação via BB's Developer Portal APIs (OAuth2 + mTLS).",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/codespar/mcp-dev-brasil",
|
|
7
|
+
"source": "github",
|
|
8
|
+
"subfolder": "packages/banking/banco-do-brasil"
|
|
9
|
+
},
|
|
10
|
+
"version": "0.1.0-alpha.1",
|
|
11
|
+
"packages": [
|
|
12
|
+
{
|
|
13
|
+
"registryType": "npm",
|
|
14
|
+
"identifier": "@codespar/mcp-banco-do-brasil",
|
|
15
|
+
"version": "0.1.0-alpha.1",
|
|
16
|
+
"transport": {
|
|
17
|
+
"type": "stdio"
|
|
18
|
+
},
|
|
19
|
+
"environmentVariables": [
|
|
20
|
+
{
|
|
21
|
+
"name": "BB_CLIENT_ID",
|
|
22
|
+
"description": "Banco do Brasil OAuth client_id issued via developers.bb.com.br after contract onboarding.",
|
|
23
|
+
"isRequired": true,
|
|
24
|
+
"format": "string",
|
|
25
|
+
"isSecret": false
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "BB_CLIENT_SECRET",
|
|
29
|
+
"description": "Banco do Brasil OAuth client_secret.",
|
|
30
|
+
"isRequired": true,
|
|
31
|
+
"format": "string",
|
|
32
|
+
"isSecret": true
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "BB_DEVELOPER_APP_KEY",
|
|
36
|
+
"description": "BB Developer App Key (gw-dev-app-key) — appended as a query param on most BB API calls.",
|
|
37
|
+
"isRequired": true,
|
|
38
|
+
"format": "string",
|
|
39
|
+
"isSecret": true
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "BB_CERT_PATH",
|
|
43
|
+
"description": "Absolute path to the mTLS client certificate (.crt or .pem). BACEN mandates mTLS for Pix v2 in production.",
|
|
44
|
+
"isRequired": false,
|
|
45
|
+
"format": "string",
|
|
46
|
+
"isSecret": false
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "BB_KEY_PATH",
|
|
50
|
+
"description": "Absolute path to the mTLS private key (.key or .pem).",
|
|
51
|
+
"isRequired": false,
|
|
52
|
+
"format": "string",
|
|
53
|
+
"isSecret": true
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "BB_ENV",
|
|
57
|
+
"description": "Environment: 'sandbox' or 'production'. Defaults to 'sandbox'.",
|
|
58
|
+
"isRequired": false,
|
|
59
|
+
"format": "string",
|
|
60
|
+
"isSecret": false
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Server for Banco do Brasil — Brazil's top public bank.
|
|
5
|
+
*
|
|
6
|
+
* BB exposes one of the broadest bank API surfaces in the country, covering
|
|
7
|
+
* Pix, Cobranças (boleto), Conta-Corrente, Open Finance, and Arrecadação
|
|
8
|
+
* via the Developer Portal at developers.bb.com.br.
|
|
9
|
+
*
|
|
10
|
+
* Tools (13):
|
|
11
|
+
* create_pix_cob — create immediate Pix charge (cob) with QR
|
|
12
|
+
* get_pix_cob — retrieve an immediate Pix charge by txid
|
|
13
|
+
* list_pix_cob — list immediate Pix charges by date range
|
|
14
|
+
* create_pix_devolucao — refund (devolução) a received Pix
|
|
15
|
+
* get_pix_devolucao — retrieve a devolução by id
|
|
16
|
+
* resolve_dict_key — resolve a DICT key to account data
|
|
17
|
+
* register_dict_key — register a DICT key on a BB account
|
|
18
|
+
* delete_dict_key — delete a DICT key owned by the merchant
|
|
19
|
+
* register_boleto — issue a boleto via BB Cobranças
|
|
20
|
+
* get_boleto — retrieve a boleto by nosso_numero
|
|
21
|
+
* cancel_boleto — cancel (baixa) an outstanding boleto
|
|
22
|
+
* get_account_balance — Conta-Corrente balance
|
|
23
|
+
* get_statement — Conta-Corrente statement / transactions
|
|
24
|
+
*
|
|
25
|
+
* Authentication
|
|
26
|
+
* OAuth 2.0 client_credentials + mandatory mTLS in production. BACEN
|
|
27
|
+
* requires mTLS for Pix v2; BB enforces it in production across product
|
|
28
|
+
* families. Sandbox typically accepts TLS-only — cert/key envs are
|
|
29
|
+
* therefore optional but recommended.
|
|
30
|
+
*
|
|
31
|
+
* Additionally, BB requires a developer-app-key (`gw-dev-app-key`)
|
|
32
|
+
* query param on every API call for traffic accounting.
|
|
33
|
+
*
|
|
34
|
+
* Version: 0.1.0-alpha.1
|
|
35
|
+
* developers.bb.com.br is contract-gated — full OpenAPI specs are visible
|
|
36
|
+
* only to onboarded merchants. Pix paths follow BACEN Pix v2 standard.
|
|
37
|
+
* Boleto / Conta-Corrente paths are best-guesses based on BB public docs
|
|
38
|
+
* and conventions shared with peers (Itaú, Santander, Bradesco). Every
|
|
39
|
+
* unverified path is marked TODO(verify). Treat 0.1.x as alpha and pin
|
|
40
|
+
* to exact versions.
|
|
41
|
+
*
|
|
42
|
+
* Environment
|
|
43
|
+
* BB_CLIENT_ID OAuth client id
|
|
44
|
+
* BB_CLIENT_SECRET OAuth client secret
|
|
45
|
+
* BB_DEVELOPER_APP_KEY gw-dev-app-key query param
|
|
46
|
+
* BB_CERT_PATH path to mTLS client cert (production)
|
|
47
|
+
* BB_KEY_PATH path to mTLS private key (production)
|
|
48
|
+
* BB_ENV "sandbox" | "production" (default: sandbox)
|
|
49
|
+
*
|
|
50
|
+
* Docs: https://developers.bb.com.br
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
import { readFileSync } from "node:fs";
|
|
54
|
+
import { Agent as HttpsAgent } from "node:https";
|
|
55
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
56
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
57
|
+
import {
|
|
58
|
+
CallToolRequestSchema,
|
|
59
|
+
ListToolsRequestSchema,
|
|
60
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
61
|
+
|
|
62
|
+
const CLIENT_ID = process.env.BB_CLIENT_ID || "";
|
|
63
|
+
const CLIENT_SECRET = process.env.BB_CLIENT_SECRET || "";
|
|
64
|
+
const DEVELOPER_APP_KEY = process.env.BB_DEVELOPER_APP_KEY || "";
|
|
65
|
+
const CERT_PATH = process.env.BB_CERT_PATH || "";
|
|
66
|
+
const KEY_PATH = process.env.BB_KEY_PATH || "";
|
|
67
|
+
const BB_ENV = (process.env.BB_ENV || "sandbox").toLowerCase();
|
|
68
|
+
const IS_PROD = BB_ENV === "production";
|
|
69
|
+
|
|
70
|
+
// TODO(verify): production base hostnames. BB documents distinct hosts per
|
|
71
|
+
// product family (api.bb.com.br for some, api-pix.bb.com.br for Pix). The
|
|
72
|
+
// sandbox subdomain is api.sandbox.bb.com.br for most products.
|
|
73
|
+
const BASE_URL = IS_PROD ? "https://api.bb.com.br" : "https://api.sandbox.bb.com.br";
|
|
74
|
+
const OAUTH_URL = IS_PROD
|
|
75
|
+
? "https://oauth.bb.com.br/oauth/token"
|
|
76
|
+
: "https://oauth.sandbox.bb.com.br/oauth/token";
|
|
77
|
+
|
|
78
|
+
// Lazy-load the mTLS agent so `--help` / schema introspection doesn't crash
|
|
79
|
+
// when certs are missing. mTLS is required by BACEN in production but BB
|
|
80
|
+
// sandbox typically accepts TLS-only.
|
|
81
|
+
let mtlsAgent: HttpsAgent | null = null;
|
|
82
|
+
function getMtlsAgent(): HttpsAgent | null {
|
|
83
|
+
if (mtlsAgent) return mtlsAgent;
|
|
84
|
+
if (!CERT_PATH || !KEY_PATH) {
|
|
85
|
+
if (IS_PROD) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"BB mTLS certificates are required in production. Set BB_CERT_PATH and BB_KEY_PATH " +
|
|
88
|
+
"to the client cert and private-key files issued by developers.bb.com.br."
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return null; // sandbox: fall back to system TLS
|
|
92
|
+
}
|
|
93
|
+
mtlsAgent = new HttpsAgent({
|
|
94
|
+
cert: readFileSync(CERT_PATH),
|
|
95
|
+
key: readFileSync(KEY_PATH),
|
|
96
|
+
keepAlive: true,
|
|
97
|
+
});
|
|
98
|
+
return mtlsAgent;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface TokenCache {
|
|
102
|
+
accessToken: string;
|
|
103
|
+
expiresAt: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let tokenCache: TokenCache | null = null;
|
|
107
|
+
|
|
108
|
+
async function getAccessToken(): Promise<string> {
|
|
109
|
+
const now = Date.now();
|
|
110
|
+
if (tokenCache && tokenCache.expiresAt > now + 60_000) {
|
|
111
|
+
return tokenCache.accessToken;
|
|
112
|
+
}
|
|
113
|
+
const basic = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64");
|
|
114
|
+
const res = await fetchWithMtls(OAUTH_URL, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
Authorization: `Basic ${basic}`,
|
|
118
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
119
|
+
},
|
|
120
|
+
// TODO(verify): scope. BB scopes are product-specific (cob.write,
|
|
121
|
+
// cob.read, pix.write, pix.read, dict.read, etc.). The sandbox
|
|
122
|
+
// commonly accepts a blank/all scope for client_credentials.
|
|
123
|
+
body: "grant_type=client_credentials",
|
|
124
|
+
});
|
|
125
|
+
if (!res.ok) {
|
|
126
|
+
throw new Error(`BB OAuth ${res.status}: ${await res.text()}`);
|
|
127
|
+
}
|
|
128
|
+
const data = (await res.json()) as { access_token: string; expires_in: number };
|
|
129
|
+
tokenCache = {
|
|
130
|
+
accessToken: data.access_token,
|
|
131
|
+
expiresAt: now + data.expires_in * 1000,
|
|
132
|
+
};
|
|
133
|
+
return data.access_token;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Node's global fetch does not honour an https.Agent for mTLS; we drop down
|
|
137
|
+
// to node:https manually so the client cert is presented on every call.
|
|
138
|
+
async function fetchWithMtls(
|
|
139
|
+
url: string,
|
|
140
|
+
init: { method: string; headers?: Record<string, string>; body?: string }
|
|
141
|
+
): Promise<{ ok: boolean; status: number; text: () => Promise<string>; json: () => Promise<unknown> }> {
|
|
142
|
+
const agent = getMtlsAgent();
|
|
143
|
+
const { request } = await import("node:https");
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const u = new URL(url);
|
|
146
|
+
const req = request(
|
|
147
|
+
{
|
|
148
|
+
...(agent ? { agent } : {}),
|
|
149
|
+
method: init.method,
|
|
150
|
+
hostname: u.hostname,
|
|
151
|
+
port: u.port || 443,
|
|
152
|
+
path: u.pathname + u.search,
|
|
153
|
+
headers: init.headers ?? {},
|
|
154
|
+
},
|
|
155
|
+
(res) => {
|
|
156
|
+
const chunks: Buffer[] = [];
|
|
157
|
+
res.on("data", (c: Buffer) => chunks.push(c));
|
|
158
|
+
res.on("end", () => {
|
|
159
|
+
const buf = Buffer.concat(chunks);
|
|
160
|
+
const status = res.statusCode ?? 0;
|
|
161
|
+
resolve({
|
|
162
|
+
ok: status >= 200 && status < 300,
|
|
163
|
+
status,
|
|
164
|
+
text: async () => buf.toString("utf8"),
|
|
165
|
+
json: async () => JSON.parse(buf.toString("utf8")),
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
req.on("error", reject);
|
|
171
|
+
if (init.body) req.write(init.body);
|
|
172
|
+
req.end();
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function appendAppKey(path: string): string {
|
|
177
|
+
if (!DEVELOPER_APP_KEY) return path;
|
|
178
|
+
const sep = path.includes("?") ? "&" : "?";
|
|
179
|
+
return `${path}${sep}gw-dev-app-key=${encodeURIComponent(DEVELOPER_APP_KEY)}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function bbRequest(method: string, path: string, body?: unknown): Promise<unknown> {
|
|
183
|
+
const token = await getAccessToken();
|
|
184
|
+
const fullPath = appendAppKey(path);
|
|
185
|
+
const res = await fetchWithMtls(`${BASE_URL}${fullPath}`, {
|
|
186
|
+
method,
|
|
187
|
+
headers: {
|
|
188
|
+
"Content-Type": "application/json",
|
|
189
|
+
Authorization: `Bearer ${token}`,
|
|
190
|
+
},
|
|
191
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
192
|
+
});
|
|
193
|
+
if (!res.ok) {
|
|
194
|
+
throw new Error(`BB API ${res.status}: ${await res.text()}`);
|
|
195
|
+
}
|
|
196
|
+
return res.json();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const server = new Server(
|
|
200
|
+
{ name: "mcp-banco-do-brasil", version: "0.1.0-alpha.1" },
|
|
201
|
+
{ capabilities: { tools: {} } }
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
205
|
+
tools: [
|
|
206
|
+
{
|
|
207
|
+
name: "create_pix_cob",
|
|
208
|
+
description:
|
|
209
|
+
"Create an immediate Pix charge (cob) with QR code. Returns txid, EMV copy-paste payload, and location URL. BACEN Pix v2 standard.",
|
|
210
|
+
inputSchema: {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties: {
|
|
213
|
+
amount: { type: "string", description: "Amount in BRL major units, e.g. '99.90'" },
|
|
214
|
+
payer: {
|
|
215
|
+
type: "object",
|
|
216
|
+
description: "Payer identification (CPF/CNPJ + name)",
|
|
217
|
+
properties: {
|
|
218
|
+
document: { type: "string", description: "CPF or CNPJ digits only" },
|
|
219
|
+
name: { type: "string" },
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
expires_in: { type: "number", description: "QR lifetime in seconds (default 3600)" },
|
|
223
|
+
description: { type: "string", description: "Payer-visible description (solicitacaoPagador)" },
|
|
224
|
+
dict_key: { type: "string", description: "Recebedor DICT key — must be a key owned by the merchant" },
|
|
225
|
+
txid: { type: "string", description: "Optional merchant-supplied txid (26-35 alphanumeric). Omit to have BCB assign one." },
|
|
226
|
+
},
|
|
227
|
+
required: ["amount", "dict_key"],
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "get_pix_cob",
|
|
232
|
+
description: "Retrieve an immediate Pix charge by its txid.",
|
|
233
|
+
inputSchema: {
|
|
234
|
+
type: "object",
|
|
235
|
+
properties: {
|
|
236
|
+
txid: { type: "string", description: "BACEN txid (26-35 alphanumeric)" },
|
|
237
|
+
},
|
|
238
|
+
required: ["txid"],
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: "list_pix_cob",
|
|
243
|
+
description: "List immediate Pix charges (cob) by date range. Paginated per BACEN Pix v2.",
|
|
244
|
+
inputSchema: {
|
|
245
|
+
type: "object",
|
|
246
|
+
properties: {
|
|
247
|
+
from: { type: "string", description: "Start date-time ISO-8601 (inicio)" },
|
|
248
|
+
to: { type: "string", description: "End date-time ISO-8601 (fim)" },
|
|
249
|
+
status: { type: "string", description: "Status filter: ATIVA | CONCLUIDA | REMOVIDA_PELO_USUARIO_RECEBEDOR | REMOVIDA_PELO_PSP" },
|
|
250
|
+
page: { type: "number", description: "paginacao.paginaAtual" },
|
|
251
|
+
page_size: { type: "number", description: "paginacao.itensPorPagina" },
|
|
252
|
+
},
|
|
253
|
+
required: ["from", "to"],
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: "create_pix_devolucao",
|
|
258
|
+
description:
|
|
259
|
+
"Refund (devolução) a previously received Pix. Must reference the original endToEndId and a merchant-side refund id.",
|
|
260
|
+
inputSchema: {
|
|
261
|
+
type: "object",
|
|
262
|
+
properties: {
|
|
263
|
+
end_to_end_id: { type: "string", description: "Original Pix endToEndId" },
|
|
264
|
+
refund_id: { type: "string", description: "Merchant-side refund id (id da devolução, alphanumeric up to 35)" },
|
|
265
|
+
amount: { type: "string", description: "Refund amount in BRL major units. Omit for full refund." },
|
|
266
|
+
reason: { type: "string", description: "Free-text reason (descricao)" },
|
|
267
|
+
},
|
|
268
|
+
required: ["end_to_end_id", "refund_id"],
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: "get_pix_devolucao",
|
|
273
|
+
description: "Retrieve a Pix devolução by its endToEndId + refund id.",
|
|
274
|
+
inputSchema: {
|
|
275
|
+
type: "object",
|
|
276
|
+
properties: {
|
|
277
|
+
end_to_end_id: { type: "string", description: "Original Pix endToEndId" },
|
|
278
|
+
refund_id: { type: "string", description: "Merchant-side refund id" },
|
|
279
|
+
},
|
|
280
|
+
required: ["end_to_end_id", "refund_id"],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: "resolve_dict_key",
|
|
285
|
+
description:
|
|
286
|
+
"Resolve a DICT key (CPF, CNPJ, email, phone, EVP) to the owner's account data before sending a Pix. Subject to BCB rate limits per consenting payer.",
|
|
287
|
+
inputSchema: {
|
|
288
|
+
type: "object",
|
|
289
|
+
properties: {
|
|
290
|
+
key: { type: "string", description: "DICT key — CPF, CNPJ, email, phone (+55...), or EVP UUID" },
|
|
291
|
+
payer_document: { type: "string", description: "End-payer CPF/CNPJ for BCB audit logging" },
|
|
292
|
+
},
|
|
293
|
+
required: ["key"],
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: "register_dict_key",
|
|
298
|
+
description:
|
|
299
|
+
"Register a DICT key on a BB account owned by the merchant. Some key types (email/phone) require BCB confirmation flows.",
|
|
300
|
+
inputSchema: {
|
|
301
|
+
type: "object",
|
|
302
|
+
properties: {
|
|
303
|
+
key_type: { type: "string", description: "DICT key type: CPF | CNPJ | EMAIL | PHONE | EVP" },
|
|
304
|
+
key: { type: "string", description: "Key value. Omit for EVP (BCB generates UUID)." },
|
|
305
|
+
account: {
|
|
306
|
+
type: "object",
|
|
307
|
+
properties: {
|
|
308
|
+
branch: { type: "string", description: "Agência" },
|
|
309
|
+
account: { type: "string", description: "Conta" },
|
|
310
|
+
account_type: { type: "string", description: "CACC | SVGS | SLRY | TRAN" },
|
|
311
|
+
},
|
|
312
|
+
required: ["branch", "account", "account_type"],
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
required: ["key_type", "account"],
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
name: "delete_dict_key",
|
|
320
|
+
description:
|
|
321
|
+
"Delete a DICT key owned by the merchant. Irreversible — key becomes available for re-registration after BCB lockout window.",
|
|
322
|
+
inputSchema: {
|
|
323
|
+
type: "object",
|
|
324
|
+
properties: {
|
|
325
|
+
key: { type: "string", description: "DICT key value to delete" },
|
|
326
|
+
},
|
|
327
|
+
required: ["key"],
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: "register_boleto",
|
|
332
|
+
description: "Issue a boleto via BB Cobranças. Returns nosso_numero, linha digitável, barcode, and PDF URL.",
|
|
333
|
+
inputSchema: {
|
|
334
|
+
type: "object",
|
|
335
|
+
properties: {
|
|
336
|
+
amount: { type: "string", description: "Amount in BRL major units, e.g. '150.00'" },
|
|
337
|
+
due_date: { type: "string", description: "Due date ISO-8601 (YYYY-MM-DD)" },
|
|
338
|
+
convenio: { type: "string", description: "BB convênio (numeroConvenio) registered for the merchant" },
|
|
339
|
+
payer: {
|
|
340
|
+
type: "object",
|
|
341
|
+
properties: {
|
|
342
|
+
name: { type: "string" },
|
|
343
|
+
document: { type: "string", description: "CPF or CNPJ digits only" },
|
|
344
|
+
address: {
|
|
345
|
+
type: "object",
|
|
346
|
+
properties: {
|
|
347
|
+
street: { type: "string" },
|
|
348
|
+
number: { type: "string" },
|
|
349
|
+
district: { type: "string" },
|
|
350
|
+
city: { type: "string" },
|
|
351
|
+
state: { type: "string", description: "2-letter UF code" },
|
|
352
|
+
postal_code: { type: "string", description: "CEP digits only" },
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
required: ["name", "document"],
|
|
357
|
+
},
|
|
358
|
+
our_number: { type: "string", description: "Nosso_numero. Omit to have BB assign one." },
|
|
359
|
+
fine: { type: "object", description: "Multa: { percentage?, amount?, days_after_due? }" },
|
|
360
|
+
interest: { type: "object", description: "Juros: { percentage?, amount? }" },
|
|
361
|
+
discount: { type: "object", description: "Desconto: { percentage?, amount?, until? }" },
|
|
362
|
+
},
|
|
363
|
+
required: ["amount", "due_date", "convenio", "payer"],
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: "get_boleto",
|
|
368
|
+
description: "Retrieve a boleto by nosso_numero.",
|
|
369
|
+
inputSchema: {
|
|
370
|
+
type: "object",
|
|
371
|
+
properties: {
|
|
372
|
+
nosso_numero: { type: "string", description: "BB nosso_numero" },
|
|
373
|
+
convenio: { type: "string", description: "BB convênio" },
|
|
374
|
+
},
|
|
375
|
+
required: ["nosso_numero", "convenio"],
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: "cancel_boleto",
|
|
380
|
+
description: "Cancel (baixa) an outstanding boleto before payment.",
|
|
381
|
+
inputSchema: {
|
|
382
|
+
type: "object",
|
|
383
|
+
properties: {
|
|
384
|
+
nosso_numero: { type: "string", description: "BB nosso_numero" },
|
|
385
|
+
convenio: { type: "string", description: "BB convênio" },
|
|
386
|
+
reason: { type: "string", description: "Cancellation reason code or free text" },
|
|
387
|
+
},
|
|
388
|
+
required: ["nosso_numero", "convenio"],
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
name: "get_account_balance",
|
|
393
|
+
description: "Retrieve the current balance of a BB conta-corrente (checking) account.",
|
|
394
|
+
inputSchema: {
|
|
395
|
+
type: "object",
|
|
396
|
+
properties: {
|
|
397
|
+
branch: { type: "string", description: "Agência (4 digits)" },
|
|
398
|
+
account: { type: "string", description: "Conta (digits, no DV)" },
|
|
399
|
+
account_dv: { type: "string", description: "Conta DV (1 digit)" },
|
|
400
|
+
},
|
|
401
|
+
required: ["branch", "account"],
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: "get_statement",
|
|
406
|
+
description: "Retrieve account statement transactions for a BB conta-corrente over a date range. Paginated.",
|
|
407
|
+
inputSchema: {
|
|
408
|
+
type: "object",
|
|
409
|
+
properties: {
|
|
410
|
+
branch: { type: "string", description: "Agência" },
|
|
411
|
+
account: { type: "string", description: "Conta" },
|
|
412
|
+
from: { type: "string", description: "Start date ISO-8601 (YYYY-MM-DD)" },
|
|
413
|
+
to: { type: "string", description: "End date ISO-8601 (YYYY-MM-DD)" },
|
|
414
|
+
page: { type: "number", description: "Page number (1-indexed)" },
|
|
415
|
+
page_size: { type: "number", description: "Items per page (default 50)" },
|
|
416
|
+
},
|
|
417
|
+
required: ["branch", "account", "from", "to"],
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
}));
|
|
422
|
+
|
|
423
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
424
|
+
const { name, arguments: args } = request.params;
|
|
425
|
+
const a = (args ?? {}) as Record<string, unknown>;
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
switch (name) {
|
|
429
|
+
case "create_pix_cob": {
|
|
430
|
+
// TODO(verify): path. BACEN Pix v2 standard is PUT /pix/v2/cob/{txid}
|
|
431
|
+
// when txid is supplied, POST /pix/v2/cob otherwise. BB hosts Pix
|
|
432
|
+
// under api-pix.bb.com.br in production with /pix/v2 prefix.
|
|
433
|
+
const txid = a.txid ? `/${encodeURIComponent(String(a.txid))}` : "";
|
|
434
|
+
const method = a.txid ? "PUT" : "POST";
|
|
435
|
+
return {
|
|
436
|
+
content: [
|
|
437
|
+
{ type: "text", text: JSON.stringify(await bbRequest(method, `/pix/v2/cob${txid}`, a), null, 2) },
|
|
438
|
+
],
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
case "get_pix_cob": {
|
|
442
|
+
const txid = encodeURIComponent(String(a.txid ?? ""));
|
|
443
|
+
return {
|
|
444
|
+
content: [
|
|
445
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/pix/v2/cob/${txid}`), null, 2) },
|
|
446
|
+
],
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
case "list_pix_cob": {
|
|
450
|
+
const params = new URLSearchParams();
|
|
451
|
+
if (a.from !== undefined) params.set("inicio", String(a.from));
|
|
452
|
+
if (a.to !== undefined) params.set("fim", String(a.to));
|
|
453
|
+
if (a.status !== undefined) params.set("status", String(a.status));
|
|
454
|
+
if (a.page !== undefined) params.set("paginacao.paginaAtual", String(a.page));
|
|
455
|
+
if (a.page_size !== undefined) params.set("paginacao.itensPorPagina", String(a.page_size));
|
|
456
|
+
return {
|
|
457
|
+
content: [
|
|
458
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/pix/v2/cob?${params}`), null, 2) },
|
|
459
|
+
],
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
case "create_pix_devolucao": {
|
|
463
|
+
const e2e = encodeURIComponent(String(a.end_to_end_id ?? ""));
|
|
464
|
+
const rid = encodeURIComponent(String(a.refund_id ?? ""));
|
|
465
|
+
const body: Record<string, unknown> = {};
|
|
466
|
+
if (a.amount !== undefined) body.valor = a.amount;
|
|
467
|
+
if (a.reason !== undefined) body.descricao = a.reason;
|
|
468
|
+
// TODO(verify): path. BACEN standard PUT /pix/v2/pix/{e2e}/devolucao/{id}.
|
|
469
|
+
return {
|
|
470
|
+
content: [
|
|
471
|
+
{ type: "text", text: JSON.stringify(await bbRequest("PUT", `/pix/v2/pix/${e2e}/devolucao/${rid}`, body), null, 2) },
|
|
472
|
+
],
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
case "get_pix_devolucao": {
|
|
476
|
+
const e2e = encodeURIComponent(String(a.end_to_end_id ?? ""));
|
|
477
|
+
const rid = encodeURIComponent(String(a.refund_id ?? ""));
|
|
478
|
+
return {
|
|
479
|
+
content: [
|
|
480
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/pix/v2/pix/${e2e}/devolucao/${rid}`), null, 2) },
|
|
481
|
+
],
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
case "resolve_dict_key": {
|
|
485
|
+
const key = encodeURIComponent(String(a.key ?? ""));
|
|
486
|
+
const qs = a.payer_document ? `&payerDocument=${encodeURIComponent(String(a.payer_document))}` : "";
|
|
487
|
+
// TODO(verify): path. BACEN DICT consulta is GET /dict/v2/keys/{key}; BB
|
|
488
|
+
// may also expose it under /pix/v2/dict for client convenience.
|
|
489
|
+
return {
|
|
490
|
+
content: [
|
|
491
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/dict/v2/keys/${key}${qs ? `?${qs.slice(1)}` : ""}`), null, 2) },
|
|
492
|
+
],
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
case "register_dict_key": {
|
|
496
|
+
// TODO(verify): path. BACEN DICT registration is POST /dict/v2/keys
|
|
497
|
+
// with a chaveEntries body; BB may add a confirmação flow for email/phone.
|
|
498
|
+
return {
|
|
499
|
+
content: [
|
|
500
|
+
{ type: "text", text: JSON.stringify(await bbRequest("POST", "/dict/v2/keys", a), null, 2) },
|
|
501
|
+
],
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
case "delete_dict_key": {
|
|
505
|
+
const key = encodeURIComponent(String(a.key ?? ""));
|
|
506
|
+
// TODO(verify): path. BACEN DICT delete is DELETE /dict/v2/keys/{key}.
|
|
507
|
+
return {
|
|
508
|
+
content: [
|
|
509
|
+
{ type: "text", text: JSON.stringify(await bbRequest("DELETE", `/dict/v2/keys/${key}`), null, 2) },
|
|
510
|
+
],
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
case "register_boleto": {
|
|
514
|
+
// TODO(verify): path. BB Cobranças v2 registration is commonly
|
|
515
|
+
// POST /cobrancas/v2/boletos with numeroConvenio carried in the body.
|
|
516
|
+
return {
|
|
517
|
+
content: [
|
|
518
|
+
{ type: "text", text: JSON.stringify(await bbRequest("POST", "/cobrancas/v2/boletos", a), null, 2) },
|
|
519
|
+
],
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
case "get_boleto": {
|
|
523
|
+
const nn = encodeURIComponent(String(a.nosso_numero ?? ""));
|
|
524
|
+
const conv = encodeURIComponent(String(a.convenio ?? ""));
|
|
525
|
+
// TODO(verify): path. BB exposes detail at GET /cobrancas/v2/boletos/{nosso_numero}?numeroConvenio=...
|
|
526
|
+
return {
|
|
527
|
+
content: [
|
|
528
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/cobrancas/v2/boletos/${nn}?numeroConvenio=${conv}`), null, 2) },
|
|
529
|
+
],
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
case "cancel_boleto": {
|
|
533
|
+
const nn = encodeURIComponent(String(a.nosso_numero ?? ""));
|
|
534
|
+
const conv = encodeURIComponent(String(a.convenio ?? ""));
|
|
535
|
+
const body: Record<string, unknown> = { numeroConvenio: a.convenio };
|
|
536
|
+
if (a.reason !== undefined) body.motivoBaixa = a.reason;
|
|
537
|
+
// TODO(verify): path. BB baixa is POST /cobrancas/v2/boletos/{nn}/baixar.
|
|
538
|
+
return {
|
|
539
|
+
content: [
|
|
540
|
+
{ type: "text", text: JSON.stringify(await bbRequest("POST", `/cobrancas/v2/boletos/${nn}/baixar?numeroConvenio=${conv}`, body), null, 2) },
|
|
541
|
+
],
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
case "get_account_balance": {
|
|
545
|
+
const branch = encodeURIComponent(String(a.branch ?? ""));
|
|
546
|
+
const account = encodeURIComponent(String(a.account ?? ""));
|
|
547
|
+
const dv = a.account_dv ? `&digitoConta=${encodeURIComponent(String(a.account_dv))}` : "";
|
|
548
|
+
// TODO(verify): path. BB Conta-Corrente saldo: GET /conta-corrente/v1/saldo?agencia=&conta=
|
|
549
|
+
return {
|
|
550
|
+
content: [
|
|
551
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/conta-corrente/v1/saldo?agencia=${branch}&conta=${account}${dv}`), null, 2) },
|
|
552
|
+
],
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
case "get_statement": {
|
|
556
|
+
const params = new URLSearchParams();
|
|
557
|
+
params.set("agencia", String(a.branch ?? ""));
|
|
558
|
+
params.set("conta", String(a.account ?? ""));
|
|
559
|
+
params.set("dataInicio", String(a.from ?? ""));
|
|
560
|
+
params.set("dataFim", String(a.to ?? ""));
|
|
561
|
+
if (a.page !== undefined) params.set("pagina", String(a.page));
|
|
562
|
+
if (a.page_size !== undefined) params.set("tamanhoPagina", String(a.page_size));
|
|
563
|
+
// TODO(verify): path. BB extrato: GET /conta-corrente/v1/extrato.
|
|
564
|
+
return {
|
|
565
|
+
content: [
|
|
566
|
+
{ type: "text", text: JSON.stringify(await bbRequest("GET", `/conta-corrente/v1/extrato?${params}`), null, 2) },
|
|
567
|
+
],
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
default:
|
|
571
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
572
|
+
}
|
|
573
|
+
} catch (err) {
|
|
574
|
+
return {
|
|
575
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
576
|
+
isError: true,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
async function main() {
|
|
582
|
+
const transport = new StdioServerTransport();
|
|
583
|
+
await server.connect(transport);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
main().catch(console.error);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|