@codespar/mcp-matera 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 +127 -0
- package/dist/index.js +358 -0
- package/package.json +35 -0
- package/server.json +44 -0
- package/src/index.ts +357 -0
- package/tsconfig.json +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# @codespar/mcp-matera
|
|
2
|
+
|
|
3
|
+
> MCP server for **Matera** — Brazilian core-banking infrastructure (BaaS) for fintechs building on top of Pix, DICT, and Pix Automático
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@codespar/mcp-matera)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Why Matera
|
|
9
|
+
|
|
10
|
+
Matera is **core-banking infrastructure**, not a PSP. Per vendor case studies it processes roughly 10% of Brazil's Pix transactions. Its customer is a **fintech building on top of Pix** — issuing accounts, moving money through DICT, registering Pix Automático agreements — **not a merchant accepting Pix** (that's what Zoop / Asaas / Mercado Pago are for).
|
|
11
|
+
|
|
12
|
+
This opens a segment in the CodeSpar catalog distinct from PSP servers: **fintech-building-on-top-of-Pix**. Matera sits under `banking` alongside Stark Bank and Open Finance, not under `payments`.
|
|
13
|
+
|
|
14
|
+
Use Matera when an agent needs to:
|
|
15
|
+
- Spin up Pix charges against accounts the fintech itself issued
|
|
16
|
+
- Do DICT lookups to resolve a Pix key before moving money
|
|
17
|
+
- Register recurring **Pix Automático** agreements (BCB 2025 product — few providers are live with this)
|
|
18
|
+
- Move money bank-to-bank through the fintech's own Matera rails
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### Claude Desktop
|
|
23
|
+
|
|
24
|
+
Add to `~/.config/claude/claude_desktop_config.json`:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"matera": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "@codespar/mcp-matera"],
|
|
32
|
+
"env": {
|
|
33
|
+
"MATERA_CLIENT_ID": "your-client-id",
|
|
34
|
+
"MATERA_CLIENT_SECRET": "your-client-secret"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Claude Code
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
claude mcp add matera -- npx @codespar/mcp-matera
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Cursor / VS Code
|
|
48
|
+
|
|
49
|
+
Add to `.cursor/mcp.json` or `.vscode/mcp.json`:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"servers": {
|
|
54
|
+
"matera": {
|
|
55
|
+
"command": "npx",
|
|
56
|
+
"args": ["-y", "@codespar/mcp-matera"],
|
|
57
|
+
"env": {
|
|
58
|
+
"MATERA_CLIENT_ID": "your-client-id",
|
|
59
|
+
"MATERA_CLIENT_SECRET": "your-client-secret"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
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) |
|
|
80
|
+
|
|
81
|
+
## Authentication
|
|
82
|
+
|
|
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.
|
|
86
|
+
|
|
87
|
+
## Environment Variables
|
|
88
|
+
|
|
89
|
+
| Variable | Required | Description |
|
|
90
|
+
|----------|----------|-------------|
|
|
91
|
+
| `MATERA_CLIENT_ID` | Yes | OAuth2 client_id issued by Matera |
|
|
92
|
+
| `MATERA_CLIENT_SECRET` | Yes | OAuth2 client_secret (secret) |
|
|
93
|
+
| `MATERA_BASE_URL` | No | API base URL. Defaults to `https://api.matera.com`. Sandbox URL varies per product line — ask your Matera contact. |
|
|
94
|
+
|
|
95
|
+
## Status
|
|
96
|
+
|
|
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.
|
|
98
|
+
|
|
99
|
+
## Roadmap
|
|
100
|
+
|
|
101
|
+
### v0.2 (planned)
|
|
102
|
+
- Signed-request auth path (`secret-key` + `data-signature`) for endpoints that require it
|
|
103
|
+
- Account opening (abertura de conta) — Matera IB product
|
|
104
|
+
- TED / bank transfers (non-Pix rails)
|
|
105
|
+
- Webhook event helpers
|
|
106
|
+
|
|
107
|
+
### v0.3 (planned)
|
|
108
|
+
- Internet Banking Server tools (statements, balances, card management)
|
|
109
|
+
- Boleto issuance
|
|
110
|
+
- Pix MED (Mecanismo Especial de Devolução) flow
|
|
111
|
+
|
|
112
|
+
Want to contribute? [Open a PR](https://github.com/codespar/mcp-dev-brasil) or [request a tool](https://github.com/codespar/mcp-dev-brasil/issues).
|
|
113
|
+
|
|
114
|
+
## Links
|
|
115
|
+
|
|
116
|
+
- [Matera](https://matera.com)
|
|
117
|
+
- [Matera API Documentation](https://doc-api.matera.com)
|
|
118
|
+
- [MCP Dev Brasil](https://github.com/codespar/mcp-dev-brasil)
|
|
119
|
+
- [Landing Page](https://codespar.dev/mcp)
|
|
120
|
+
|
|
121
|
+
## Enterprise
|
|
122
|
+
|
|
123
|
+
Need governance, budget limits, and audit trails for agent-initiated bank transfers? [CodeSpar Enterprise](https://codespar.dev/enterprise) adds policy engine, payment routing, and compliance templates on top of these MCP servers.
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Server for Matera — Brazilian core-banking infrastructure (BaaS).
|
|
4
|
+
*
|
|
5
|
+
* Matera is core-banking rails underneath fintechs, not a PSP. Per vendor
|
|
6
|
+
* case studies it processes ~10% of Brazil's Pix transactions. Its customer
|
|
7
|
+
* is a fintech building on top of Pix (issuing accounts, moving money through
|
|
8
|
+
* DICT, registering recurring Pix Automático agreements) — distinct from PSPs
|
|
9
|
+
* like Zoop/Asaas/Mercado Pago which serve merchants accepting Pix.
|
|
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)
|
|
22
|
+
*
|
|
23
|
+
* Authentication
|
|
24
|
+
* OAuth 2.0 Client Credentials. POST /auth/token with Basic auth
|
|
25
|
+
* (client_id:client_secret) + grant_type=client_credentials. Bearer token
|
|
26
|
+
* 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.
|
|
29
|
+
*
|
|
30
|
+
* Environment
|
|
31
|
+
* MATERA_CLIENT_ID OAuth2 client_id
|
|
32
|
+
* MATERA_CLIENT_SECRET OAuth2 client_secret
|
|
33
|
+
* MATERA_BASE_URL optional; defaults to https://api.matera.com
|
|
34
|
+
* (sandbox URL varies per product line)
|
|
35
|
+
*
|
|
36
|
+
* Docs: https://doc-api.matera.com
|
|
37
|
+
*/
|
|
38
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
39
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
40
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
41
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
42
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
43
|
+
const CLIENT_ID = process.env.MATERA_CLIENT_ID || "";
|
|
44
|
+
const CLIENT_SECRET = process.env.MATERA_CLIENT_SECRET || "";
|
|
45
|
+
const BASE_URL = process.env.MATERA_BASE_URL || "https://api.matera.com";
|
|
46
|
+
let tokenCache = null;
|
|
47
|
+
async function getAccessToken() {
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
if (tokenCache && tokenCache.expiresAt > now + 60_000) {
|
|
50
|
+
return tokenCache.accessToken;
|
|
51
|
+
}
|
|
52
|
+
const basic = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64");
|
|
53
|
+
const res = await fetch(`${BASE_URL}/auth/token`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: {
|
|
56
|
+
"Authorization": `Basic ${basic}`,
|
|
57
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
58
|
+
},
|
|
59
|
+
body: "grant_type=client_credentials",
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
throw new Error(`Matera OAuth ${res.status}: ${await res.text()}`);
|
|
63
|
+
}
|
|
64
|
+
const data = (await res.json());
|
|
65
|
+
tokenCache = {
|
|
66
|
+
accessToken: data.access_token,
|
|
67
|
+
expiresAt: now + data.expires_in * 1000,
|
|
68
|
+
};
|
|
69
|
+
return data.access_token;
|
|
70
|
+
}
|
|
71
|
+
async function materaRequest(method, path, body) {
|
|
72
|
+
const token = await getAccessToken();
|
|
73
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
74
|
+
method,
|
|
75
|
+
headers: {
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
"Authorization": `Bearer ${token}`,
|
|
78
|
+
},
|
|
79
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
80
|
+
});
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
throw new Error(`Matera API ${res.status}: ${await res.text()}`);
|
|
83
|
+
}
|
|
84
|
+
// Some endpoints (e.g. 204 No Content on refund) may return empty body
|
|
85
|
+
const text = await res.text();
|
|
86
|
+
return text ? JSON.parse(text) : {};
|
|
87
|
+
}
|
|
88
|
+
const server = new Server({ name: "mcp-matera", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
89
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
90
|
+
tools: [
|
|
91
|
+
{
|
|
92
|
+
name: "create_pix_charge_static",
|
|
93
|
+
description: "Create a static Pix charge (reusable QR code tied to a merchant Pix key). Returns EMV copy-paste payload and QR code image. Use for points-of-sale or donations where the same QR is shown to many payers.",
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: "object",
|
|
96
|
+
properties: {
|
|
97
|
+
pix_key: { type: "string", description: "Merchant Pix key (CPF, CNPJ, email, phone, or random UUID)" },
|
|
98
|
+
amount: { type: "number", description: "Amount in BRL (decimal, e.g. 10.50). Omit for open-amount QR." },
|
|
99
|
+
description: { type: "string", description: "Free-text description shown to payer" },
|
|
100
|
+
merchant_name: { type: "string", description: "Merchant name as it will appear on the QR payload" },
|
|
101
|
+
merchant_city: { type: "string", description: "Merchant city" },
|
|
102
|
+
txid: { type: "string", description: "Optional merchant-side transaction identifier (26-35 alphanumerics)" },
|
|
103
|
+
},
|
|
104
|
+
required: ["pix_key", "description"],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "create_pix_charge_dynamic",
|
|
109
|
+
description: "Create a dynamic Pix charge (single-use QR with expiration). Returns txid, EMV copy-paste, and QR image. Preferred for e-commerce checkouts and invoices.",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {
|
|
113
|
+
pix_key: { type: "string", description: "Merchant Pix key the charge settles to" },
|
|
114
|
+
amount: { type: "number", description: "Amount in BRL (decimal)" },
|
|
115
|
+
description: { type: "string", description: "Description shown to payer" },
|
|
116
|
+
expiration: { type: "number", description: "QR lifetime in seconds (e.g. 3600 = 1 hour)" },
|
|
117
|
+
debtor: {
|
|
118
|
+
type: "object",
|
|
119
|
+
description: "Optional payer identification (BCB requires CPF/CNPJ to be pre-known for some flows)",
|
|
120
|
+
properties: {
|
|
121
|
+
cpf: { type: "string" },
|
|
122
|
+
cnpj: { type: "string" },
|
|
123
|
+
name: { type: "string" },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
txid: { type: "string", description: "Optional merchant-side transaction identifier" },
|
|
127
|
+
},
|
|
128
|
+
required: ["pix_key", "amount", "description", "expiration"],
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "get_pix_charge",
|
|
133
|
+
description: "Retrieve a Pix charge (static or dynamic) by txid.",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: {
|
|
137
|
+
txid: { type: "string", description: "Matera txid returned by create_pix_charge_*" },
|
|
138
|
+
},
|
|
139
|
+
required: ["txid"],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "create_pix_payment",
|
|
144
|
+
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.",
|
|
145
|
+
inputSchema: {
|
|
146
|
+
type: "object",
|
|
147
|
+
properties: {
|
|
148
|
+
debtor_account: {
|
|
149
|
+
type: "object",
|
|
150
|
+
description: "Source account held on Matera (ispb, branch, account, account type)",
|
|
151
|
+
properties: {
|
|
152
|
+
ispb: { type: "string", description: "ISPB of the debtor bank" },
|
|
153
|
+
branch: { type: "string", description: "Branch (agência)" },
|
|
154
|
+
account: { type: "string", description: "Account number" },
|
|
155
|
+
account_type: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"], description: "ISO 20022 account type (CACC=corrente, SVGS=poupança)" },
|
|
156
|
+
},
|
|
157
|
+
required: ["ispb", "branch", "account", "account_type"],
|
|
158
|
+
},
|
|
159
|
+
creditor_pix_key: { type: "string", description: "Destination Pix key (CPF/CNPJ/email/phone/random)" },
|
|
160
|
+
amount: { type: "number", description: "Amount in BRL (decimal)" },
|
|
161
|
+
description: { type: "string", description: "Message shown to the recipient (optional)" },
|
|
162
|
+
idempotency_key: { type: "string", description: "Merchant-side unique id to prevent double-send on retry" },
|
|
163
|
+
},
|
|
164
|
+
required: ["debtor_account", "creditor_pix_key", "amount"],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "get_pix_payment",
|
|
169
|
+
description: "Retrieve an outbound Pix payment by endToEndId.",
|
|
170
|
+
inputSchema: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
end_to_end_id: { type: "string", description: "32-char BCB endToEndId (E<ispb><yyyyMMddHHmm><random>)" },
|
|
174
|
+
},
|
|
175
|
+
required: ["end_to_end_id"],
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: "refund_pix_payment",
|
|
180
|
+
description: "Refund (devolução) a Pix payment. Supports full or partial amount. Use reason codes per BCB MED catalog.",
|
|
181
|
+
inputSchema: {
|
|
182
|
+
type: "object",
|
|
183
|
+
properties: {
|
|
184
|
+
end_to_end_id: { type: "string", description: "endToEndId of the payment to refund" },
|
|
185
|
+
amount: { type: "number", description: "Refund amount in BRL. Must be <= original." },
|
|
186
|
+
reason: { type: "string", description: "Reason for refund (BCB MED code or free text)" },
|
|
187
|
+
},
|
|
188
|
+
required: ["end_to_end_id", "amount", "reason"],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: "list_pix_payments",
|
|
193
|
+
description: "List outbound Pix payments with optional filters. Useful for reconciliation and agent-driven audit.",
|
|
194
|
+
inputSchema: {
|
|
195
|
+
type: "object",
|
|
196
|
+
properties: {
|
|
197
|
+
start: { type: "string", description: "ISO-8601 start timestamp (inclusive)" },
|
|
198
|
+
end: { type: "string", description: "ISO-8601 end timestamp (exclusive)" },
|
|
199
|
+
status: { type: "string", description: "Filter by status (e.g. ACSC, RJCT, PDNG)" },
|
|
200
|
+
page: { type: "number", description: "Page number (starts at 1)" },
|
|
201
|
+
limit: { type: "number", description: "Page size" },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "resolve_pix_key",
|
|
207
|
+
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.",
|
|
208
|
+
inputSchema: {
|
|
209
|
+
type: "object",
|
|
210
|
+
properties: {
|
|
211
|
+
key: { type: "string", description: "Pix key to resolve (CPF, CNPJ, email, phone, or random UUID)" },
|
|
212
|
+
},
|
|
213
|
+
required: ["key"],
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: "list_dict_keys",
|
|
218
|
+
description: "List DICT keys registered to the merchant's accounts on Matera.",
|
|
219
|
+
inputSchema: {
|
|
220
|
+
type: "object",
|
|
221
|
+
properties: {
|
|
222
|
+
account: { type: "string", description: "Optional filter: return only keys for this account number" },
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
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.",
|
|
229
|
+
inputSchema: {
|
|
230
|
+
type: "object",
|
|
231
|
+
properties: {
|
|
232
|
+
payer: {
|
|
233
|
+
type: "object",
|
|
234
|
+
description: "Payer identity + bank",
|
|
235
|
+
properties: {
|
|
236
|
+
cpf: { type: "string" },
|
|
237
|
+
cnpj: { type: "string" },
|
|
238
|
+
name: { type: "string" },
|
|
239
|
+
ispb: { type: "string", description: "Payer bank ISPB" },
|
|
240
|
+
},
|
|
241
|
+
required: ["name"],
|
|
242
|
+
},
|
|
243
|
+
merchant_pix_key: { type: "string", description: "Merchant Pix key receiving the recurring payments" },
|
|
244
|
+
frequency: { type: "string", enum: ["WEEKLY", "MONTHLY", "QUARTERLY", "SEMESTRAL", "ANNUAL"], description: "Recurrence frequency" },
|
|
245
|
+
amount: { type: "number", description: "Amount per charge in BRL (fixed schedule)" },
|
|
246
|
+
first_charge_date: { type: "string", description: "ISO-8601 date of the first charge" },
|
|
247
|
+
end_date: { type: "string", description: "Optional ISO-8601 date to stop the recurrence" },
|
|
248
|
+
description: { type: "string", description: "Description shown to the payer on the authorization screen" },
|
|
249
|
+
},
|
|
250
|
+
required: ["payer", "merchant_pix_key", "frequency", "amount", "first_charge_date", "description"],
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
}));
|
|
255
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
256
|
+
const { name, arguments: args } = request.params;
|
|
257
|
+
const a = (args ?? {});
|
|
258
|
+
try {
|
|
259
|
+
switch (name) {
|
|
260
|
+
case "create_pix_charge_static":
|
|
261
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/charges/static", a), null, 2) }] };
|
|
262
|
+
case "create_pix_charge_dynamic":
|
|
263
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/charges/dynamic", a), null, 2) }] };
|
|
264
|
+
case "get_pix_charge": {
|
|
265
|
+
const txid = encodeURIComponent(String(a.txid ?? ""));
|
|
266
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/charges/${txid}`), null, 2) }] };
|
|
267
|
+
}
|
|
268
|
+
case "create_pix_payment":
|
|
269
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/payments", a), null, 2) }] };
|
|
270
|
+
case "get_pix_payment": {
|
|
271
|
+
const e2e = encodeURIComponent(String(a.end_to_end_id ?? ""));
|
|
272
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/payments/${e2e}`), null, 2) }] };
|
|
273
|
+
}
|
|
274
|
+
case "refund_pix_payment": {
|
|
275
|
+
const e2e = encodeURIComponent(String(a.end_to_end_id ?? ""));
|
|
276
|
+
const body = { amount: a.amount, reason: a.reason };
|
|
277
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", `/pix/payments/${e2e}/refund`, body), null, 2) }] };
|
|
278
|
+
}
|
|
279
|
+
case "list_pix_payments": {
|
|
280
|
+
const params = new URLSearchParams();
|
|
281
|
+
if (a.start)
|
|
282
|
+
params.set("start", String(a.start));
|
|
283
|
+
if (a.end)
|
|
284
|
+
params.set("end", String(a.end));
|
|
285
|
+
if (a.status)
|
|
286
|
+
params.set("status", String(a.status));
|
|
287
|
+
if (a.page)
|
|
288
|
+
params.set("page", String(a.page));
|
|
289
|
+
if (a.limit)
|
|
290
|
+
params.set("limit", String(a.limit));
|
|
291
|
+
const qs = params.toString();
|
|
292
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/payments${qs ? `?${qs}` : ""}`), null, 2) }] };
|
|
293
|
+
}
|
|
294
|
+
case "resolve_pix_key": {
|
|
295
|
+
const key = encodeURIComponent(String(a.key ?? ""));
|
|
296
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/dict/${key}`), null, 2) }] };
|
|
297
|
+
}
|
|
298
|
+
case "list_dict_keys": {
|
|
299
|
+
const params = new URLSearchParams();
|
|
300
|
+
if (a.account)
|
|
301
|
+
params.set("account", String(a.account));
|
|
302
|
+
const qs = params.toString();
|
|
303
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/dict/keys${qs ? `?${qs}` : ""}`), null, 2) }] };
|
|
304
|
+
}
|
|
305
|
+
case "create_pix_automatico":
|
|
306
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/automatico", a), null, 2) }] };
|
|
307
|
+
default:
|
|
308
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
async function main() {
|
|
316
|
+
if (process.argv.includes("--http") || process.env.MCP_HTTP === "true") {
|
|
317
|
+
const { default: express } = await import("express");
|
|
318
|
+
const { randomUUID } = await import("node:crypto");
|
|
319
|
+
const app = express();
|
|
320
|
+
app.use(express.json());
|
|
321
|
+
const transports = new Map();
|
|
322
|
+
app.get("/health", (_req, res) => res.json({ status: "ok", sessions: transports.size }));
|
|
323
|
+
app.post("/mcp", async (req, res) => {
|
|
324
|
+
const sid = req.headers["mcp-session-id"];
|
|
325
|
+
if (sid && transports.has(sid)) {
|
|
326
|
+
await transports.get(sid).handleRequest(req, res, req.body);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (!sid && isInitializeRequest(req.body)) {
|
|
330
|
+
const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
|
|
331
|
+
t.onclose = () => { if (t.sessionId)
|
|
332
|
+
transports.delete(t.sessionId); };
|
|
333
|
+
const s = new Server({ name: "mcp-matera", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
334
|
+
server._requestHandlers.forEach((v, k) => s._requestHandlers.set(k, v));
|
|
335
|
+
server._notificationHandlers?.forEach((v, k) => s._notificationHandlers.set(k, v));
|
|
336
|
+
await s.connect(t);
|
|
337
|
+
await t.handleRequest(req, res, req.body);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });
|
|
341
|
+
});
|
|
342
|
+
app.get("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
|
|
343
|
+
await transports.get(sid).handleRequest(req, res);
|
|
344
|
+
else
|
|
345
|
+
res.status(400).send("Invalid session"); });
|
|
346
|
+
app.delete("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
|
|
347
|
+
await transports.get(sid).handleRequest(req, res);
|
|
348
|
+
else
|
|
349
|
+
res.status(400).send("Invalid session"); });
|
|
350
|
+
const port = Number(process.env.MCP_PORT) || 3000;
|
|
351
|
+
app.listen(port, () => { console.error(`MCP HTTP server on http://localhost:${port}/mcp`); });
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
const transport = new StdioServerTransport();
|
|
355
|
+
await server.connect(transport);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
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",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-matera": "./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
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^25.5.0",
|
|
19
|
+
"typescript": "^5.8.0"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"matera",
|
|
25
|
+
"banking",
|
|
26
|
+
"baas",
|
|
27
|
+
"core-banking",
|
|
28
|
+
"pix",
|
|
29
|
+
"pix-automatico",
|
|
30
|
+
"dict",
|
|
31
|
+
"brazil",
|
|
32
|
+
"fintech"
|
|
33
|
+
],
|
|
34
|
+
"mcpName": "io.github.codespar/mcp-matera"
|
|
35
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.codespar/mcp-matera",
|
|
4
|
+
"description": "MCP server for Matera — Brazilian core-banking infrastructure (BaaS) for fintechs building on top of Pix, DICT, and Pix Automático",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/codespar/mcp-dev-brasil",
|
|
7
|
+
"source": "github",
|
|
8
|
+
"subfolder": "packages/banking/matera"
|
|
9
|
+
},
|
|
10
|
+
"version": "0.1.0",
|
|
11
|
+
"packages": [
|
|
12
|
+
{
|
|
13
|
+
"registryType": "npm",
|
|
14
|
+
"identifier": "@codespar/mcp-matera",
|
|
15
|
+
"version": "0.1.0",
|
|
16
|
+
"transport": {
|
|
17
|
+
"type": "stdio"
|
|
18
|
+
},
|
|
19
|
+
"environmentVariables": [
|
|
20
|
+
{
|
|
21
|
+
"name": "MATERA_CLIENT_ID",
|
|
22
|
+
"description": "OAuth2 client_id issued by Matera",
|
|
23
|
+
"isRequired": true,
|
|
24
|
+
"format": "string",
|
|
25
|
+
"isSecret": false
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "MATERA_CLIENT_SECRET",
|
|
29
|
+
"description": "OAuth2 client_secret issued by Matera",
|
|
30
|
+
"isRequired": true,
|
|
31
|
+
"format": "string",
|
|
32
|
+
"isSecret": true
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "MATERA_BASE_URL",
|
|
36
|
+
"description": "API base URL. Defaults to https://api.matera.com (production)",
|
|
37
|
+
"isRequired": false,
|
|
38
|
+
"format": "string",
|
|
39
|
+
"isSecret": false
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Server for Matera — Brazilian core-banking infrastructure (BaaS).
|
|
4
|
+
*
|
|
5
|
+
* Matera is core-banking rails underneath fintechs, not a PSP. Per vendor
|
|
6
|
+
* case studies it processes ~10% of Brazil's Pix transactions. Its customer
|
|
7
|
+
* is a fintech building on top of Pix (issuing accounts, moving money through
|
|
8
|
+
* DICT, registering recurring Pix Automático agreements) — distinct from PSPs
|
|
9
|
+
* like Zoop/Asaas/Mercado Pago which serve merchants accepting Pix.
|
|
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)
|
|
22
|
+
*
|
|
23
|
+
* Authentication
|
|
24
|
+
* OAuth 2.0 Client Credentials. POST /auth/token with Basic auth
|
|
25
|
+
* (client_id:client_secret) + grant_type=client_credentials. Bearer token
|
|
26
|
+
* 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.
|
|
29
|
+
*
|
|
30
|
+
* Environment
|
|
31
|
+
* MATERA_CLIENT_ID OAuth2 client_id
|
|
32
|
+
* MATERA_CLIENT_SECRET OAuth2 client_secret
|
|
33
|
+
* MATERA_BASE_URL optional; defaults to https://api.matera.com
|
|
34
|
+
* (sandbox URL varies per product line)
|
|
35
|
+
*
|
|
36
|
+
* Docs: https://doc-api.matera.com
|
|
37
|
+
*/
|
|
38
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
39
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
40
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
41
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
42
|
+
import {
|
|
43
|
+
CallToolRequestSchema,
|
|
44
|
+
ListToolsRequestSchema,
|
|
45
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
46
|
+
|
|
47
|
+
const CLIENT_ID = process.env.MATERA_CLIENT_ID || "";
|
|
48
|
+
const CLIENT_SECRET = process.env.MATERA_CLIENT_SECRET || "";
|
|
49
|
+
const BASE_URL = process.env.MATERA_BASE_URL || "https://api.matera.com";
|
|
50
|
+
|
|
51
|
+
let tokenCache: { accessToken: string; expiresAt: number } | null = null;
|
|
52
|
+
|
|
53
|
+
async function getAccessToken(): Promise<string> {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
if (tokenCache && tokenCache.expiresAt > now + 60_000) {
|
|
56
|
+
return tokenCache.accessToken;
|
|
57
|
+
}
|
|
58
|
+
const basic = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64");
|
|
59
|
+
const res = await fetch(`${BASE_URL}/auth/token`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: {
|
|
62
|
+
"Authorization": `Basic ${basic}`,
|
|
63
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
64
|
+
},
|
|
65
|
+
body: "grant_type=client_credentials",
|
|
66
|
+
});
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
throw new Error(`Matera OAuth ${res.status}: ${await res.text()}`);
|
|
69
|
+
}
|
|
70
|
+
const data = (await res.json()) as { access_token: string; expires_in: number };
|
|
71
|
+
tokenCache = {
|
|
72
|
+
accessToken: data.access_token,
|
|
73
|
+
expiresAt: now + data.expires_in * 1000,
|
|
74
|
+
};
|
|
75
|
+
return data.access_token;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function materaRequest(method: string, path: string, body?: unknown): Promise<unknown> {
|
|
79
|
+
const token = await getAccessToken();
|
|
80
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
81
|
+
method,
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": "application/json",
|
|
84
|
+
"Authorization": `Bearer ${token}`,
|
|
85
|
+
},
|
|
86
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
87
|
+
});
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
throw new Error(`Matera API ${res.status}: ${await res.text()}`);
|
|
90
|
+
}
|
|
91
|
+
// Some endpoints (e.g. 204 No Content on refund) may return empty body
|
|
92
|
+
const text = await res.text();
|
|
93
|
+
return text ? JSON.parse(text) : {};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const server = new Server(
|
|
97
|
+
{ name: "mcp-matera", version: "0.1.0" },
|
|
98
|
+
{ capabilities: { tools: {} } },
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
102
|
+
tools: [
|
|
103
|
+
{
|
|
104
|
+
name: "create_pix_charge_static",
|
|
105
|
+
description: "Create a static Pix charge (reusable QR code tied to a merchant Pix key). Returns EMV copy-paste payload and QR code image. Use for points-of-sale or donations where the same QR is shown to many payers.",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
pix_key: { type: "string", description: "Merchant Pix key (CPF, CNPJ, email, phone, or random UUID)" },
|
|
110
|
+
amount: { type: "number", description: "Amount in BRL (decimal, e.g. 10.50). Omit for open-amount QR." },
|
|
111
|
+
description: { type: "string", description: "Free-text description shown to payer" },
|
|
112
|
+
merchant_name: { type: "string", description: "Merchant name as it will appear on the QR payload" },
|
|
113
|
+
merchant_city: { type: "string", description: "Merchant city" },
|
|
114
|
+
txid: { type: "string", description: "Optional merchant-side transaction identifier (26-35 alphanumerics)" },
|
|
115
|
+
},
|
|
116
|
+
required: ["pix_key", "description"],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "create_pix_charge_dynamic",
|
|
121
|
+
description: "Create a dynamic Pix charge (single-use QR with expiration). Returns txid, EMV copy-paste, and QR image. Preferred for e-commerce checkouts and invoices.",
|
|
122
|
+
inputSchema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: {
|
|
125
|
+
pix_key: { type: "string", description: "Merchant Pix key the charge settles to" },
|
|
126
|
+
amount: { type: "number", description: "Amount in BRL (decimal)" },
|
|
127
|
+
description: { type: "string", description: "Description shown to payer" },
|
|
128
|
+
expiration: { type: "number", description: "QR lifetime in seconds (e.g. 3600 = 1 hour)" },
|
|
129
|
+
debtor: {
|
|
130
|
+
type: "object",
|
|
131
|
+
description: "Optional payer identification (BCB requires CPF/CNPJ to be pre-known for some flows)",
|
|
132
|
+
properties: {
|
|
133
|
+
cpf: { type: "string" },
|
|
134
|
+
cnpj: { type: "string" },
|
|
135
|
+
name: { type: "string" },
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
txid: { type: "string", description: "Optional merchant-side transaction identifier" },
|
|
139
|
+
},
|
|
140
|
+
required: ["pix_key", "amount", "description", "expiration"],
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "get_pix_charge",
|
|
145
|
+
description: "Retrieve a Pix charge (static or dynamic) by txid.",
|
|
146
|
+
inputSchema: {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
txid: { type: "string", description: "Matera txid returned by create_pix_charge_*" },
|
|
150
|
+
},
|
|
151
|
+
required: ["txid"],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "create_pix_payment",
|
|
156
|
+
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.",
|
|
157
|
+
inputSchema: {
|
|
158
|
+
type: "object",
|
|
159
|
+
properties: {
|
|
160
|
+
debtor_account: {
|
|
161
|
+
type: "object",
|
|
162
|
+
description: "Source account held on Matera (ispb, branch, account, account type)",
|
|
163
|
+
properties: {
|
|
164
|
+
ispb: { type: "string", description: "ISPB of the debtor bank" },
|
|
165
|
+
branch: { type: "string", description: "Branch (agência)" },
|
|
166
|
+
account: { type: "string", description: "Account number" },
|
|
167
|
+
account_type: { type: "string", enum: ["CACC", "SLRY", "SVGS", "TRAN"], description: "ISO 20022 account type (CACC=corrente, SVGS=poupança)" },
|
|
168
|
+
},
|
|
169
|
+
required: ["ispb", "branch", "account", "account_type"],
|
|
170
|
+
},
|
|
171
|
+
creditor_pix_key: { type: "string", description: "Destination Pix key (CPF/CNPJ/email/phone/random)" },
|
|
172
|
+
amount: { type: "number", description: "Amount in BRL (decimal)" },
|
|
173
|
+
description: { type: "string", description: "Message shown to the recipient (optional)" },
|
|
174
|
+
idempotency_key: { type: "string", description: "Merchant-side unique id to prevent double-send on retry" },
|
|
175
|
+
},
|
|
176
|
+
required: ["debtor_account", "creditor_pix_key", "amount"],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: "get_pix_payment",
|
|
181
|
+
description: "Retrieve an outbound Pix payment by endToEndId.",
|
|
182
|
+
inputSchema: {
|
|
183
|
+
type: "object",
|
|
184
|
+
properties: {
|
|
185
|
+
end_to_end_id: { type: "string", description: "32-char BCB endToEndId (E<ispb><yyyyMMddHHmm><random>)" },
|
|
186
|
+
},
|
|
187
|
+
required: ["end_to_end_id"],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "refund_pix_payment",
|
|
192
|
+
description: "Refund (devolução) a Pix payment. Supports full or partial amount. Use reason codes per BCB MED catalog.",
|
|
193
|
+
inputSchema: {
|
|
194
|
+
type: "object",
|
|
195
|
+
properties: {
|
|
196
|
+
end_to_end_id: { type: "string", description: "endToEndId of the payment to refund" },
|
|
197
|
+
amount: { type: "number", description: "Refund amount in BRL. Must be <= original." },
|
|
198
|
+
reason: { type: "string", description: "Reason for refund (BCB MED code or free text)" },
|
|
199
|
+
},
|
|
200
|
+
required: ["end_to_end_id", "amount", "reason"],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "list_pix_payments",
|
|
205
|
+
description: "List outbound Pix payments with optional filters. Useful for reconciliation and agent-driven audit.",
|
|
206
|
+
inputSchema: {
|
|
207
|
+
type: "object",
|
|
208
|
+
properties: {
|
|
209
|
+
start: { type: "string", description: "ISO-8601 start timestamp (inclusive)" },
|
|
210
|
+
end: { type: "string", description: "ISO-8601 end timestamp (exclusive)" },
|
|
211
|
+
status: { type: "string", description: "Filter by status (e.g. ACSC, RJCT, PDNG)" },
|
|
212
|
+
page: { type: "number", description: "Page number (starts at 1)" },
|
|
213
|
+
limit: { type: "number", description: "Page size" },
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: "resolve_pix_key",
|
|
219
|
+
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.",
|
|
220
|
+
inputSchema: {
|
|
221
|
+
type: "object",
|
|
222
|
+
properties: {
|
|
223
|
+
key: { type: "string", description: "Pix key to resolve (CPF, CNPJ, email, phone, or random UUID)" },
|
|
224
|
+
},
|
|
225
|
+
required: ["key"],
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "list_dict_keys",
|
|
230
|
+
description: "List DICT keys registered to the merchant's accounts on Matera.",
|
|
231
|
+
inputSchema: {
|
|
232
|
+
type: "object",
|
|
233
|
+
properties: {
|
|
234
|
+
account: { type: "string", description: "Optional filter: return only keys for this account number" },
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
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.",
|
|
241
|
+
inputSchema: {
|
|
242
|
+
type: "object",
|
|
243
|
+
properties: {
|
|
244
|
+
payer: {
|
|
245
|
+
type: "object",
|
|
246
|
+
description: "Payer identity + bank",
|
|
247
|
+
properties: {
|
|
248
|
+
cpf: { type: "string" },
|
|
249
|
+
cnpj: { type: "string" },
|
|
250
|
+
name: { type: "string" },
|
|
251
|
+
ispb: { type: "string", description: "Payer bank ISPB" },
|
|
252
|
+
},
|
|
253
|
+
required: ["name"],
|
|
254
|
+
},
|
|
255
|
+
merchant_pix_key: { type: "string", description: "Merchant Pix key receiving the recurring payments" },
|
|
256
|
+
frequency: { type: "string", enum: ["WEEKLY", "MONTHLY", "QUARTERLY", "SEMESTRAL", "ANNUAL"], description: "Recurrence frequency" },
|
|
257
|
+
amount: { type: "number", description: "Amount per charge in BRL (fixed schedule)" },
|
|
258
|
+
first_charge_date: { type: "string", description: "ISO-8601 date of the first charge" },
|
|
259
|
+
end_date: { type: "string", description: "Optional ISO-8601 date to stop the recurrence" },
|
|
260
|
+
description: { type: "string", description: "Description shown to the payer on the authorization screen" },
|
|
261
|
+
},
|
|
262
|
+
required: ["payer", "merchant_pix_key", "frequency", "amount", "first_charge_date", "description"],
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
269
|
+
const { name, arguments: args } = request.params;
|
|
270
|
+
const a = (args ?? {}) as Record<string, unknown>;
|
|
271
|
+
try {
|
|
272
|
+
switch (name) {
|
|
273
|
+
case "create_pix_charge_static":
|
|
274
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/charges/static", a), null, 2) }] };
|
|
275
|
+
case "create_pix_charge_dynamic":
|
|
276
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/charges/dynamic", a), null, 2) }] };
|
|
277
|
+
case "get_pix_charge": {
|
|
278
|
+
const txid = encodeURIComponent(String(a.txid ?? ""));
|
|
279
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/charges/${txid}`), null, 2) }] };
|
|
280
|
+
}
|
|
281
|
+
case "create_pix_payment":
|
|
282
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/payments", a), null, 2) }] };
|
|
283
|
+
case "get_pix_payment": {
|
|
284
|
+
const e2e = encodeURIComponent(String(a.end_to_end_id ?? ""));
|
|
285
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/payments/${e2e}`), null, 2) }] };
|
|
286
|
+
}
|
|
287
|
+
case "refund_pix_payment": {
|
|
288
|
+
const e2e = encodeURIComponent(String(a.end_to_end_id ?? ""));
|
|
289
|
+
const body: Record<string, unknown> = { amount: a.amount, reason: a.reason };
|
|
290
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", `/pix/payments/${e2e}/refund`, body), null, 2) }] };
|
|
291
|
+
}
|
|
292
|
+
case "list_pix_payments": {
|
|
293
|
+
const params = new URLSearchParams();
|
|
294
|
+
if (a.start) params.set("start", String(a.start));
|
|
295
|
+
if (a.end) params.set("end", String(a.end));
|
|
296
|
+
if (a.status) params.set("status", String(a.status));
|
|
297
|
+
if (a.page) params.set("page", String(a.page));
|
|
298
|
+
if (a.limit) params.set("limit", String(a.limit));
|
|
299
|
+
const qs = params.toString();
|
|
300
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/payments${qs ? `?${qs}` : ""}`), null, 2) }] };
|
|
301
|
+
}
|
|
302
|
+
case "resolve_pix_key": {
|
|
303
|
+
const key = encodeURIComponent(String(a.key ?? ""));
|
|
304
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/dict/${key}`), null, 2) }] };
|
|
305
|
+
}
|
|
306
|
+
case "list_dict_keys": {
|
|
307
|
+
const params = new URLSearchParams();
|
|
308
|
+
if (a.account) params.set("account", String(a.account));
|
|
309
|
+
const qs = params.toString();
|
|
310
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("GET", `/pix/dict/keys${qs ? `?${qs}` : ""}`), null, 2) }] };
|
|
311
|
+
}
|
|
312
|
+
case "create_pix_automatico":
|
|
313
|
+
return { content: [{ type: "text", text: JSON.stringify(await materaRequest("POST", "/pix/automatico", a), null, 2) }] };
|
|
314
|
+
default:
|
|
315
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
316
|
+
}
|
|
317
|
+
} catch (err) {
|
|
318
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
async function main() {
|
|
323
|
+
if (process.argv.includes("--http") || process.env.MCP_HTTP === "true") {
|
|
324
|
+
const { default: express } = await import("express");
|
|
325
|
+
const { randomUUID } = await import("node:crypto");
|
|
326
|
+
const app = express();
|
|
327
|
+
app.use(express.json());
|
|
328
|
+
const transports = new Map<string, StreamableHTTPServerTransport>();
|
|
329
|
+
app.get("/health", (_req, res) => res.json({ status: "ok", sessions: transports.size }));
|
|
330
|
+
app.post("/mcp", async (req, res) => {
|
|
331
|
+
const sid = req.headers["mcp-session-id"] as string | undefined;
|
|
332
|
+
if (sid && transports.has(sid)) {
|
|
333
|
+
await transports.get(sid)!.handleRequest(req, res, req.body);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (!sid && isInitializeRequest(req.body)) {
|
|
337
|
+
const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
|
|
338
|
+
t.onclose = () => { if (t.sessionId) transports.delete(t.sessionId); };
|
|
339
|
+
const s = new Server({ name: "mcp-matera", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
340
|
+
(server as any)._requestHandlers.forEach((v: any, k: any) => (s as any)._requestHandlers.set(k, v));
|
|
341
|
+
(server as any)._notificationHandlers?.forEach((v: any, k: any) => (s as any)._notificationHandlers.set(k, v));
|
|
342
|
+
await s.connect(t);
|
|
343
|
+
await t.handleRequest(req, res, req.body);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });
|
|
347
|
+
});
|
|
348
|
+
app.get("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"] as string | undefined; if (sid && transports.has(sid)) await transports.get(sid)!.handleRequest(req, res); else res.status(400).send("Invalid session"); });
|
|
349
|
+
app.delete("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"] as string | undefined; if (sid && transports.has(sid)) await transports.get(sid)!.handleRequest(req, res); else res.status(400).send("Invalid session"); });
|
|
350
|
+
const port = Number(process.env.MCP_PORT) || 3000;
|
|
351
|
+
app.listen(port, () => { console.error(`MCP HTTP server on http://localhost:${port}/mcp`); });
|
|
352
|
+
} else {
|
|
353
|
+
const transport = new StdioServerTransport();
|
|
354
|
+
await server.connect(transport);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
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
|
+
}
|