@codespar/mcp-matera 0.1.0-alpha.1 → 0.2.0-alpha.2

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 CHANGED
@@ -63,26 +63,36 @@ Add to `.cursor/mcp.json` or `.vscode/mcp.json`:
63
63
  }
64
64
  ```
65
65
 
66
- ## Tools
67
-
68
- | Tool | Description |
69
- |------|-------------|
70
- | `create_pix_charge_static` | Static Pix QR code (reusable, tied to a merchant Pix key) |
71
- | `create_pix_charge_dynamic` | Dynamic Pix QR code (single-use, expiring) |
72
- | `get_pix_charge` | Retrieve a Pix charge by txid |
73
- | `create_pix_payment` | Initiate an outbound Pix transfer |
74
- | `get_pix_payment` | Retrieve an outbound Pix payment by endToEndId |
75
- | `refund_pix_payment` | Refund (devolução) a Pix payment |
76
- | `list_pix_payments` | List Pix payments with filters (start, end, status) |
77
- | `resolve_pix_key` | DICT lookup resolve a Pix key to account info |
78
- | `list_dict_keys` | List DICT keys registered to merchant accounts |
79
- | `create_pix_automatico` | Register a recurring Pix Automático agreement (BCB 2025) |
66
+ ## Tools (22)
67
+
68
+ | Tool | Purpose |
69
+ |---|---|
70
+ | `create_pix_charge_static` | Create a static Pix charge (reusable QR code tied to a merchant Pix key). |
71
+ | `create_pix_charge_dynamic` | Create a dynamic Pix charge (single-use QR with expiration). |
72
+ | `get_pix_charge` | Retrieve a Pix charge (static or dynamic) by txid. |
73
+ | `list_pix_charges` | List immediate Pix charges (BCB /cob) with date and status filters. |
74
+ | `update_pix_charge` | Update an immediate Pix charge (BCB PATCH /cob/{txid}). |
75
+ | `create_pix_charge_due` | Create a due-dated Pix charge (BCB /cobv Pix com Vencimento). |
76
+ | `get_pix_charge_due` | Retrieve a due-dated Pix charge (BCB GET /cobv/{txid}). |
77
+ | `create_pix_payment` | Initiate an outbound Pix transfer (ordem de pagamento). |
78
+ | `get_pix_payment` | Retrieve an outbound Pix payment by endToEndId. |
79
+ | `refund_pix_payment` | Refund (devolução) a Pix payment. |
80
+ | `list_pix_payments` | List outbound Pix payments with optional filters. |
81
+ | `list_pix_received` | List inbound Pix (Pix recebidos) credited to merchant accounts in a date range. |
82
+ | `resolve_pix_key` | Resolve a Pix DICT key to the account holder's identity and ISPB/branch/account. |
83
+ | `list_dict_keys` | List DICT keys registered to the merchant's accounts on Matera. |
84
+ | `register_dict_key` | Register (claim) a DICT key for a merchant account on Matera. |
85
+ | `delete_dict_key` | Delete a DICT key the merchant owns. |
86
+ | `create_pix_automatico` | Register a Pix Automático recurrence (BCB 2025 recurring Pix product, /rec). |
87
+ | `get_pix_automatico` | Retrieve a Pix Automático recurrence by idRec. |
88
+ | `cancel_pix_automatico` | Cancel an active Pix Automático recurrence. |
89
+ | `get_account_balance` | Get the current balance of a Matera-managed account. |
90
+ | `get_account_statement` | Get the statement (extrato) of a Matera-managed account in a date range. |
91
+ | `internal_transfer` | Book a transfer between two accounts both held on Matera (TED-interno / transferência interna). |
80
92
 
81
93
  ## Authentication
82
94
 
83
- Matera uses **OAuth 2.0 client_credentials**. The server calls `POST /auth/token` with HTTP Basic auth and caches the bearer token in memory until a minute before expiry.
84
-
85
- Matera also supports `secret-key` + `data-signature` headers for signed server-to-server calls. That path is not implemented in v0.1; OAuth2 is sufficient for every tool above.
95
+ Matera's public doc index states that **server integration uses `secret-key` + `data-signature` headers**, while **OAuth2 is scoped to mobile / web-UI integrations** ([doc-api.matera.com](https://doc-api.matera.com/)). The v0.1-alpha scaffold currently implements the OAuth2 client-credentials path against `POST /auth/token`. That pairing is almost certainly wrong for server-to-server use and will be replaced with the signed-request scheme once we can validate against a live sandbox — see the "Status" section below.
86
96
 
87
97
  ## Environment Variables
88
98
 
@@ -94,7 +104,23 @@ Matera also supports `secret-key` + `data-signature` headers for signed server-t
94
104
 
95
105
  ## Status
96
106
 
97
- v0.1 — scaffold. Endpoint paths follow Matera's published doc structure but were not validated against a live sandbox. Expect small adjustments to paths and request shapes once the team has credentials. Schemas are deliberately lightweight — only required fields are marked `required`; nested objects accept any shape so agents can pass through fields we haven't modeled yet.
107
+ **v0.1.0-alpha.1tool surface is stable, wire paths are NOT yet verified.**
108
+
109
+ On 2026-04-24 we attempted to validate every endpoint path against `doc-api.matera.com`. The doc site is reachable in a browser but could not be fetched from our automation environment (DNS / network-gated; no login wall observed). Public search snippets and third-party references confirm the product surface (Pix charges, Pix payments, DICT, Pix Automático) and the authentication model (server = `secret-key` + `data-signature`; OAuth2 = end-user only) but do **not** surface the exact URL paths.
110
+
111
+ Until we can run the server against a live Matera sandbox, the following remain **assumed, not verified**:
112
+
113
+ | Area | Value in code | Why it's suspect |
114
+ |------|---------------|------------------|
115
+ | Token endpoint | `POST /auth/token` (Basic auth, `grant_type=client_credentials`) | Likely wrong for server integration — see Authentication |
116
+ | Pix charges | `/pix/charges/static`, `/pix/charges/dynamic`, `/pix/charges/{txid}` | Matera likely follows BCB's `/cob` (immediate) / `/cobv` (dated) naming |
117
+ | Pix payments | `/pix/payments`, `/pix/payments/{e2eid}`, `/pix/payments/{e2eid}/refund` | BCB spec names refunds `/pix/{e2eid}/devolucao/{id}` |
118
+ | DICT lookup | `GET /pix/dict/{key}`, `GET /pix/dict/keys` | BCB DICT is RSFN-gated; Matera exposes its own wrapper. Exact path unknown. |
119
+ | Pix Automático | `POST /pix/automatico` | Almost certainly a placeholder. BCB 2025 spec uses `POST /rec` (recurrence) + `/cobr/{txid}` (recurring charge). |
120
+
121
+ The 10 tool **names** and **input schemas** above are the public contract and will remain stable. Only the internal HTTP calls in `src/index.ts` will change when we verify against the sandbox. Using this server against a live Matera tenant today will produce 404s on most calls.
122
+
123
+ Track the verification work in the repo issues; PR welcome from anyone with a Matera sandbox.
98
124
 
99
125
  ## Roadmap
100
126
 
package/dist/index.js CHANGED
@@ -8,24 +8,80 @@
8
8
  * DICT, registering recurring Pix Automático agreements) — distinct from PSPs
9
9
  * like Zoop/Asaas/Mercado Pago which serve merchants accepting Pix.
10
10
  *
11
- * Tools (10) — Pix focus for v0.1:
12
- * create_pix_charge_static — static QR code (merchant Pix key, reusable)
13
- * create_pix_charge_dynamic — dynamic QR code (single-use, expiring)
14
- * get_pix_charge — fetch a charge by txid
15
- * create_pix_payment — initiate an outbound Pix transfer
16
- * get_pix_payment — fetch a payment by endToEndId
17
- * refund_pix_payment — refund a Pix payment (MED / devolução)
18
- * list_pix_payments — list payments with start/end/status filters
19
- * resolve_pix_key — DICT lookup (CPF/CNPJ/email/phone/random → account)
20
- * list_dict_keys — list DICT keys registered to merchant accounts
21
- * create_pix_automatico — register a recurring Pix agreement (BCB 2025)
11
+ * Tools (21) — Pix + DICT + accounts for v0.2:
22
12
  *
23
- * Authentication
13
+ * Immediate Pix charges (BCB /cob)
14
+ * create_pix_charge_static — static QR code (merchant Pix key, reusable)
15
+ * create_pix_charge_dynamic — dynamic QR code (single-use, expiring)
16
+ * get_pix_charge — fetch a charge by txid
17
+ * list_pix_charges — list /cob charges with date+status filters
18
+ * update_pix_charge — PATCH a /cob (revise amount/expiration/status)
19
+ *
20
+ * Due-dated Pix charges (BCB /cobv — boleto-style with vencimento)
21
+ * create_pix_charge_due — cobv create
22
+ * get_pix_charge_due — cobv get by txid
23
+ *
24
+ * Outbound Pix payments (ordem de pagamento)
25
+ * create_pix_payment — initiate an outbound Pix transfer
26
+ * get_pix_payment — fetch a payment by endToEndId
27
+ * refund_pix_payment — refund a Pix payment (MED / devolução)
28
+ * list_pix_payments — list outbound payments with filters
29
+ *
30
+ * Inbound Pix (recebidos)
31
+ * list_pix_received — list received Pix in a window
32
+ *
33
+ * DICT (Pix key directory)
34
+ * resolve_pix_key — DICT lookup (CPF/CNPJ/email/phone/random → account)
35
+ * list_dict_keys — list DICT keys registered to merchant accounts
36
+ * register_dict_key — claim/register a DICT key for a merchant account
37
+ * delete_dict_key — remove a DICT key the merchant owns
38
+ *
39
+ * Pix Automático (BCB 2025 recurring Pix)
40
+ * create_pix_automatico — register a recurring Pix agreement
41
+ * get_pix_automatico — fetch a recurrence by idRec
42
+ * cancel_pix_automatico — cancel an active recurrence
43
+ *
44
+ * Accounts (Matera-managed core-banking accounts)
45
+ * get_account_balance — current balance for a Matera account
46
+ * get_account_statement — extrato / statement for a date range
47
+ * internal_transfer — book transfer between two Matera-managed accounts
48
+ *
49
+ * -------------------------------------------------------------------------
50
+ * ALPHA STATUS — endpoint paths below are NOT verified against a live sandbox.
51
+ *
52
+ * On 2026-04-24 we attempted to validate every path against doc-api.matera.com.
53
+ * The doc site was not reachable from our automation environment (DNS-gated;
54
+ * no login wall observed). Public search snippets and third-party references
55
+ * confirm Matera's product surface and auth model but do not surface exact URL
56
+ * paths. Where Matera-specific paths are unknown, we follow BCB Pix API v2
57
+ * conventions (`/cob`, `/cobv`, `/pix`, `/rec`) which Matera is required to
58
+ * mirror as a participating PSP. Known-suspect items are flagged inline with
59
+ * `TODO(verify)` comments.
60
+ *
61
+ * High-confidence corrections pending sandbox access:
62
+ * - Auth: Matera's server integration uses `secret-key` + `data-signature`
63
+ * headers, not OAuth2. OAuth2 is documented only for mobile / web-UI
64
+ * integrations. The code below still uses the OAuth2 client_credentials
65
+ * flow as a placeholder and will fail against the real server path.
66
+ * - Pix Automático: BCB's 2025 spec uses POST /rec (recurrence) and /cobr
67
+ * (recurring charge). `/pix/automatico` is a placeholder; the true Matera
68
+ * path is unknown.
69
+ * - DICT: Real DICT (RSFN-gated) sits at BCB; Matera wraps it. The wrapper
70
+ * path `/pix/dict/*` is a guess.
71
+ * - Accounts (balance/statement/internal-transfer): no public path.
72
+ * `/accounts/*` is a reasoned default.
73
+ *
74
+ * The 21 tool names + input schemas are the stable public contract. Only the
75
+ * internal `materaRequest(method, path, body)` calls will change when paths
76
+ * are verified.
77
+ * -------------------------------------------------------------------------
78
+ *
79
+ * Authentication (placeholder — see note above)
24
80
  * OAuth 2.0 Client Credentials. POST /auth/token with Basic auth
25
81
  * (client_id:client_secret) + grant_type=client_credentials. Bearer token
26
82
  * cached in memory until a minute before expiry.
27
- * Matera also supports secret-key + data-signature headers for signed
28
- * server-to-server calls; not used in v0.1.
83
+ * Matera's production server auth is secret-key + data-signature NOT
84
+ * implemented in this alpha.
29
85
  *
30
86
  * Environment
31
87
  * MATERA_CLIENT_ID OAuth2 client_id
@@ -50,6 +106,9 @@ async function getAccessToken() {
50
106
  return tokenCache.accessToken;
51
107
  }
52
108
  const basic = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64");
109
+ // TODO(verify): Matera's real token path is unconfirmed. Candidates include
110
+ // `/auth/token`, `/oauth/token`, `/auth/v1/token`. Leaving the original
111
+ // placeholder until the sandbox confirms it.
53
112
  const res = await fetch(`${BASE_URL}/auth/token`, {
54
113
  method: "POST",
55
114
  headers: {
@@ -85,7 +144,7 @@ async function materaRequest(method, path, body) {
85
144
  const text = await res.text();
86
145
  return text ? JSON.parse(text) : {};
87
146
  }
88
- const server = new Server({ name: "mcp-matera", version: "0.1.0" }, { capabilities: { tools: {} } });
147
+ const server = new Server({ name: "mcp-matera", version: "0.2.0" }, { capabilities: { tools: {} } });
89
148
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
90
149
  tools: [
91
150
  {
@@ -139,6 +198,85 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
139
198
  required: ["txid"],
140
199
  },
141
200
  },
201
+ {
202
+ name: "list_pix_charges",
203
+ description: "List immediate Pix charges (BCB /cob) with date and status filters. Use for reconciling QR-driven receipts in a time window.",
204
+ inputSchema: {
205
+ type: "object",
206
+ properties: {
207
+ start: { type: "string", description: "ISO-8601 start timestamp (inclusive). BCB calls this `inicio`." },
208
+ end: { type: "string", description: "ISO-8601 end timestamp (exclusive). BCB calls this `fim`." },
209
+ status: { type: "string", description: "Filter by status (ATIVA, CONCLUIDA, REMOVIDA_PELO_USUARIO_RECEBEDOR, REMOVIDA_PELO_PSP)" },
210
+ cpf: { type: "string", description: "Filter charges where the payer CPF equals this value" },
211
+ cnpj: { type: "string", description: "Filter charges where the payer CNPJ equals this value" },
212
+ page: { type: "number", description: "Page number (starts at 0 per BCB convention)" },
213
+ limit: { type: "number", description: "Page size (BCB max 1000)" },
214
+ },
215
+ required: ["start", "end"],
216
+ },
217
+ },
218
+ {
219
+ name: "update_pix_charge",
220
+ description: "Update an immediate Pix charge (BCB PATCH /cob/{txid}). Use to revise amount/expiration before payment, or to mark a charge REMOVIDA_PELO_USUARIO_RECEBEDOR.",
221
+ inputSchema: {
222
+ type: "object",
223
+ properties: {
224
+ txid: { type: "string", description: "txid of the existing charge" },
225
+ amount: { type: "number", description: "New amount in BRL (only if charge still ATIVA)" },
226
+ expiration: { type: "number", description: "New expiration in seconds from creation" },
227
+ description: { type: "string", description: "New description shown to payer" },
228
+ status: { type: "string", description: "Set to REMOVIDA_PELO_USUARIO_RECEBEDOR to cancel an unpaid charge" },
229
+ },
230
+ required: ["txid"],
231
+ },
232
+ },
233
+ {
234
+ name: "create_pix_charge_due",
235
+ description: "Create a due-dated Pix charge (BCB /cobv — Pix com Vencimento). Boleto-style charge with due date, late fee (multa), interest (juros), and discount fields. Returns txid + QR payload.",
236
+ inputSchema: {
237
+ type: "object",
238
+ properties: {
239
+ pix_key: { type: "string", description: "Merchant Pix key the charge settles to" },
240
+ amount: { type: "number", description: "Original amount in BRL (valor.original)" },
241
+ description: { type: "string", description: "Description shown to payer (solicitacaoPagador)" },
242
+ due_date: { type: "string", description: "ISO-8601 date when the charge becomes due (data.dataDeVencimento)" },
243
+ validity_days_after_due: { type: "number", description: "How many calendar days after due date the QR remains payable (validadeAposVencimento). Default 30." },
244
+ debtor: {
245
+ type: "object",
246
+ description: "Payer (devedor). BCB requires CPF or CNPJ + name for cobv.",
247
+ properties: {
248
+ cpf: { type: "string" },
249
+ cnpj: { type: "string" },
250
+ name: { type: "string" },
251
+ address: { type: "string", description: "Logradouro" },
252
+ city: { type: "string", description: "Cidade" },
253
+ state: { type: "string", description: "UF (2-letter)" },
254
+ zip: { type: "string", description: "CEP" },
255
+ },
256
+ required: ["name"],
257
+ },
258
+ fine: { type: "number", description: "Late fee, BRL or % depending on `fine_mode` (multa.valor or multa.modalidade)" },
259
+ fine_mode: { type: "number", enum: [1, 2], description: "1=fixed amount, 2=percent (BCB modalidade)" },
260
+ interest: { type: "number", description: "Interest after due date" },
261
+ interest_mode: { type: "number", description: "BCB juros.modalidade (1..7). 2=percent per day, 5=percent per month, etc." },
262
+ discount: { type: "number", description: "Discount value if paid before due date" },
263
+ discount_mode: { type: "number", description: "BCB desconto.modalidade (1..6)" },
264
+ txid: { type: "string", description: "Optional merchant-side txid (26-35 alphanumerics)" },
265
+ },
266
+ required: ["pix_key", "amount", "description", "due_date", "debtor"],
267
+ },
268
+ },
269
+ {
270
+ name: "get_pix_charge_due",
271
+ description: "Retrieve a due-dated Pix charge (BCB GET /cobv/{txid}).",
272
+ inputSchema: {
273
+ type: "object",
274
+ properties: {
275
+ txid: { type: "string", description: "txid of the cobv charge" },
276
+ },
277
+ required: ["txid"],
278
+ },
279
+ },
142
280
  {
143
281
  name: "create_pix_payment",
144
282
  description: "Initiate an outbound Pix transfer (ordem de pagamento). Moves money from a debtor account held on Matera to any Pix key in BR. Returns endToEndId once the BCB SPI confirms.",
@@ -202,6 +340,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
202
340
  },
203
341
  },
204
342
  },
343
+ {
344
+ name: "list_pix_received",
345
+ description: "List inbound Pix (Pix recebidos) credited to merchant accounts in a date range. Mirrors BCB GET /pix. Filter by txid to find the receipt that closed a specific cob.",
346
+ inputSchema: {
347
+ type: "object",
348
+ properties: {
349
+ start: { type: "string", description: "ISO-8601 start timestamp (inclusive)" },
350
+ end: { type: "string", description: "ISO-8601 end timestamp (exclusive)" },
351
+ txid: { type: "string", description: "Only return Pix receipts that closed this txid" },
352
+ cpf: { type: "string", description: "Filter by payer CPF" },
353
+ cnpj: { type: "string", description: "Filter by payer CNPJ" },
354
+ page: { type: "number", description: "Page number (BCB starts at 0)" },
355
+ limit: { type: "number", description: "Page size" },
356
+ },
357
+ required: ["start", "end"],
358
+ },
359
+ },
205
360
  {
206
361
  name: "resolve_pix_key",
207
362
  description: "Resolve a Pix DICT key to the account holder's identity and ISPB/branch/account. Use before sending large transfers to verify the counterparty. Note: DICT queries are rate-limited and logged by BCB.",
@@ -223,9 +378,44 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
223
378
  },
224
379
  },
225
380
  },
381
+ {
382
+ name: "register_dict_key",
383
+ description: "Register (claim) a DICT key for a merchant account on Matera. For email/phone keys this triggers BCB's confirmation flow; for CPF/CNPJ/random it claims immediately. Subject to BCB DICT rate limits and ownership rules (max 5 keys per CPF, 20 per CNPJ).",
384
+ inputSchema: {
385
+ type: "object",
386
+ properties: {
387
+ key_type: { type: "string", enum: ["CPF", "CNPJ", "EMAIL", "PHONE", "RANDOM"], description: "Type of key to register" },
388
+ key_value: { type: "string", description: "Value (omit for RANDOM — Matera will generate the UUID)" },
389
+ account: {
390
+ type: "object",
391
+ description: "Matera-managed account that will own the key",
392
+ properties: {
393
+ branch: { type: "string", description: "Branch (agência)" },
394
+ account: { type: "string", description: "Account number" },
395
+ account_type: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"], description: "ISO 20022 account type" },
396
+ ispb: { type: "string", description: "ISPB of the holding institution (Matera-issued)" },
397
+ },
398
+ required: ["branch", "account", "account_type"],
399
+ },
400
+ },
401
+ required: ["key_type", "account"],
402
+ },
403
+ },
404
+ {
405
+ name: "delete_dict_key",
406
+ description: "Delete a DICT key the merchant owns. After delete, the key is held in BCB's quarentena window before another holder can claim it.",
407
+ inputSchema: {
408
+ type: "object",
409
+ properties: {
410
+ key: { type: "string", description: "Pix key value to delete" },
411
+ reason: { type: "string", description: "Optional reason (BCB MotivoExclusao). Default: USER_REQUESTED." },
412
+ },
413
+ required: ["key"],
414
+ },
415
+ },
226
416
  {
227
417
  name: "create_pix_automatico",
228
- description: "Register a Pix Automático agreement (BCB 2025 recurring Pix product). The payer authorizes the merchant to pull recurring amounts on a schedule. Matera is one of the few providers live with this.",
418
+ description: "Register a Pix Automático recurrence (BCB 2025 recurring Pix product, /rec). The payer authorizes the merchant to pull recurring amounts on a schedule. Matera is one of the few providers live with this.",
229
419
  inputSchema: {
230
420
  type: "object",
231
421
  properties: {
@@ -250,6 +440,90 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
250
440
  required: ["payer", "merchant_pix_key", "frequency", "amount", "first_charge_date", "description"],
251
441
  },
252
442
  },
443
+ {
444
+ name: "get_pix_automatico",
445
+ description: "Retrieve a Pix Automático recurrence by idRec. Returns current status (CRIADA, APROVADA, REJEITADA, CANCELADA), schedule, and last charge attempt.",
446
+ inputSchema: {
447
+ type: "object",
448
+ properties: {
449
+ id_rec: { type: "string", description: "Recurrence id (idRec) returned by create_pix_automatico" },
450
+ },
451
+ required: ["id_rec"],
452
+ },
453
+ },
454
+ {
455
+ name: "cancel_pix_automatico",
456
+ description: "Cancel an active Pix Automático recurrence. Future charges stop after BCB confirms the cancellation. Past charges are unaffected.",
457
+ inputSchema: {
458
+ type: "object",
459
+ properties: {
460
+ id_rec: { type: "string", description: "Recurrence id (idRec) to cancel" },
461
+ reason: { type: "string", description: "Optional reason recorded on the cancellation" },
462
+ },
463
+ required: ["id_rec"],
464
+ },
465
+ },
466
+ {
467
+ name: "get_account_balance",
468
+ description: "Get the current balance of a Matera-managed account. Returns available, blocked, and total balance in BRL.",
469
+ inputSchema: {
470
+ type: "object",
471
+ properties: {
472
+ branch: { type: "string", description: "Branch (agência)" },
473
+ account: { type: "string", description: "Account number" },
474
+ account_type: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"], description: "ISO 20022 account type. Defaults to CACC." },
475
+ },
476
+ required: ["branch", "account"],
477
+ },
478
+ },
479
+ {
480
+ name: "get_account_statement",
481
+ description: "Get the statement (extrato) of a Matera-managed account in a date range. Returns one entry per credit/debit with counterparty, endToEndId (when applicable), and running balance.",
482
+ inputSchema: {
483
+ type: "object",
484
+ properties: {
485
+ branch: { type: "string", description: "Branch (agência)" },
486
+ account: { type: "string", description: "Account number" },
487
+ account_type: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"], description: "ISO 20022 account type. Defaults to CACC." },
488
+ start: { type: "string", description: "ISO-8601 start date (inclusive)" },
489
+ end: { type: "string", description: "ISO-8601 end date (exclusive)" },
490
+ page: { type: "number", description: "Page number" },
491
+ limit: { type: "number", description: "Page size" },
492
+ },
493
+ required: ["branch", "account", "start", "end"],
494
+ },
495
+ },
496
+ {
497
+ name: "internal_transfer",
498
+ description: "Book a transfer between two accounts both held on Matera (TED-interno / transferência interna). Settles instantly without touching SPI/Pix rails — no endToEndId, no DICT lookup. Use for moving funds across a fintech's own customer accounts.",
499
+ inputSchema: {
500
+ type: "object",
501
+ properties: {
502
+ debtor_account: {
503
+ type: "object",
504
+ properties: {
505
+ branch: { type: "string" },
506
+ account: { type: "string" },
507
+ account_type: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"] },
508
+ },
509
+ required: ["branch", "account", "account_type"],
510
+ },
511
+ creditor_account: {
512
+ type: "object",
513
+ properties: {
514
+ branch: { type: "string" },
515
+ account: { type: "string" },
516
+ account_type: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"] },
517
+ },
518
+ required: ["branch", "account", "account_type"],
519
+ },
520
+ amount: { type: "number", description: "Amount in BRL (decimal)" },
521
+ description: { type: "string", description: "Free-text description recorded on both ledger entries" },
522
+ idempotency_key: { type: "string", description: "Merchant-side unique id to prevent double-debit on retry" },
523
+ },
524
+ required: ["debtor_account", "creditor_account", "amount"],
525
+ },
526
+ },
253
527
  ],
254
528
  }));
255
529
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -257,6 +531,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
257
531
  const a = (args ?? {});
258
532
  try {
259
533
  switch (name) {
534
+ // TODO(verify): BCB canonical charge paths are `/cob` (immediate) and
535
+ // `/cobv` (dated). Matera may expose them at `/pix/charges/*` per the
536
+ // original guess, but this has NOT been confirmed against the sandbox.
260
537
  case "create_pix_charge_static":
261
538
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/charges/static", a), null, 2) }] };
262
539
  case "create_pix_charge_dynamic":
@@ -265,6 +542,53 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
265
542
  const txid = encodeURIComponent(String(a.txid ?? ""));
266
543
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/charges/${txid}`), null, 2) }] };
267
544
  }
545
+ case "list_pix_charges": {
546
+ // TODO(verify): BCB Pix v2 lists /cob via `?inicio=&fim=`. Path is a
547
+ // wrapped Matera guess.
548
+ const params = new URLSearchParams();
549
+ if (a.start)
550
+ params.set("inicio", String(a.start));
551
+ if (a.end)
552
+ params.set("fim", String(a.end));
553
+ if (a.status)
554
+ params.set("status", String(a.status));
555
+ if (a.cpf)
556
+ params.set("cpf", String(a.cpf));
557
+ if (a.cnpj)
558
+ params.set("cnpj", String(a.cnpj));
559
+ if (a.page !== undefined)
560
+ params.set("paginacao.paginaAtual", String(a.page));
561
+ if (a.limit !== undefined)
562
+ params.set("paginacao.itensPorPagina", String(a.limit));
563
+ const qs = params.toString();
564
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/charges${qs ? `?${qs}` : ""}`), null, 2) }] };
565
+ }
566
+ case "update_pix_charge": {
567
+ // TODO(verify): BCB spec uses PATCH /cob/{txid}. Wrapper path guessed.
568
+ const txid = encodeURIComponent(String(a.txid ?? ""));
569
+ const body = {};
570
+ if (a.amount !== undefined)
571
+ body.amount = a.amount;
572
+ if (a.expiration !== undefined)
573
+ body.expiration = a.expiration;
574
+ if (a.description !== undefined)
575
+ body.description = a.description;
576
+ if (a.status !== undefined)
577
+ body.status = a.status;
578
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("PATCH", `/pix/charges/${txid}`, body), null, 2) }] };
579
+ }
580
+ case "create_pix_charge_due": {
581
+ // TODO(verify): BCB canonical path is PUT /cobv/{txid}. Matera wrapper
582
+ // path unknown — using `/pix/charges/due` for symmetry with /charges.
583
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/charges/due", a), null, 2) }] };
584
+ }
585
+ case "get_pix_charge_due": {
586
+ const txid = encodeURIComponent(String(a.txid ?? ""));
587
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/charges/due/${txid}`), null, 2) }] };
588
+ }
589
+ // TODO(verify): BCB spec names outbound Pix refunds
590
+ // `PUT /pix/{e2eid}/devolucao/{id}`. The `POST /pix/payments/{e2eid}/refund`
591
+ // path below is a plausible Matera wrapper but NOT confirmed.
268
592
  case "create_pix_payment":
269
593
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/payments", a), null, 2) }] };
270
594
  case "get_pix_payment": {
@@ -291,6 +615,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
291
615
  const qs = params.toString();
292
616
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/payments${qs ? `?${qs}` : ""}`), null, 2) }] };
293
617
  }
618
+ case "list_pix_received": {
619
+ // TODO(verify): BCB canonical is GET /pix?inicio=&fim=. Wrapper path
620
+ // assumed at `/pix/received`.
621
+ const params = new URLSearchParams();
622
+ if (a.start)
623
+ params.set("inicio", String(a.start));
624
+ if (a.end)
625
+ params.set("fim", String(a.end));
626
+ if (a.txid)
627
+ params.set("txid", String(a.txid));
628
+ if (a.cpf)
629
+ params.set("cpf", String(a.cpf));
630
+ if (a.cnpj)
631
+ params.set("cnpj", String(a.cnpj));
632
+ if (a.page !== undefined)
633
+ params.set("paginacao.paginaAtual", String(a.page));
634
+ if (a.limit !== undefined)
635
+ params.set("paginacao.itensPorPagina", String(a.limit));
636
+ const qs = params.toString();
637
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/received${qs ? `?${qs}` : ""}`), null, 2) }] };
638
+ }
639
+ // TODO(verify): BCB DICT is RSFN-gated and typically lives under
640
+ // `/api/v2/entries/*` on the Central Bank side. Matera wraps it in its
641
+ // own API — path `/pix/dict/*` below is a guess.
294
642
  case "resolve_pix_key": {
295
643
  const key = encodeURIComponent(String(a.key ?? ""));
296
644
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/dict/${key}`), null, 2) }] };
@@ -302,8 +650,70 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
302
650
  const qs = params.toString();
303
651
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/dict/keys${qs ? `?${qs}` : ""}`), null, 2) }] };
304
652
  }
653
+ case "register_dict_key": {
654
+ // TODO(verify): BCB DICT register is POST /api/v2/entries with an XML
655
+ // signed payload. Matera wraps it; path `/pix/dict/keys` is a guess.
656
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/dict/keys", a), null, 2) }] };
657
+ }
658
+ case "delete_dict_key": {
659
+ // TODO(verify): BCB uses DELETE /api/v2/entries/{key}. Wrapper path
660
+ // assumed.
661
+ const key = encodeURIComponent(String(a.key ?? ""));
662
+ const params = new URLSearchParams();
663
+ if (a.reason)
664
+ params.set("reason", String(a.reason));
665
+ const qs = params.toString();
666
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("DELETE", `/pix/dict/${key}${qs ? `?${qs}` : ""}`), null, 2) }] };
667
+ }
668
+ // TODO(verify): `/pix/automatico` is almost certainly wrong. BCB's
669
+ // 2025 Pix Automático spec uses `POST /rec` (recorrência — payer
670
+ // authorization) and `/cobr/{txid}` (cobrança recorrente — each
671
+ // recurring charge). Matera likely mirrors this. The body shape here
672
+ // also needs to change (idRec, expiracaoSolicitacao, vinculo, etc.)
305
673
  case "create_pix_automatico":
306
674
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/automatico", a), null, 2) }] };
675
+ case "get_pix_automatico": {
676
+ const id = encodeURIComponent(String(a.id_rec ?? ""));
677
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/automatico/${id}`), null, 2) }] };
678
+ }
679
+ case "cancel_pix_automatico": {
680
+ const id = encodeURIComponent(String(a.id_rec ?? ""));
681
+ const body = { status: "CANCELADA" };
682
+ if (a.reason)
683
+ body.reason = a.reason;
684
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("PATCH", `/pix/automatico/${id}`, body), null, 2) }] };
685
+ }
686
+ // TODO(verify): No public Matera path for accounts. `/accounts/*` is a
687
+ // reasoned default. Real path may live under `/baas/accounts/*` or
688
+ // `/core/accounts/*`.
689
+ case "get_account_balance": {
690
+ const params = new URLSearchParams();
691
+ params.set("branch", String(a.branch ?? ""));
692
+ params.set("account", String(a.account ?? ""));
693
+ if (a.account_type)
694
+ params.set("account_type", String(a.account_type));
695
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/accounts/balance?${params.toString()}`), null, 2) }] };
696
+ }
697
+ case "get_account_statement": {
698
+ const params = new URLSearchParams();
699
+ params.set("branch", String(a.branch ?? ""));
700
+ params.set("account", String(a.account ?? ""));
701
+ if (a.account_type)
702
+ params.set("account_type", String(a.account_type));
703
+ if (a.start)
704
+ params.set("start", String(a.start));
705
+ if (a.end)
706
+ params.set("end", String(a.end));
707
+ if (a.page !== undefined)
708
+ params.set("page", String(a.page));
709
+ if (a.limit !== undefined)
710
+ params.set("limit", String(a.limit));
711
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/accounts/statement?${params.toString()}`), null, 2) }] };
712
+ }
713
+ case "internal_transfer": {
714
+ // TODO(verify): book transfer endpoint unknown. Using `/accounts/transfers`.
715
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/accounts/transfers", a), null, 2) }] };
716
+ }
307
717
  default:
308
718
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
309
719
  }
@@ -330,7 +740,7 @@ async function main() {
330
740
  const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
331
741
  t.onclose = () => { if (t.sessionId)
332
742
  transports.delete(t.sessionId); };
333
- const s = new Server({ name: "mcp-matera", version: "0.1.0" }, { capabilities: { tools: {} } });
743
+ const s = new Server({ name: "mcp-matera", version: "0.2.0" }, { capabilities: { tools: {} } });
334
744
  server._requestHandlers.forEach((v, k) => s._requestHandlers.set(k, v));
335
745
  server._notificationHandlers?.forEach((v, k) => s._notificationHandlers.set(k, v));
336
746
  await s.connect(t);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@codespar/mcp-matera",
3
- "version": "0.1.0-alpha.1",
4
- "description": "MCP server for Matera Brazilian core-banking infrastructure (BaaS) for fintechs building on top of Pix, DICT, and Pix Automático",
3
+ "version": "0.2.0-alpha.2",
4
+ "description": "MCP server for Matera \u2014 Brazilian core-banking infrastructure (BaaS) for fintechs building on top of Pix, DICT, and Pix Autom\u00e1tico",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "bin": {
@@ -15,6 +15,7 @@
15
15
  "@modelcontextprotocol/sdk": "^1.0.0"
16
16
  },
17
17
  "devDependencies": {
18
+ "@types/express": "^5.0.6",
18
19
  "@types/node": "^25.5.0",
19
20
  "typescript": "^5.8.0"
20
21
  },
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "source": "github",
8
8
  "subfolder": "packages/banking/matera"
9
9
  },
10
- "version": "0.1.0",
10
+ "version": "0.2.0-alpha.2",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "@codespar/mcp-matera",
15
- "version": "0.1.0",
15
+ "version": "0.2.0-alpha.2",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },
package/src/index.ts CHANGED
@@ -8,24 +8,80 @@
8
8
  * DICT, registering recurring Pix Automático agreements) — distinct from PSPs
9
9
  * like Zoop/Asaas/Mercado Pago which serve merchants accepting Pix.
10
10
  *
11
- * Tools (10) — Pix focus for v0.1:
12
- * create_pix_charge_static — static QR code (merchant Pix key, reusable)
13
- * create_pix_charge_dynamic — dynamic QR code (single-use, expiring)
14
- * get_pix_charge — fetch a charge by txid
15
- * create_pix_payment — initiate an outbound Pix transfer
16
- * get_pix_payment — fetch a payment by endToEndId
17
- * refund_pix_payment — refund a Pix payment (MED / devolução)
18
- * list_pix_payments — list payments with start/end/status filters
19
- * resolve_pix_key — DICT lookup (CPF/CNPJ/email/phone/random → account)
20
- * list_dict_keys — list DICT keys registered to merchant accounts
21
- * create_pix_automatico — register a recurring Pix agreement (BCB 2025)
11
+ * Tools (21) — Pix + DICT + accounts for v0.2:
22
12
  *
23
- * Authentication
13
+ * Immediate Pix charges (BCB /cob)
14
+ * create_pix_charge_static — static QR code (merchant Pix key, reusable)
15
+ * create_pix_charge_dynamic — dynamic QR code (single-use, expiring)
16
+ * get_pix_charge — fetch a charge by txid
17
+ * list_pix_charges — list /cob charges with date+status filters
18
+ * update_pix_charge — PATCH a /cob (revise amount/expiration/status)
19
+ *
20
+ * Due-dated Pix charges (BCB /cobv — boleto-style with vencimento)
21
+ * create_pix_charge_due — cobv create
22
+ * get_pix_charge_due — cobv get by txid
23
+ *
24
+ * Outbound Pix payments (ordem de pagamento)
25
+ * create_pix_payment — initiate an outbound Pix transfer
26
+ * get_pix_payment — fetch a payment by endToEndId
27
+ * refund_pix_payment — refund a Pix payment (MED / devolução)
28
+ * list_pix_payments — list outbound payments with filters
29
+ *
30
+ * Inbound Pix (recebidos)
31
+ * list_pix_received — list received Pix in a window
32
+ *
33
+ * DICT (Pix key directory)
34
+ * resolve_pix_key — DICT lookup (CPF/CNPJ/email/phone/random → account)
35
+ * list_dict_keys — list DICT keys registered to merchant accounts
36
+ * register_dict_key — claim/register a DICT key for a merchant account
37
+ * delete_dict_key — remove a DICT key the merchant owns
38
+ *
39
+ * Pix Automático (BCB 2025 recurring Pix)
40
+ * create_pix_automatico — register a recurring Pix agreement
41
+ * get_pix_automatico — fetch a recurrence by idRec
42
+ * cancel_pix_automatico — cancel an active recurrence
43
+ *
44
+ * Accounts (Matera-managed core-banking accounts)
45
+ * get_account_balance — current balance for a Matera account
46
+ * get_account_statement — extrato / statement for a date range
47
+ * internal_transfer — book transfer between two Matera-managed accounts
48
+ *
49
+ * -------------------------------------------------------------------------
50
+ * ALPHA STATUS — endpoint paths below are NOT verified against a live sandbox.
51
+ *
52
+ * On 2026-04-24 we attempted to validate every path against doc-api.matera.com.
53
+ * The doc site was not reachable from our automation environment (DNS-gated;
54
+ * no login wall observed). Public search snippets and third-party references
55
+ * confirm Matera's product surface and auth model but do not surface exact URL
56
+ * paths. Where Matera-specific paths are unknown, we follow BCB Pix API v2
57
+ * conventions (`/cob`, `/cobv`, `/pix`, `/rec`) which Matera is required to
58
+ * mirror as a participating PSP. Known-suspect items are flagged inline with
59
+ * `TODO(verify)` comments.
60
+ *
61
+ * High-confidence corrections pending sandbox access:
62
+ * - Auth: Matera's server integration uses `secret-key` + `data-signature`
63
+ * headers, not OAuth2. OAuth2 is documented only for mobile / web-UI
64
+ * integrations. The code below still uses the OAuth2 client_credentials
65
+ * flow as a placeholder and will fail against the real server path.
66
+ * - Pix Automático: BCB's 2025 spec uses POST /rec (recurrence) and /cobr
67
+ * (recurring charge). `/pix/automatico` is a placeholder; the true Matera
68
+ * path is unknown.
69
+ * - DICT: Real DICT (RSFN-gated) sits at BCB; Matera wraps it. The wrapper
70
+ * path `/pix/dict/*` is a guess.
71
+ * - Accounts (balance/statement/internal-transfer): no public path.
72
+ * `/accounts/*` is a reasoned default.
73
+ *
74
+ * The 21 tool names + input schemas are the stable public contract. Only the
75
+ * internal `materaRequest(method, path, body)` calls will change when paths
76
+ * are verified.
77
+ * -------------------------------------------------------------------------
78
+ *
79
+ * Authentication (placeholder — see note above)
24
80
  * OAuth 2.0 Client Credentials. POST /auth/token with Basic auth
25
81
  * (client_id:client_secret) + grant_type=client_credentials. Bearer token
26
82
  * cached in memory until a minute before expiry.
27
- * Matera also supports secret-key + data-signature headers for signed
28
- * server-to-server calls; not used in v0.1.
83
+ * Matera's production server auth is secret-key + data-signature NOT
84
+ * implemented in this alpha.
29
85
  *
30
86
  * Environment
31
87
  * MATERA_CLIENT_ID OAuth2 client_id
@@ -56,6 +112,9 @@ async function getAccessToken(): Promise<string> {
56
112
  return tokenCache.accessToken;
57
113
  }
58
114
  const basic = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64");
115
+ // TODO(verify): Matera's real token path is unconfirmed. Candidates include
116
+ // `/auth/token`, `/oauth/token`, `/auth/v1/token`. Leaving the original
117
+ // placeholder until the sandbox confirms it.
59
118
  const res = await fetch(`${BASE_URL}/auth/token`, {
60
119
  method: "POST",
61
120
  headers: {
@@ -94,7 +153,7 @@ async function materaRequest(method: string, path: string, body?: unknown): Prom
94
153
  }
95
154
 
96
155
  const server = new Server(
97
- { name: "mcp-matera", version: "0.1.0" },
156
+ { name: "mcp-matera", version: "0.2.0" },
98
157
  { capabilities: { tools: {} } },
99
158
  );
100
159
 
@@ -151,6 +210,85 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
151
210
  required: ["txid"],
152
211
  },
153
212
  },
213
+ {
214
+ name: "list_pix_charges",
215
+ description: "List immediate Pix charges (BCB /cob) with date and status filters. Use for reconciling QR-driven receipts in a time window.",
216
+ inputSchema: {
217
+ type: "object",
218
+ properties: {
219
+ start: { type: "string", description: "ISO-8601 start timestamp (inclusive). BCB calls this `inicio`." },
220
+ end: { type: "string", description: "ISO-8601 end timestamp (exclusive). BCB calls this `fim`." },
221
+ status: { type: "string", description: "Filter by status (ATIVA, CONCLUIDA, REMOVIDA_PELO_USUARIO_RECEBEDOR, REMOVIDA_PELO_PSP)" },
222
+ cpf: { type: "string", description: "Filter charges where the payer CPF equals this value" },
223
+ cnpj: { type: "string", description: "Filter charges where the payer CNPJ equals this value" },
224
+ page: { type: "number", description: "Page number (starts at 0 per BCB convention)" },
225
+ limit: { type: "number", description: "Page size (BCB max 1000)" },
226
+ },
227
+ required: ["start", "end"],
228
+ },
229
+ },
230
+ {
231
+ name: "update_pix_charge",
232
+ description: "Update an immediate Pix charge (BCB PATCH /cob/{txid}). Use to revise amount/expiration before payment, or to mark a charge REMOVIDA_PELO_USUARIO_RECEBEDOR.",
233
+ inputSchema: {
234
+ type: "object",
235
+ properties: {
236
+ txid: { type: "string", description: "txid of the existing charge" },
237
+ amount: { type: "number", description: "New amount in BRL (only if charge still ATIVA)" },
238
+ expiration: { type: "number", description: "New expiration in seconds from creation" },
239
+ description: { type: "string", description: "New description shown to payer" },
240
+ status: { type: "string", description: "Set to REMOVIDA_PELO_USUARIO_RECEBEDOR to cancel an unpaid charge" },
241
+ },
242
+ required: ["txid"],
243
+ },
244
+ },
245
+ {
246
+ name: "create_pix_charge_due",
247
+ description: "Create a due-dated Pix charge (BCB /cobv — Pix com Vencimento). Boleto-style charge with due date, late fee (multa), interest (juros), and discount fields. Returns txid + QR payload.",
248
+ inputSchema: {
249
+ type: "object",
250
+ properties: {
251
+ pix_key: { type: "string", description: "Merchant Pix key the charge settles to" },
252
+ amount: { type: "number", description: "Original amount in BRL (valor.original)" },
253
+ description: { type: "string", description: "Description shown to payer (solicitacaoPagador)" },
254
+ due_date: { type: "string", description: "ISO-8601 date when the charge becomes due (data.dataDeVencimento)" },
255
+ validity_days_after_due: { type: "number", description: "How many calendar days after due date the QR remains payable (validadeAposVencimento). Default 30." },
256
+ debtor: {
257
+ type: "object",
258
+ description: "Payer (devedor). BCB requires CPF or CNPJ + name for cobv.",
259
+ properties: {
260
+ cpf: { type: "string" },
261
+ cnpj: { type: "string" },
262
+ name: { type: "string" },
263
+ address: { type: "string", description: "Logradouro" },
264
+ city: { type: "string", description: "Cidade" },
265
+ state: { type: "string", description: "UF (2-letter)" },
266
+ zip: { type: "string", description: "CEP" },
267
+ },
268
+ required: ["name"],
269
+ },
270
+ fine: { type: "number", description: "Late fee, BRL or % depending on `fine_mode` (multa.valor or multa.modalidade)" },
271
+ fine_mode: { type: "number", enum: [1, 2], description: "1=fixed amount, 2=percent (BCB modalidade)" },
272
+ interest: { type: "number", description: "Interest after due date" },
273
+ interest_mode: { type: "number", description: "BCB juros.modalidade (1..7). 2=percent per day, 5=percent per month, etc." },
274
+ discount: { type: "number", description: "Discount value if paid before due date" },
275
+ discount_mode: { type: "number", description: "BCB desconto.modalidade (1..6)" },
276
+ txid: { type: "string", description: "Optional merchant-side txid (26-35 alphanumerics)" },
277
+ },
278
+ required: ["pix_key", "amount", "description", "due_date", "debtor"],
279
+ },
280
+ },
281
+ {
282
+ name: "get_pix_charge_due",
283
+ description: "Retrieve a due-dated Pix charge (BCB GET /cobv/{txid}).",
284
+ inputSchema: {
285
+ type: "object",
286
+ properties: {
287
+ txid: { type: "string", description: "txid of the cobv charge" },
288
+ },
289
+ required: ["txid"],
290
+ },
291
+ },
154
292
  {
155
293
  name: "create_pix_payment",
156
294
  description: "Initiate an outbound Pix transfer (ordem de pagamento). Moves money from a debtor account held on Matera to any Pix key in BR. Returns endToEndId once the BCB SPI confirms.",
@@ -214,6 +352,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
214
352
  },
215
353
  },
216
354
  },
355
+ {
356
+ name: "list_pix_received",
357
+ description: "List inbound Pix (Pix recebidos) credited to merchant accounts in a date range. Mirrors BCB GET /pix. Filter by txid to find the receipt that closed a specific cob.",
358
+ inputSchema: {
359
+ type: "object",
360
+ properties: {
361
+ start: { type: "string", description: "ISO-8601 start timestamp (inclusive)" },
362
+ end: { type: "string", description: "ISO-8601 end timestamp (exclusive)" },
363
+ txid: { type: "string", description: "Only return Pix receipts that closed this txid" },
364
+ cpf: { type: "string", description: "Filter by payer CPF" },
365
+ cnpj: { type: "string", description: "Filter by payer CNPJ" },
366
+ page: { type: "number", description: "Page number (BCB starts at 0)" },
367
+ limit: { type: "number", description: "Page size" },
368
+ },
369
+ required: ["start", "end"],
370
+ },
371
+ },
217
372
  {
218
373
  name: "resolve_pix_key",
219
374
  description: "Resolve a Pix DICT key to the account holder's identity and ISPB/branch/account. Use before sending large transfers to verify the counterparty. Note: DICT queries are rate-limited and logged by BCB.",
@@ -235,9 +390,44 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
235
390
  },
236
391
  },
237
392
  },
393
+ {
394
+ name: "register_dict_key",
395
+ description: "Register (claim) a DICT key for a merchant account on Matera. For email/phone keys this triggers BCB's confirmation flow; for CPF/CNPJ/random it claims immediately. Subject to BCB DICT rate limits and ownership rules (max 5 keys per CPF, 20 per CNPJ).",
396
+ inputSchema: {
397
+ type: "object",
398
+ properties: {
399
+ key_type: { type: "string", enum: ["CPF", "CNPJ", "EMAIL", "PHONE", "RANDOM"], description: "Type of key to register" },
400
+ key_value: { type: "string", description: "Value (omit for RANDOM — Matera will generate the UUID)" },
401
+ account: {
402
+ type: "object",
403
+ description: "Matera-managed account that will own the key",
404
+ properties: {
405
+ branch: { type: "string", description: "Branch (agência)" },
406
+ account: { type: "string", description: "Account number" },
407
+ account_type: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"], description: "ISO 20022 account type" },
408
+ ispb: { type: "string", description: "ISPB of the holding institution (Matera-issued)" },
409
+ },
410
+ required: ["branch", "account", "account_type"],
411
+ },
412
+ },
413
+ required: ["key_type", "account"],
414
+ },
415
+ },
416
+ {
417
+ name: "delete_dict_key",
418
+ description: "Delete a DICT key the merchant owns. After delete, the key is held in BCB's quarentena window before another holder can claim it.",
419
+ inputSchema: {
420
+ type: "object",
421
+ properties: {
422
+ key: { type: "string", description: "Pix key value to delete" },
423
+ reason: { type: "string", description: "Optional reason (BCB MotivoExclusao). Default: USER_REQUESTED." },
424
+ },
425
+ required: ["key"],
426
+ },
427
+ },
238
428
  {
239
429
  name: "create_pix_automatico",
240
- description: "Register a Pix Automático agreement (BCB 2025 recurring Pix product). The payer authorizes the merchant to pull recurring amounts on a schedule. Matera is one of the few providers live with this.",
430
+ description: "Register a Pix Automático recurrence (BCB 2025 recurring Pix product, /rec). The payer authorizes the merchant to pull recurring amounts on a schedule. Matera is one of the few providers live with this.",
241
431
  inputSchema: {
242
432
  type: "object",
243
433
  properties: {
@@ -262,6 +452,90 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
262
452
  required: ["payer", "merchant_pix_key", "frequency", "amount", "first_charge_date", "description"],
263
453
  },
264
454
  },
455
+ {
456
+ name: "get_pix_automatico",
457
+ description: "Retrieve a Pix Automático recurrence by idRec. Returns current status (CRIADA, APROVADA, REJEITADA, CANCELADA), schedule, and last charge attempt.",
458
+ inputSchema: {
459
+ type: "object",
460
+ properties: {
461
+ id_rec: { type: "string", description: "Recurrence id (idRec) returned by create_pix_automatico" },
462
+ },
463
+ required: ["id_rec"],
464
+ },
465
+ },
466
+ {
467
+ name: "cancel_pix_automatico",
468
+ description: "Cancel an active Pix Automático recurrence. Future charges stop after BCB confirms the cancellation. Past charges are unaffected.",
469
+ inputSchema: {
470
+ type: "object",
471
+ properties: {
472
+ id_rec: { type: "string", description: "Recurrence id (idRec) to cancel" },
473
+ reason: { type: "string", description: "Optional reason recorded on the cancellation" },
474
+ },
475
+ required: ["id_rec"],
476
+ },
477
+ },
478
+ {
479
+ name: "get_account_balance",
480
+ description: "Get the current balance of a Matera-managed account. Returns available, blocked, and total balance in BRL.",
481
+ inputSchema: {
482
+ type: "object",
483
+ properties: {
484
+ branch: { type: "string", description: "Branch (agência)" },
485
+ account: { type: "string", description: "Account number" },
486
+ account_type: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"], description: "ISO 20022 account type. Defaults to CACC." },
487
+ },
488
+ required: ["branch", "account"],
489
+ },
490
+ },
491
+ {
492
+ name: "get_account_statement",
493
+ description: "Get the statement (extrato) of a Matera-managed account in a date range. Returns one entry per credit/debit with counterparty, endToEndId (when applicable), and running balance.",
494
+ inputSchema: {
495
+ type: "object",
496
+ properties: {
497
+ branch: { type: "string", description: "Branch (agência)" },
498
+ account: { type: "string", description: "Account number" },
499
+ account_type: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"], description: "ISO 20022 account type. Defaults to CACC." },
500
+ start: { type: "string", description: "ISO-8601 start date (inclusive)" },
501
+ end: { type: "string", description: "ISO-8601 end date (exclusive)" },
502
+ page: { type: "number", description: "Page number" },
503
+ limit: { type: "number", description: "Page size" },
504
+ },
505
+ required: ["branch", "account", "start", "end"],
506
+ },
507
+ },
508
+ {
509
+ name: "internal_transfer",
510
+ description: "Book a transfer between two accounts both held on Matera (TED-interno / transferência interna). Settles instantly without touching SPI/Pix rails — no endToEndId, no DICT lookup. Use for moving funds across a fintech's own customer accounts.",
511
+ inputSchema: {
512
+ type: "object",
513
+ properties: {
514
+ debtor_account: {
515
+ type: "object",
516
+ properties: {
517
+ branch: { type: "string" },
518
+ account: { type: "string" },
519
+ account_type: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"] },
520
+ },
521
+ required: ["branch", "account", "account_type"],
522
+ },
523
+ creditor_account: {
524
+ type: "object",
525
+ properties: {
526
+ branch: { type: "string" },
527
+ account: { type: "string" },
528
+ account_type: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"] },
529
+ },
530
+ required: ["branch", "account", "account_type"],
531
+ },
532
+ amount: { type: "number", description: "Amount in BRL (decimal)" },
533
+ description: { type: "string", description: "Free-text description recorded on both ledger entries" },
534
+ idempotency_key: { type: "string", description: "Merchant-side unique id to prevent double-debit on retry" },
535
+ },
536
+ required: ["debtor_account", "creditor_account", "amount"],
537
+ },
538
+ },
265
539
  ],
266
540
  }));
267
541
 
@@ -270,6 +544,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
270
544
  const a = (args ?? {}) as Record<string, unknown>;
271
545
  try {
272
546
  switch (name) {
547
+ // TODO(verify): BCB canonical charge paths are `/cob` (immediate) and
548
+ // `/cobv` (dated). Matera may expose them at `/pix/charges/*` per the
549
+ // original guess, but this has NOT been confirmed against the sandbox.
273
550
  case "create_pix_charge_static":
274
551
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/charges/static", a), null, 2) }] };
275
552
  case "create_pix_charge_dynamic":
@@ -278,6 +555,42 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
278
555
  const txid = encodeURIComponent(String(a.txid ?? ""));
279
556
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/charges/${txid}`), null, 2) }] };
280
557
  }
558
+ case "list_pix_charges": {
559
+ // TODO(verify): BCB Pix v2 lists /cob via `?inicio=&fim=`. Path is a
560
+ // wrapped Matera guess.
561
+ const params = new URLSearchParams();
562
+ if (a.start) params.set("inicio", String(a.start));
563
+ if (a.end) params.set("fim", String(a.end));
564
+ if (a.status) params.set("status", String(a.status));
565
+ if (a.cpf) params.set("cpf", String(a.cpf));
566
+ if (a.cnpj) params.set("cnpj", String(a.cnpj));
567
+ if (a.page !== undefined) params.set("paginacao.paginaAtual", String(a.page));
568
+ if (a.limit !== undefined) params.set("paginacao.itensPorPagina", String(a.limit));
569
+ const qs = params.toString();
570
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/charges${qs ? `?${qs}` : ""}`), null, 2) }] };
571
+ }
572
+ case "update_pix_charge": {
573
+ // TODO(verify): BCB spec uses PATCH /cob/{txid}. Wrapper path guessed.
574
+ const txid = encodeURIComponent(String(a.txid ?? ""));
575
+ const body: Record<string, unknown> = {};
576
+ if (a.amount !== undefined) body.amount = a.amount;
577
+ if (a.expiration !== undefined) body.expiration = a.expiration;
578
+ if (a.description !== undefined) body.description = a.description;
579
+ if (a.status !== undefined) body.status = a.status;
580
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("PATCH", `/pix/charges/${txid}`, body), null, 2) }] };
581
+ }
582
+ case "create_pix_charge_due": {
583
+ // TODO(verify): BCB canonical path is PUT /cobv/{txid}. Matera wrapper
584
+ // path unknown — using `/pix/charges/due` for symmetry with /charges.
585
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/charges/due", a), null, 2) }] };
586
+ }
587
+ case "get_pix_charge_due": {
588
+ const txid = encodeURIComponent(String(a.txid ?? ""));
589
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/charges/due/${txid}`), null, 2) }] };
590
+ }
591
+ // TODO(verify): BCB spec names outbound Pix refunds
592
+ // `PUT /pix/{e2eid}/devolucao/{id}`. The `POST /pix/payments/{e2eid}/refund`
593
+ // path below is a plausible Matera wrapper but NOT confirmed.
281
594
  case "create_pix_payment":
282
595
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/payments", a), null, 2) }] };
283
596
  case "get_pix_payment": {
@@ -299,6 +612,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
299
612
  const qs = params.toString();
300
613
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/payments${qs ? `?${qs}` : ""}`), null, 2) }] };
301
614
  }
615
+ case "list_pix_received": {
616
+ // TODO(verify): BCB canonical is GET /pix?inicio=&fim=. Wrapper path
617
+ // assumed at `/pix/received`.
618
+ const params = new URLSearchParams();
619
+ if (a.start) params.set("inicio", String(a.start));
620
+ if (a.end) params.set("fim", String(a.end));
621
+ if (a.txid) params.set("txid", String(a.txid));
622
+ if (a.cpf) params.set("cpf", String(a.cpf));
623
+ if (a.cnpj) params.set("cnpj", String(a.cnpj));
624
+ if (a.page !== undefined) params.set("paginacao.paginaAtual", String(a.page));
625
+ if (a.limit !== undefined) params.set("paginacao.itensPorPagina", String(a.limit));
626
+ const qs = params.toString();
627
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/received${qs ? `?${qs}` : ""}`), null, 2) }] };
628
+ }
629
+ // TODO(verify): BCB DICT is RSFN-gated and typically lives under
630
+ // `/api/v2/entries/*` on the Central Bank side. Matera wraps it in its
631
+ // own API — path `/pix/dict/*` below is a guess.
302
632
  case "resolve_pix_key": {
303
633
  const key = encodeURIComponent(String(a.key ?? ""));
304
634
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/dict/${key}`), null, 2) }] };
@@ -309,8 +639,62 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
309
639
  const qs = params.toString();
310
640
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/dict/keys${qs ? `?${qs}` : ""}`), null, 2) }] };
311
641
  }
642
+ case "register_dict_key": {
643
+ // TODO(verify): BCB DICT register is POST /api/v2/entries with an XML
644
+ // signed payload. Matera wraps it; path `/pix/dict/keys` is a guess.
645
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/dict/keys", a), null, 2) }] };
646
+ }
647
+ case "delete_dict_key": {
648
+ // TODO(verify): BCB uses DELETE /api/v2/entries/{key}. Wrapper path
649
+ // assumed.
650
+ const key = encodeURIComponent(String(a.key ?? ""));
651
+ const params = new URLSearchParams();
652
+ if (a.reason) params.set("reason", String(a.reason));
653
+ const qs = params.toString();
654
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("DELETE", `/pix/dict/${key}${qs ? `?${qs}` : ""}`), null, 2) }] };
655
+ }
656
+ // TODO(verify): `/pix/automatico` is almost certainly wrong. BCB's
657
+ // 2025 Pix Automático spec uses `POST /rec` (recorrência — payer
658
+ // authorization) and `/cobr/{txid}` (cobrança recorrente — each
659
+ // recurring charge). Matera likely mirrors this. The body shape here
660
+ // also needs to change (idRec, expiracaoSolicitacao, vinculo, etc.)
312
661
  case "create_pix_automatico":
313
662
  return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/automatico", a), null, 2) }] };
663
+ case "get_pix_automatico": {
664
+ const id = encodeURIComponent(String(a.id_rec ?? ""));
665
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/automatico/${id}`), null, 2) }] };
666
+ }
667
+ case "cancel_pix_automatico": {
668
+ const id = encodeURIComponent(String(a.id_rec ?? ""));
669
+ const body: Record<string, unknown> = { status: "CANCELADA" };
670
+ if (a.reason) body.reason = a.reason;
671
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("PATCH", `/pix/automatico/${id}`, body), null, 2) }] };
672
+ }
673
+ // TODO(verify): No public Matera path for accounts. `/accounts/*` is a
674
+ // reasoned default. Real path may live under `/baas/accounts/*` or
675
+ // `/core/accounts/*`.
676
+ case "get_account_balance": {
677
+ const params = new URLSearchParams();
678
+ params.set("branch", String(a.branch ?? ""));
679
+ params.set("account", String(a.account ?? ""));
680
+ if (a.account_type) params.set("account_type", String(a.account_type));
681
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/accounts/balance?${params.toString()}`), null, 2) }] };
682
+ }
683
+ case "get_account_statement": {
684
+ const params = new URLSearchParams();
685
+ params.set("branch", String(a.branch ?? ""));
686
+ params.set("account", String(a.account ?? ""));
687
+ if (a.account_type) params.set("account_type", String(a.account_type));
688
+ if (a.start) params.set("start", String(a.start));
689
+ if (a.end) params.set("end", String(a.end));
690
+ if (a.page !== undefined) params.set("page", String(a.page));
691
+ if (a.limit !== undefined) params.set("limit", String(a.limit));
692
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/accounts/statement?${params.toString()}`), null, 2) }] };
693
+ }
694
+ case "internal_transfer": {
695
+ // TODO(verify): book transfer endpoint unknown. Using `/accounts/transfers`.
696
+ return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/accounts/transfers", a), null, 2) }] };
697
+ }
314
698
  default:
315
699
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
316
700
  }
@@ -336,7 +720,7 @@ async function main() {
336
720
  if (!sid && isInitializeRequest(req.body)) {
337
721
  const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
338
722
  t.onclose = () => { if (t.sessionId) transports.delete(t.sessionId); };
339
- const s = new Server({ name: "mcp-matera", version: "0.1.0" }, { capabilities: { tools: {} } });
723
+ const s = new Server({ name: "mcp-matera", version: "0.2.0" }, { capabilities: { tools: {} } });
340
724
  (server as any)._requestHandlers.forEach((v: any, k: any) => (s as any)._requestHandlers.set(k, v));
341
725
  (server as any)._notificationHandlers?.forEach((v: any, k: any) => (s as any)._notificationHandlers.set(k, v));
342
726
  await s.connect(t);