opf-br-mcp 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/dist/index.js +1185 -0
  4. package/package.json +53 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 José Rogério
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # opf-br-mcp
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
4
+ [![npm](https://img.shields.io/npm/v/opf-br-mcp.svg)](https://www.npmjs.com/package/opf-br-mcp)
5
+
6
+ MCP server local que dá a agentes de codificação (Claude Code, GitHub Copilot)
7
+ acesso token-eficiente às regras do Open Finance Brasil.
8
+
9
+ ## Domínios disponíveis
10
+
11
+ | Domínio | Fonte | Conteúdo |
12
+ |---|---|---|
13
+ | `pcm-additional-info` | Confluence público OFB | Regras de obrigatoriedade do `additionalInfo` (PCM) |
14
+ | `payments-v4-openapi` | GitHub OpenBanking-Brasil/openapi | Spec OpenAPI 4.0.0 da API de Pagamentos |
15
+ | `payments-v5-openapi` | GitHub OpenBanking-Brasil/all-services-repo | Spec OpenAPI 5.0.0 da API de Iniciação de Pagamentos (consentimentos + Pix) |
16
+ | `enrollments-v2-openapi` | GitHub OpenBanking-Brasil/all-services-repo | Spec OpenAPI 2.2.0 da API de Vínculo de Dispositivo (Enrollments, FIDO, Pix Automático) |
17
+ | `automatic-payments-v2-openapi` | GitHub OpenBanking-Brasil/all-services-repo | Spec OpenAPI 2.2.0 da API de Pagamentos Automáticos (Pix Automático e Transferências Inteligentes) |
18
+ | `consents-v3-openapi` | GitHub Pages openbanking-brasil.github.io | Spec OpenAPI 3.3.1 da API de Consentimentos (Dados Cadastrais e Transacionais) |
19
+ | `pcm-openapi` | GitHub OpenBanking-Brasil/pcm-specs | Spec OpenAPI da PCM (reportes, hybrid-flow, opendata, consents/stock, credit-portabilities, payments/status) |
20
+ | `pcm-business-rules` | Confluence público OFB | Regras de negócio da PCM (Reporte, Processamento, Divergências, Especificação Técnica, Manual de Integração) — item por seção |
21
+ | `jornada-otimizada` | Confluence público OFB | Regras da Jornada Otimizada (Orientações Gerais, Transferências Inteligentes, Jornada sem Redirecionamento) — item por seção |
22
+ | `mqd` | Confluence público OFB | Motor de Qualidade de Dados (Especificação Técnica, Arquitetura, Documentação da API, Manual de Instalação, Endpoints Validados, FAQ, Troubleshooting) — item por seção |
23
+ | `webhook-v1-openapi` | GitHub OpenBanking-Brasil/all-services-repo | Spec OpenAPI 1.3.0 da API de Webhook (notificações de mudança de estado: pagamentos, enrollments, pagamentos automáticos) |
24
+ | `seguranca` | Confluence público OFB | Segurança do Open Finance Brasil (Perfil de Segurança, FAPI, DCR, CIBA, Padrão de Certificados, Assinaturas, Casos de Erro, Redirecionamento App-to-App, Glossário, Versionamento) — item por seção |
25
+ | `participantes` | Diretório OFB (data.directory.openbankingbrasil.org.br) | Organizações participantes, marcas (authorisation servers) e famílias de API suportadas com versões — um item por organização |
26
+ | `portal` | Confluence público OFB (busca ao vivo) | Busca CQL em todo o Portal do Desenvolvedor (espaço OF) — sem cache, `query` obrigatória; fallback quando os domínios específicos não cobrem o assunto |
27
+
28
+ ## Tools
29
+
30
+ - `list_domains()` — descoberta: domínios, filtros e estado do cache
31
+ - `search(domain, query?, filters?, limit?, offset?)` — busca filtrada, retorno compacto
32
+ - `get_item(domain, id)` — registro completo
33
+ - `refresh(domain?)` — força re-extração das fontes
34
+
35
+ Fluxo recomendado para o agente: `list_domains` → `search` → `get_item`.
36
+
37
+ Domínios marcados como `live` (ex.: `portal`) consultam a fonte a cada chamada:
38
+ não têm cache nem `refresh`, e `search` exige `query`. Quando um `search` em
39
+ domínio comum retorna 0 resultados, a resposta inclui um `hint` sugerindo o
40
+ `portal`.
41
+
42
+ ## Progressive disclosure (por que economiza contexto)
43
+
44
+ O problema que este servidor resolve: uma spec Swagger/OpenAPI inteira não cabe
45
+ bem na janela de contexto de um agente, e despejá-la desperdiça tokens. A solução
46
+ é **revelação progressiva** — o agente nunca recebe a spec completa de uma vez,
47
+ apenas o mínimo necessário em cada etapa do funil:
48
+
49
+ 1. **`list_domains`** — catálogo barato: quais domínios e filtros existem.
50
+ 2. **`search`** — índice **pesquisável e resumido**. Cada resultado traz só
51
+ `id`, `path`, `method`, `summary`/`name` e `required`; o nó pesado da spec
52
+ (`detail`) é removido do resumo, e o retorno ainda é compactado (omite `null`
53
+ e arrays vazios).
54
+ 3. **`get_item`** — só aqui o nó integral da spec é entregue, e apenas para o
55
+ `id` que o agente escolheu.
56
+
57
+ Como o Swagger/OpenAPI vira dados pesquisáveis: o parser "achata" a spec em itens
58
+ com `id` estável — um por endpoint (`type: operation`, ex.
59
+ `payments-v4:POST /pix/payments`) e um por schema (`type: schema`, ex.
60
+ `payments-v4:schema:PixPayment`). O JSON completo de cada nó fica retido em
61
+ `detail` até um `get_item` explícito. Os `id`s não são adivinháveis: sempre vêm
62
+ de um `search`. Assim o agente localiza o endpoint/schema certo pagando poucos
63
+ tokens e só "paga" o payload integral quando pede um item nomeado.
64
+
65
+ Dados: extraídos das fontes públicas na primeira consulta (lazy), cache em
66
+ `~/.cache/opf-br-mcp/` com TTL de 24h. Sem rede, serve cache expirado com aviso.
67
+
68
+ ## Instalação
69
+
70
+ Requer Node >= 20. O servidor roda via `npx`, sem clone nem build.
71
+
72
+ Claude Code — `.mcp.json` na raiz do projeto consumidor:
73
+
74
+ ```json
75
+ { "mcpServers": { "opf-br": { "command": "npx", "args": ["-y", "opf-br-mcp"] } } }
76
+ ```
77
+
78
+ GitHub Copilot (VS Code) — `.vscode/mcp.json`:
79
+
80
+ ```json
81
+ { "servers": { "opf-br": { "command": "npx", "args": ["-y", "opf-br-mcp"] } } }
82
+ ```
83
+
84
+ ### Uso local (a partir do fonte)
85
+
86
+ ```bash
87
+ git clone https://github.com/jrogeriosilva/opf-br-mcp.git && cd opf-br-mcp
88
+ npm install && npm run build
89
+ ```
90
+
91
+ E aponte o client para o build local:
92
+
93
+ ```json
94
+ { "mcpServers": { "opf-br": { "command": "node", "args": ["/caminho/para/opf-br-mcp/dist/index.js"] } } }
95
+ ```
96
+
97
+ ## Adicionando um domínio novo
98
+
99
+ 1. Criar `src/domains/<id>/index.ts` exportando um objeto `Domain`
100
+ (`src/core/types.ts`): `extract()` busca e estrutura os dados;
101
+ `search`/`getItem` consultam; `filters` documenta os filtros.
102
+ 2. Registrar em `src/core/registry.ts`.
103
+ 3. Adicionar fixture e builder em `test/contract.test.ts` — a suíte de
104
+ conformidade valida o contrato automaticamente.
105
+
106
+ ## Desenvolvimento
107
+
108
+ ```bash
109
+ npm test # vitest (fixtures locais, sem rede)
110
+ npm run typecheck # tsc --noEmit
111
+ npm run build # tsup → dist/
112
+ ```
package/dist/index.js ADDED
@@ -0,0 +1,1185 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+
6
+ // src/core/server.ts
7
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ import { z } from "zod";
9
+
10
+ // src/core/cache.ts
11
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
12
+ import { homedir } from "os";
13
+ import { join } from "path";
14
+ function cacheDir() {
15
+ const base = process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
16
+ return join(base, "opf-br-mcp");
17
+ }
18
+ function cachePath(domainId) {
19
+ return join(cacheDir(), `${domainId}.json`);
20
+ }
21
+ function readCache(domainId) {
22
+ const path = cachePath(domainId);
23
+ if (!existsSync(path)) return null;
24
+ try {
25
+ return JSON.parse(readFileSync(path, "utf8"));
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ function writeCache(domainId, data, packageVersion) {
31
+ const entry = {
32
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
33
+ packageVersion,
34
+ data
35
+ };
36
+ mkdirSync(cacheDir(), { recursive: true });
37
+ const finalPath = cachePath(domainId);
38
+ const tmpPath = `${finalPath}.${process.pid}.tmp`;
39
+ writeFileSync(tmpPath, JSON.stringify(entry));
40
+ renameSync(tmpPath, finalPath);
41
+ return entry;
42
+ }
43
+ function isFresh(entry, ttlHours, now = /* @__PURE__ */ new Date()) {
44
+ const age = now.getTime() - new Date(entry.extractedAt).getTime();
45
+ return age < ttlHours * 36e5;
46
+ }
47
+
48
+ // src/core/version.ts
49
+ var PACKAGE_VERSION = "0.4.0";
50
+
51
+ // src/core/data.ts
52
+ async function getDomainData(domain, force = false, ctx) {
53
+ const cached = readCache(domain.id);
54
+ if (!force && cached && cached.packageVersion === PACKAGE_VERSION && isFresh(cached, domain.ttlHours)) {
55
+ return { data: cached.data, extractedAt: cached.extractedAt, stale: false };
56
+ }
57
+ try {
58
+ const data = await domain.extract(ctx);
59
+ const entry = writeCache(domain.id, data, PACKAGE_VERSION);
60
+ return { data, extractedAt: entry.extractedAt, stale: false };
61
+ } catch (err) {
62
+ if (cached) {
63
+ return { data: cached.data, extractedAt: cached.extractedAt, stale: true };
64
+ }
65
+ throw err;
66
+ }
67
+ }
68
+
69
+ // src/core/http.ts
70
+ function sleep(ms) {
71
+ return new Promise((resolve) => setTimeout(resolve, ms));
72
+ }
73
+ async function fetchWithRetry(url, opts) {
74
+ let lastError;
75
+ for (let attempt = 0; attempt <= opts.retryDelaysMs.length; attempt++) {
76
+ if (opts.signal?.aborted) break;
77
+ if (attempt > 0) await sleep(opts.retryDelaysMs[attempt - 1]);
78
+ const timeout = AbortSignal.timeout(3e4);
79
+ const signal = opts.signal ? AbortSignal.any([opts.signal, timeout]) : timeout;
80
+ let response;
81
+ try {
82
+ response = await fetch(url, { headers: opts.headers, signal });
83
+ } catch (err) {
84
+ lastError = err instanceof Error ? err : new Error(String(err));
85
+ continue;
86
+ }
87
+ if (response.ok) return response;
88
+ lastError = new Error(`HTTP ${response.status} ${response.statusText} for ${url}`);
89
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
90
+ throw lastError;
91
+ }
92
+ }
93
+ throw lastError ?? new Error(`requisi\xE7\xE3o cancelada: ${url}`);
94
+ }
95
+
96
+ // src/core/text.ts
97
+ function normalize(s) {
98
+ return s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
99
+ }
100
+ function matchesQuery(haystack, query) {
101
+ const h = normalize(haystack);
102
+ return query.split(/\s+/).filter(Boolean).every((term) => h.includes(normalize(term)));
103
+ }
104
+
105
+ // src/domains/pcm-additional-info/config.ts
106
+ var pcmConfig = {
107
+ confluenceBaseUrl: "https://openfinancebrasil.atlassian.net",
108
+ interRequestDelayMs: 2e3,
109
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3],
110
+ pages: [
111
+ { pageId: "1621622796", title: "Inicia\xE7\xE3o de Pagamentos" },
112
+ { pageId: "1623162886", title: "Inicia\xE7\xE3o de Pagamentos Sem Redirecionamento" },
113
+ { pageId: "1622081540", title: "Pagamentos Autom\xE1ticos" },
114
+ { pageId: "1212153857", title: "Regras de Obrigatoriedade additionalInfo - SG" },
115
+ { pageId: "1621786627", title: "DC - C\xE2mbio" },
116
+ { pageId: "1622736899", title: "DC - Cart\xE3o de Cr\xE9dito" },
117
+ { pageId: "1622474756", title: "DC - Consentimento" },
118
+ { pageId: "1623195649", title: "DC - Contas" },
119
+ { pageId: "1622212611", title: "DC - Dados Cadastrais" },
120
+ { pageId: "1623130114", title: "DC - Investimentos" },
121
+ { pageId: "1620181433", title: "DC - Opera\xE7\xF5es de Cr\xE9dito" },
122
+ { pageId: "1623293953", title: "DC - Recursos" }
123
+ ]
124
+ };
125
+
126
+ // src/core/confluence.ts
127
+ var HEADERS = {
128
+ "User-Agent": "Mozilla/5.0 (compatible; opf-br-mcp/0.1; +opf-br-mcp)",
129
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
130
+ "Accept-Charset": "UTF-8",
131
+ "Accept-Language": "pt-BR,pt;q=0.9,en;q=0.8"
132
+ };
133
+ async function fetchConfluencePage(baseUrl, pageId, retryDelaysMs, signal) {
134
+ const apiUrl = `${baseUrl}/wiki/rest/api/content/${pageId}?expand=body.view`;
135
+ const response = await fetchWithRetry(apiUrl, { retryDelaysMs, headers: HEADERS, signal });
136
+ const json = await response.json();
137
+ return {
138
+ html: json.body?.view?.value ?? "",
139
+ title: json.title,
140
+ url: `${baseUrl}/wiki${json._links?.webui ?? ""}`
141
+ };
142
+ }
143
+
144
+ // src/domains/pcm-additional-info/parser.ts
145
+ import * as cheerio from "cheerio";
146
+ var ARRAY_FIELDS = /* @__PURE__ */ new Set([
147
+ "metodos",
148
+ "dominio",
149
+ "endpoints",
150
+ "versoes"
151
+ ]);
152
+ var DIACRITICS_REGEX = /[̀-ͯ]/g;
153
+ function toCamelCase(text2) {
154
+ return text2.normalize("NFD").replace(DIACRITICS_REGEX, "").replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/).map(
155
+ (word, i) => i === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
156
+ ).join("");
157
+ }
158
+ function normalizeHeader(raw) {
159
+ const lower = raw.trim().toLowerCase().normalize("NFD").replace(DIACRITICS_REGEX, "");
160
+ const known = {
161
+ "campo": "campo",
162
+ "definicao": "definicao",
163
+ "regra de preenchimento": "regraDePreenchimento",
164
+ "roles": "roles",
165
+ "http code": "httpCode",
166
+ "httpcode": "httpCode",
167
+ "metodos": "metodos",
168
+ "metodo": "metodos",
169
+ "dominio": "dominio",
170
+ "endpoints": "endpoints",
171
+ "endpoint": "endpoints",
172
+ "versoes": "versoes",
173
+ "versao": "versoes",
174
+ "tamanho maximo": "tamanhoMaximo",
175
+ "padrao": "padrao",
176
+ "exemplo": "exemplo"
177
+ };
178
+ if (known[lower]) return known[lower];
179
+ return toCamelCase(raw);
180
+ }
181
+ function splitArrayValue(value, key) {
182
+ if (!value.trim()) return [];
183
+ const parts = value.split(/[\n,]+/).map((p) => p.trim()).filter(Boolean);
184
+ if (key === "endpoints") {
185
+ const result = [];
186
+ for (const part of parts) {
187
+ if (part.startsWith("/")) {
188
+ result.push(part);
189
+ } else {
190
+ result.push(...part.split(/\s+/).filter((t) => t.startsWith("/")));
191
+ const nonPaths = part.split(/\s+/).filter((t) => !t.startsWith("/") && t.length > 0);
192
+ result.push(...nonPaths);
193
+ }
194
+ }
195
+ return result.filter(Boolean);
196
+ }
197
+ if (key === "metodos" || key === "dominio" || key === "versoes") {
198
+ const result = [];
199
+ for (const part of parts) {
200
+ result.push(...part.split(/\s+/).filter(Boolean));
201
+ }
202
+ return result;
203
+ }
204
+ return parts;
205
+ }
206
+ function cellText($, el) {
207
+ const clone = $(el).clone();
208
+ clone.find("br").replaceWith("\n");
209
+ return clone.text().replace(/ /g, " ").trim();
210
+ }
211
+ function parseAdditionalInfoTables(html) {
212
+ const $ = cheerio.load(html);
213
+ const fields = [];
214
+ $("table").each((_, table) => {
215
+ const rows = $(table).find("tr").toArray();
216
+ if (rows.length < 2) return;
217
+ const headerRow = rows[0];
218
+ const rawHeaders = $(headerRow).find("th, td").toArray().map((th) => cellText($, th));
219
+ if (rawHeaders.length === 0 || rawHeaders.every((h) => !h)) return;
220
+ const headers = rawHeaders.map(normalizeHeader);
221
+ if (!headers.includes("campo")) return;
222
+ for (let i = 1; i < rows.length; i++) {
223
+ const cells = $(rows[i]).find("td").toArray();
224
+ if (cells.length === 0) continue;
225
+ const field = {
226
+ campo: null,
227
+ definicao: null,
228
+ regraDePreenchimento: null,
229
+ roles: null,
230
+ httpCode: null,
231
+ metodos: [],
232
+ dominio: [],
233
+ endpoints: [],
234
+ versoes: [],
235
+ tamanhoMaximo: null,
236
+ padrao: null,
237
+ exemplo: null
238
+ };
239
+ cells.forEach((cell, colIdx) => {
240
+ const key = headers[colIdx];
241
+ if (!key) return;
242
+ const text2 = cellText($, cell);
243
+ const isEmpty = text2 === "" || text2 === "-" || text2 === "\u2014";
244
+ if (ARRAY_FIELDS.has(key)) {
245
+ field[key] = isEmpty ? [] : splitArrayValue(text2, key);
246
+ } else {
247
+ field[key] = isEmpty ? null : text2;
248
+ }
249
+ });
250
+ fields.push(field);
251
+ }
252
+ });
253
+ return fields;
254
+ }
255
+
256
+ // src/domains/pcm-additional-info/index.ts
257
+ function slugify(text2) {
258
+ return text2.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
259
+ }
260
+ function buildItems(pages) {
261
+ const items = [];
262
+ const seen = /* @__PURE__ */ new Map();
263
+ for (const page of pages) {
264
+ for (const field of page.fields) {
265
+ const base = `${page.pageId}:${slugify(String(field.campo ?? ""))}`;
266
+ const count = (seen.get(base) ?? 0) + 1;
267
+ seen.set(base, count);
268
+ const id = count > 1 ? `${base}-${count}` : base;
269
+ items.push({
270
+ ...field,
271
+ id,
272
+ page: { pageId: page.pageId, title: page.title, url: page.url }
273
+ });
274
+ }
275
+ }
276
+ return items;
277
+ }
278
+ var pcmDomain = {
279
+ id: "pcm-additional-info",
280
+ title: "PCM \u2014 Regras de obrigatoriedade do additionalInfo",
281
+ description: "Regras de preenchimento do campo additionalInfo das p\xE1ginas PCM (Plataforma de Coleta de M\xE9tricas) do Confluence do Open Finance Brasil (Inicia\xE7\xE3o de Pagamentos, Pagamentos Autom\xE1ticos, Sem Redirecionamento, DC-*). Cada item \xE9 um campo com regra, m\xE9todos, endpoints, vers\xF5es, tamanho m\xE1ximo e exemplo.",
282
+ ttlHours: 24,
283
+ filters: [
284
+ { name: "field", description: "Match exato no nome do campo (case-insensitive)" },
285
+ { name: "contains", description: "Substring em campo ou definicao" },
286
+ { name: "endpoint", description: "Substring em endpoints[] (ex.: /pix)" },
287
+ { name: "method", description: "Verbo HTTP presente em metodos[] (ex.: POST)" },
288
+ { name: "page", description: "Substring no t\xEDtulo da p\xE1gina Confluence" }
289
+ ],
290
+ async extract(ctx) {
291
+ const total = pcmConfig.pages.length;
292
+ const pages = [];
293
+ for (const [i, page] of pcmConfig.pages.entries()) {
294
+ if (ctx?.signal?.aborted) throw new Error("Extra\xE7\xE3o cancelada pelo cliente");
295
+ if (i > 0) await sleep(pcmConfig.interRequestDelayMs);
296
+ ctx?.onProgress?.(i, total, `Extraindo "${page.title}"`);
297
+ const { html, url } = await fetchConfluencePage(
298
+ pcmConfig.confluenceBaseUrl,
299
+ page.pageId,
300
+ pcmConfig.retryDelaysMs,
301
+ ctx?.signal
302
+ );
303
+ pages.push({ pageId: page.pageId, title: page.title, url, fields: parseAdditionalInfoTables(html) });
304
+ }
305
+ ctx?.onProgress?.(total, total);
306
+ return { items: buildItems(pages) };
307
+ },
308
+ search(data, query, filters = {}) {
309
+ const field = filters.field?.trim().toLowerCase();
310
+ const contains = filters.contains ? normalize(filters.contains) : void 0;
311
+ const endpoint = filters.endpoint?.toLowerCase();
312
+ const method = filters.method?.toUpperCase();
313
+ const page = filters.page ? normalize(filters.page) : void 0;
314
+ return data.items.filter((item) => {
315
+ const campo = String(item.campo ?? "");
316
+ if (page && !normalize(item.page.title).includes(page)) return false;
317
+ if (field && campo.trim().toLowerCase() !== field) return false;
318
+ if (contains && !normalize(`${campo} ${item.definicao ?? ""}`).includes(contains)) return false;
319
+ if (endpoint && !item.endpoints.some((e) => e.toLowerCase().includes(endpoint))) return false;
320
+ if (method && !item.metodos.some((m) => m.toUpperCase() === method)) return false;
321
+ if (query?.trim()) {
322
+ const haystack = `${campo} ${item.definicao ?? ""} ${item.regraDePreenchimento ?? ""}`;
323
+ if (!matchesQuery(haystack, query)) return false;
324
+ }
325
+ return true;
326
+ });
327
+ },
328
+ getItem(data, id) {
329
+ return data.items.find((i) => i.id === id) ?? null;
330
+ }
331
+ };
332
+
333
+ // src/domains/_openapi/parser.ts
334
+ import { parse } from "yaml";
335
+ var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
336
+ function parseOpenApiSpec(yamlText, specName) {
337
+ const spec = parse(yamlText);
338
+ const items = [];
339
+ for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {
340
+ for (const method of HTTP_METHODS) {
341
+ const op = pathItem[method];
342
+ if (!op) continue;
343
+ items.push({
344
+ id: `${specName}:${method.toUpperCase()} ${path}`,
345
+ type: "operation",
346
+ path,
347
+ method: method.toUpperCase(),
348
+ summary: op.summary ?? null,
349
+ description: op.description ?? null,
350
+ tags: op.tags ?? [],
351
+ detail: op
352
+ });
353
+ }
354
+ }
355
+ for (const [name, schema] of Object.entries(spec.components?.schemas ?? {})) {
356
+ items.push({
357
+ id: `${specName}:schema:${name}`,
358
+ type: "schema",
359
+ name,
360
+ description: schema.description ?? null,
361
+ required: schema.required ?? [],
362
+ detail: schema
363
+ });
364
+ }
365
+ return items;
366
+ }
367
+
368
+ // src/domains/_openapi/domain.ts
369
+ function summarize(item) {
370
+ const { detail: _detail, ...rest } = item;
371
+ return rest;
372
+ }
373
+ function createOpenApiDomain(config) {
374
+ return {
375
+ id: config.id,
376
+ title: config.title,
377
+ description: config.description,
378
+ ttlHours: 24,
379
+ filters: [
380
+ { name: "path", description: `Substring no path do endpoint (ex.: ${config.pathExample})` },
381
+ { name: "method", description: "Verbo HTTP exato (ex.: POST)" },
382
+ { name: "schema", description: "Substring no nome do schema (case-insensitive)" }
383
+ ],
384
+ async extract(ctx) {
385
+ if (ctx?.signal?.aborted) throw new Error("Extra\xE7\xE3o cancelada pelo cliente");
386
+ ctx?.onProgress?.(0, 1, `Baixando spec ${config.specName} ${config.specVersion}`);
387
+ const response = await fetchWithRetry(config.url, {
388
+ retryDelaysMs: config.retryDelaysMs,
389
+ signal: ctx?.signal
390
+ });
391
+ const yamlText = await response.text();
392
+ ctx?.onProgress?.(1, 1);
393
+ return { items: parseOpenApiSpec(yamlText, config.specName) };
394
+ },
395
+ search(data, query, filters = {}) {
396
+ const path = filters.path ? normalize(filters.path) : void 0;
397
+ const method = filters.method?.toUpperCase();
398
+ const schema = filters.schema ? normalize(filters.schema) : void 0;
399
+ return data.items.filter((item) => {
400
+ if (path && !normalize(String(item.path ?? "")).includes(path)) return false;
401
+ if (method && item.method !== method) return false;
402
+ if (schema && !normalize(String(item.name ?? "")).includes(schema)) return false;
403
+ if (query?.trim()) {
404
+ const haystack = [item.path, item.summary, item.description, item.name].map((v) => String(v ?? "")).join(" ");
405
+ if (!matchesQuery(haystack, query)) return false;
406
+ }
407
+ return true;
408
+ }).map(summarize);
409
+ },
410
+ getItem(data, id) {
411
+ return data.items.find((i) => i.id === id) ?? null;
412
+ }
413
+ };
414
+ }
415
+
416
+ // src/domains/payments-v4-openapi/config.ts
417
+ var paymentsConfig = {
418
+ id: "payments-v4-openapi",
419
+ title: "API de Pagamentos \u2014 spec OpenAPI 4.0.0",
420
+ description: "Spec OpenAPI oficial da API de Pagamentos (Pix) do Open Finance Brasil. Itens type=operation (endpoints, um por m\xE9todo+path) e type=schema (payloads). search devolve resumos; use get_item para o n\xF3 completo da spec.",
421
+ pathExample: "/pix/payments",
422
+ specName: "payments",
423
+ specVersion: "4.0.0",
424
+ url: "https://raw.githubusercontent.com/OpenBanking-Brasil/openapi/main/swagger-apis/payments/4.0.0.yml",
425
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3]
426
+ };
427
+
428
+ // src/domains/payments-v4-openapi/index.ts
429
+ var paymentsDomain = createOpenApiDomain(paymentsConfig);
430
+
431
+ // src/domains/payments-v5-openapi/config.ts
432
+ var paymentsV5Config = {
433
+ id: "payments-v5-openapi",
434
+ title: "API de Pagamentos \u2014 spec OpenAPI 5.0.0",
435
+ description: "Spec OpenAPI oficial da API de Inicia\xE7\xE3o de Pagamentos (Pix) do Open Finance Brasil, vers\xE3o 5. Inclui endpoints de consentimento (/consents) e de pagamento (/pix/payments). Itens type=operation (endpoints, um por m\xE9todo+path) e type=schema (payloads). search devolve resumos; use get_item para o n\xF3 completo da spec.",
436
+ pathExample: "/pix/payments",
437
+ specName: "payments",
438
+ specVersion: "5.0.0",
439
+ url: "https://raw.githubusercontent.com/OpenBanking-Brasil/all-services-repo/refs/heads/main/api_payment_initiation_-_open_finance_brasil/5.0.0.yaml",
440
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3]
441
+ };
442
+
443
+ // src/domains/payments-v5-openapi/index.ts
444
+ var paymentsV5Domain = createOpenApiDomain(paymentsV5Config);
445
+
446
+ // src/domains/enrollments-v2-openapi/config.ts
447
+ var enrollmentsV2Config = {
448
+ id: "enrollments-v2-openapi",
449
+ title: "API de V\xEDnculo de Dispositivo (Enrollments) \u2014 spec OpenAPI 2.2.0",
450
+ description: "Spec OpenAPI oficial da API de Enrollments (V\xEDnculo de Dispositivo) do Open Finance Brasil, vers\xE3o 2. Cobre o pagamento sem redirecionamento: v\xEDnculo de dispositivos (FIDO), autoriza\xE7\xE3o de consentimentos e Pix Autom\xE1tico. Endpoints /enrollments, /consents/{consentId}/authorise e /recurring-consents/{recurringConsentId}/authorise. Itens type=operation (endpoints, um por m\xE9todo+path) e type=schema (payloads). search devolve resumos; use get_item para o n\xF3 completo da spec.",
451
+ pathExample: "/enrollments",
452
+ specName: "enrollments",
453
+ specVersion: "2.2.0",
454
+ url: "https://raw.githubusercontent.com/OpenBanking-Brasil/all-services-repo/refs/heads/main/api_enrollments_for_payment_initiation_-_open_finance_brasil/2.2.0.yaml",
455
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3]
456
+ };
457
+
458
+ // src/domains/enrollments-v2-openapi/index.ts
459
+ var enrollmentsV2Domain = createOpenApiDomain(enrollmentsV2Config);
460
+
461
+ // src/domains/automatic-payments-v2-openapi/config.ts
462
+ var automaticPaymentsV2Config = {
463
+ id: "automatic-payments-v2-openapi",
464
+ title: "API de Pagamentos Autom\xE1ticos (Automatic Payments) \u2014 spec OpenAPI 2.2.0",
465
+ description: "Spec OpenAPI oficial da API de Automatic Payments do Open Finance Brasil, vers\xE3o 2. Cobre a inicia\xE7\xE3o de pagamentos autom\xE1ticos (Pix Autom\xE1tico e Transfer\xEAncias Inteligentes) mediante consentimento recorrente. Endpoints /recurring-consents (cria\xE7\xE3o, consulta e edi\xE7\xE3o/revoga\xE7\xE3o) e /pix/recurring-payments (cria\xE7\xE3o, retry, consulta e cancelamento); scope recurring-payments. Itens type=operation (endpoints, um por m\xE9todo+path) e type=schema (payloads). search devolve resumos; use get_item para o n\xF3 completo da spec.",
466
+ pathExample: "/pix/recurring-payments",
467
+ specName: "automatic-payments",
468
+ specVersion: "2.2.0",
469
+ url: "https://raw.githubusercontent.com/OpenBanking-Brasil/all-services-repo/refs/heads/main/api_automatic_payments_-_open_finance_brasil/2.2.0.yaml",
470
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3]
471
+ };
472
+
473
+ // src/domains/automatic-payments-v2-openapi/index.ts
474
+ var automaticPaymentsV2Domain = createOpenApiDomain(automaticPaymentsV2Config);
475
+
476
+ // src/domains/consents-v3-openapi/config.ts
477
+ var consentsV3Config = {
478
+ id: "consents-v3-openapi",
479
+ title: "API de Consentimentos \u2014 spec OpenAPI 3.3.1",
480
+ description: "Spec OpenAPI oficial da API de Consentimentos (Dados Cadastrais e Transacionais) do Open Finance Brasil, vers\xE3o 3.3.1. Cobre cria\xE7\xE3o (POST /consents), consulta (GET /consents/{consentId}), revoga\xE7\xE3o (DELETE /consents/{consentId}), renova\xE7\xE3o (POST /consents/{consentId}/extends) e hist\xF3rico de renova\xE7\xF5es (GET /consents/{consentId}/extensions). Itens type=operation (um por m\xE9todo+path) e type=schema (payloads, ex.: CreateConsent, ResponseConsent, LoggedUser, BusinessEntity). search devolve resumos; use get_item para o n\xF3 completo da spec.",
481
+ pathExample: "/consents/{consentId}",
482
+ specName: "consents",
483
+ specVersion: "3.3.1",
484
+ url: "https://openbanking-brasil.github.io/openapi/swagger-apis/consents/3.3.1.yml",
485
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3]
486
+ };
487
+
488
+ // src/domains/consents-v3-openapi/index.ts
489
+ var consentsV3Domain = createOpenApiDomain(consentsV3Config);
490
+
491
+ // src/domains/pcm-openapi/config.ts
492
+ var pcmOpenapiConfig = {
493
+ specName: "pcm",
494
+ specVersion: "1.0.0",
495
+ url: "https://raw.githubusercontent.com/OpenBanking-Brasil/pcm-specs/refs/heads/feature/correct-paths/PCM-current.openapi.yaml",
496
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3]
497
+ };
498
+
499
+ // src/domains/pcm-openapi/parser.ts
500
+ import { parse as parse2 } from "yaml";
501
+ var HTTP_METHODS2 = ["get", "post", "put", "patch", "delete"];
502
+ function parseOpenApiSpec2(yamlText, specName) {
503
+ const spec = parse2(yamlText);
504
+ const items = [];
505
+ for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {
506
+ for (const method of HTTP_METHODS2) {
507
+ const op = pathItem[method];
508
+ if (!op) continue;
509
+ items.push({
510
+ id: `${specName}:${method.toUpperCase()} ${path}`,
511
+ type: "operation",
512
+ path,
513
+ method: method.toUpperCase(),
514
+ summary: op.summary ?? null,
515
+ description: op.description ?? null,
516
+ tags: op.tags ?? [],
517
+ detail: op
518
+ });
519
+ }
520
+ }
521
+ for (const [name, schema] of Object.entries(spec.components?.schemas ?? {})) {
522
+ items.push({
523
+ id: `${specName}:schema:${name}`,
524
+ type: "schema",
525
+ name,
526
+ description: schema.description ?? null,
527
+ required: schema.required ?? [],
528
+ detail: schema
529
+ });
530
+ }
531
+ return items;
532
+ }
533
+
534
+ // src/domains/pcm-openapi/index.ts
535
+ function summarize2(item) {
536
+ const { detail: _detail, ...rest } = item;
537
+ return rest;
538
+ }
539
+ var pcmOpenapiDomain = {
540
+ id: "pcm-openapi",
541
+ title: `PCM \u2014 spec OpenAPI ${pcmOpenapiConfig.specVersion}`,
542
+ description: "Spec OpenAPI oficial da PCM (Plataforma de Coleta de M\xE9tricas) do Open Finance Brasil. Cobre os endpoints de reporte (report-api v1/v2), hybrid-flow, opendata, consents/stock, credit-portabilities, payments/status e token. Itens type=operation (um por m\xE9todo+path) e type=schema (payloads). search devolve resumos; use get_item para o n\xF3 completo da spec.",
543
+ ttlHours: 24,
544
+ filters: [
545
+ { name: "path", description: "Substring no path do endpoint (ex.: /report-api/v1/private/report)" },
546
+ { name: "method", description: "Verbo HTTP exato (ex.: POST)" },
547
+ { name: "schema", description: "Substring no nome do schema (case-insensitive)" }
548
+ ],
549
+ async extract(ctx) {
550
+ if (ctx?.signal?.aborted) throw new Error("Extra\xE7\xE3o cancelada pelo cliente");
551
+ ctx?.onProgress?.(0, 1, `Baixando spec ${pcmOpenapiConfig.specName} ${pcmOpenapiConfig.specVersion}`);
552
+ const response = await fetchWithRetry(pcmOpenapiConfig.url, {
553
+ retryDelaysMs: pcmOpenapiConfig.retryDelaysMs,
554
+ signal: ctx?.signal
555
+ });
556
+ const yamlText = await response.text();
557
+ ctx?.onProgress?.(1, 1);
558
+ return { items: parseOpenApiSpec2(yamlText, pcmOpenapiConfig.specName) };
559
+ },
560
+ search(data, query, filters = {}) {
561
+ const path = filters.path ? normalize(filters.path) : void 0;
562
+ const method = filters.method?.toUpperCase();
563
+ const schema = filters.schema ? normalize(filters.schema) : void 0;
564
+ return data.items.filter((item) => {
565
+ if (path && !normalize(String(item.path ?? "")).includes(path)) return false;
566
+ if (method && item.method !== method) return false;
567
+ if (schema && !normalize(String(item.name ?? "")).includes(schema)) return false;
568
+ if (query?.trim()) {
569
+ const haystack = [item.path, item.summary, item.description, item.name].map((v) => String(v ?? "")).join(" ");
570
+ if (!matchesQuery(haystack, query)) return false;
571
+ }
572
+ return true;
573
+ }).map(summarize2);
574
+ },
575
+ getItem(data, id) {
576
+ return data.items.find((i) => i.id === id) ?? null;
577
+ }
578
+ };
579
+
580
+ // src/domains/_confluence-sections/parser.ts
581
+ import * as cheerio2 from "cheerio";
582
+ var HEADING_SELECTOR = "h1, h2, h3";
583
+ function cellText2($, el) {
584
+ const clone = $(el).clone();
585
+ clone.find("br").replaceWith("\n");
586
+ return clone.text().replace(/ /g, " ").replace(/[ \t]+/g, " ").trim();
587
+ }
588
+ function blockText($, node) {
589
+ const $node = $(node);
590
+ if (node.tagName === "table") {
591
+ return $node.find("tr").toArray().map(
592
+ (tr) => $(tr).find("th, td").toArray().map((c) => cellText2($, c)).join(" | ")
593
+ ).filter((line) => line.replace(/[ |]/g, "").length > 0).join("\n");
594
+ }
595
+ return cellText2($, node);
596
+ }
597
+ function parseSections(html) {
598
+ const $ = cheerio2.load(html);
599
+ const sections = [];
600
+ const headings = $(HEADING_SELECTOR).toArray();
601
+ const first = headings[0];
602
+ const introNodes = first ? $(first).prevAll().toArray().reverse() : $("body").contents().toArray();
603
+ const introParts = introNodes.map((n) => blockText($, n)).filter(Boolean);
604
+ const introContent = introParts.join("\n\n").trim();
605
+ if (introContent) sections.push({ heading: "intro", level: 0, content: introContent });
606
+ for (const el of headings) {
607
+ const heading = cellText2($, el);
608
+ if (!heading) continue;
609
+ const level = Number(el.tagName.slice(1));
610
+ const parts = $(el).nextUntil(HEADING_SELECTOR).toArray().map((n) => blockText($, n)).filter(Boolean);
611
+ sections.push({ heading, level, content: parts.join("\n\n").trim() });
612
+ }
613
+ return sections;
614
+ }
615
+
616
+ // src/domains/_confluence-sections/domain.ts
617
+ var SNIPPET_LEN = 200;
618
+ function slugify2(text2) {
619
+ return text2.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
620
+ }
621
+ function buildItems2(pages) {
622
+ const items = [];
623
+ const seen = /* @__PURE__ */ new Map();
624
+ for (const page of pages) {
625
+ for (const section of page.sections) {
626
+ const base = `${page.pageId}:${slugify2(section.heading)}`;
627
+ const count = (seen.get(base) ?? 0) + 1;
628
+ seen.set(base, count);
629
+ const id = count > 1 ? `${base}-${count}` : base;
630
+ items.push({
631
+ id,
632
+ heading: section.heading,
633
+ level: section.level,
634
+ content: section.content,
635
+ page: { pageId: page.pageId, title: page.title, url: page.url }
636
+ });
637
+ }
638
+ }
639
+ return items;
640
+ }
641
+ function summarize3(item) {
642
+ const { content, ...rest } = item;
643
+ const snippet = content.length > SNIPPET_LEN ? `${content.slice(0, SNIPPET_LEN)}\u2026` : content;
644
+ return { ...rest, snippet };
645
+ }
646
+ function createConfluenceSectionsDomain(config) {
647
+ return {
648
+ id: config.id,
649
+ title: config.title,
650
+ description: config.description,
651
+ ttlHours: 24,
652
+ filters: [
653
+ { name: "page", description: "Substring no t\xEDtulo da p\xE1gina Confluence" },
654
+ { name: "heading", description: "Substring no t\xEDtulo da se\xE7\xE3o (heading)" },
655
+ { name: "contains", description: "Substring no conte\xFAdo da se\xE7\xE3o" }
656
+ ],
657
+ async extract(ctx) {
658
+ const total = config.pages.length;
659
+ const pages = [];
660
+ for (const [i, page] of config.pages.entries()) {
661
+ if (ctx?.signal?.aborted) throw new Error("Extra\xE7\xE3o cancelada pelo cliente");
662
+ if (i > 0) await sleep(config.interRequestDelayMs);
663
+ ctx?.onProgress?.(i, total, `Extraindo "${page.title}"`);
664
+ const { html, url } = await fetchConfluencePage(
665
+ config.confluenceBaseUrl,
666
+ page.pageId,
667
+ config.retryDelaysMs,
668
+ ctx?.signal
669
+ );
670
+ pages.push({ pageId: page.pageId, title: page.title, url, sections: parseSections(html) });
671
+ }
672
+ ctx?.onProgress?.(total, total);
673
+ return { items: buildItems2(pages) };
674
+ },
675
+ search(data, query, filters = {}) {
676
+ const page = filters.page ? normalize(filters.page) : void 0;
677
+ const heading = filters.heading ? normalize(filters.heading) : void 0;
678
+ const contains = filters.contains ? normalize(filters.contains) : void 0;
679
+ return data.items.filter((item) => {
680
+ if (page && !normalize(item.page.title).includes(page)) return false;
681
+ if (heading && !normalize(item.heading).includes(heading)) return false;
682
+ if (contains && !normalize(item.content).includes(contains)) return false;
683
+ if (query?.trim()) {
684
+ if (!matchesQuery(`${item.heading} ${item.content}`, query)) return false;
685
+ }
686
+ return true;
687
+ }).map(summarize3);
688
+ },
689
+ getItem(data, id) {
690
+ return data.items.find((i) => i.id === id) ?? null;
691
+ }
692
+ };
693
+ }
694
+
695
+ // src/domains/pcm-business-rules/config.ts
696
+ var pcmBusinessRulesConfig = {
697
+ id: "pcm-business-rules",
698
+ title: "PCM \u2014 Regras de neg\xF3cio (Reporte, Processamento, Diverg\xEAncias)",
699
+ description: "Regras de neg\xF3cio da PCM (Plataforma de Coleta de M\xE9tricas) do Open Finance Brasil, extra\xEDdas das p\xE1ginas Confluence: Especifica\xE7\xE3o T\xE9cnica, Reporte, Processamento, Diverg\xEAncias e Manual de Integra\xE7\xE3o. Cada item \xE9 uma se\xE7\xE3o (heading) da p\xE1gina. search devolve um snippet do conte\xFAdo; use get_item para o texto completo da se\xE7\xE3o.",
700
+ confluenceBaseUrl: "https://openfinancebrasil.atlassian.net",
701
+ interRequestDelayMs: 2e3,
702
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3],
703
+ pages: [
704
+ { pageId: "37945356", title: "Especifica\xE7\xE3o T\xE9cnica" },
705
+ { pageId: "37945368", title: "Reporte, Processamento e Diverg\xEAncias" },
706
+ { pageId: "37879861", title: "Reporte" },
707
+ { pageId: "37912631", title: "Processamento" },
708
+ { pageId: "37945515", title: "Manual de Integra\xE7\xE3o" }
709
+ ]
710
+ };
711
+
712
+ // src/domains/pcm-business-rules/index.ts
713
+ var pcmBusinessRulesDomain = createConfluenceSectionsDomain(pcmBusinessRulesConfig);
714
+
715
+ // src/domains/jornada-otimizada/config.ts
716
+ var jornadaOtimizadaConfig = {
717
+ id: "jornada-otimizada",
718
+ title: "Jornada Otimizada",
719
+ description: "Conhecimento regulat\xF3rio da Jornada Otimizada do Open Finance Brasil, extra\xEDdo das p\xE1ginas Confluence: Jornada Otimizada (introdu\xE7\xE3o), Orienta\xE7\xF5es Gerais, Transfer\xEAncias Inteligentes e Jornada sem Redirecionamento. Cada item \xE9 uma se\xE7\xE3o (heading) da p\xE1gina. search devolve um snippet do conte\xFAdo; use get_item para o texto completo da se\xE7\xE3o.",
720
+ confluenceBaseUrl: "https://openfinancebrasil.atlassian.net",
721
+ interRequestDelayMs: 2e3,
722
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3],
723
+ pages: [
724
+ { pageId: "1128890377", title: "Jornada Otimizada" },
725
+ { pageId: "1129250817", title: "Orienta\xE7\xF5es Gerais" },
726
+ { pageId: "1129021472", title: "Transfer\xEAncias Inteligentes" },
727
+ { pageId: "1128857617", title: "Jornada sem Redirecionamento" }
728
+ ]
729
+ };
730
+
731
+ // src/domains/jornada-otimizada/index.ts
732
+ var jornadaOtimizadaDomain = createConfluenceSectionsDomain(jornadaOtimizadaConfig);
733
+
734
+ // src/domains/mqd/config.ts
735
+ var mqdConfig = {
736
+ id: "mqd",
737
+ title: "Motor de Qualidade de Dados (MQD)",
738
+ description: "Conhecimento regulat\xF3rio do Motor de Qualidade de Dados (MQD) do Open Finance Brasil, extra\xEDdo das p\xE1ginas Confluence: Especifica\xE7\xE3o T\xE9cnica, Arquitetura, Documenta\xE7\xE3o da API, Manual de Instala\xE7\xE3o, Tabela de Endpoints Validados, FAQ e Troubleshooting. Cada item \xE9 uma se\xE7\xE3o (heading) de uma p\xE1gina. search devolve um snippet do conte\xFAdo; use get_item para o texto completo da se\xE7\xE3o. A spec OpenAPI do MQD est\xE1 inclu\xEDda como conte\xFAdo da se\xE7\xE3o da p\xE1gina Documenta\xE7\xE3o da API.",
739
+ confluenceBaseUrl: "https://openfinancebrasil.atlassian.net",
740
+ interRequestDelayMs: 2e3,
741
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3],
742
+ pages: [
743
+ { pageId: "362578617", title: "Especifica\xE7\xE3o T\xE9cnica" },
744
+ { pageId: "362578657", title: "Arquitetura" },
745
+ { pageId: "362578918", title: "Documenta\xE7\xE3o da API" },
746
+ { pageId: "362578967", title: "Manual de Instala\xE7\xE3o" },
747
+ { pageId: "619413971", title: "Tabela de Endpoints Validados" },
748
+ { pageId: "362579143", title: "FAQ" },
749
+ { pageId: "362579195", title: "Troubleshooting" }
750
+ ]
751
+ };
752
+
753
+ // src/domains/mqd/index.ts
754
+ var mqdDomain = createConfluenceSectionsDomain(mqdConfig);
755
+
756
+ // src/domains/webhook-v1-openapi/config.ts
757
+ var webhookConfig = {
758
+ id: "webhook-v1-openapi",
759
+ title: "API de Webhook \u2014 spec OpenAPI 1.3.0",
760
+ description: "Spec OpenAPI oficial da API de Webhook do Open Finance Brasil, que notifica mudan\xE7as de estado das demais APIs. 5 opera\xE7\xF5es POST (type=operation): consentNotification e pixPaymentNotification (Pagamentos), enrollmentIdNotification (Enrollments), recurringConsentIdNotification e recurringPaymentIdNotification (Pagamentos Autom\xE1ticos); e schemas type=schema (RequestBodyWebhook, RequestBodyWebhookEvents, EventType, Timestamp, xWebhookInteractionId). search devolve resumos; use get_item para o n\xF3 completo da spec.",
761
+ pathExample: "/payments/{versionApi}/consents/{consentId}",
762
+ specName: "webhook",
763
+ specVersion: "1.3.0",
764
+ url: "https://raw.githubusercontent.com/OpenBanking-Brasil/all-services-repo/refs/heads/main/api_webhook_-_open_finance_brasil/1.3.0.yaml",
765
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3]
766
+ };
767
+
768
+ // src/domains/webhook-v1-openapi/index.ts
769
+ var webhookDomain = createOpenApiDomain(webhookConfig);
770
+
771
+ // src/domains/seguranca/config.ts
772
+ var segurancaConfig = {
773
+ id: "seguranca",
774
+ title: "Seguran\xE7a",
775
+ description: "Conhecimento regulat\xF3rio de Seguran\xE7a do Open Finance Brasil, extra\xEDdo das p\xE1ginas Confluence sob a \xE1rvore Seguran\xE7a: Vis\xE3o Geral, Introdu\xE7\xE3o, Guia do Usu\xE1rio, Perfil de Seguran\xE7a (DCR, FAPI, CIBA, criptografia de ID_TOKEN), Padr\xE3o de Certificados e diretrizes de valida\xE7\xE3o, Assinaturas, Casos de Erro, Redirecionamento App-to-App, Gloss\xE1rio e Pol\xEDtica de Versionamento. Cada item \xE9 uma se\xE7\xE3o (heading) de uma p\xE1gina. search devolve um snippet do conte\xFAdo; use get_item para o texto completo da se\xE7\xE3o.",
776
+ confluenceBaseUrl: "https://openfinancebrasil.atlassian.net",
777
+ interRequestDelayMs: 2e3,
778
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3],
779
+ pages: [
780
+ { pageId: "240648227", title: "Vis\xE3o Geral" },
781
+ { pageId: "240648385", title: "Introdu\xE7\xE3o" },
782
+ { pageId: "240648471", title: "Guia do Usu\xE1rio" },
783
+ { pageId: "240648789", title: "Perfil de Seguran\xE7a" },
784
+ { pageId: "240649257", title: "DCR - Dynamic Client Registration" },
785
+ { pageId: "1799421990", title: "FAPI - Financial-grade API Security Profile" },
786
+ { pageId: "1799946241", title: "CIBA - Client Initiated Backchannel Authentication" },
787
+ { pageId: "240649215", title: "Requisitos de criptografia ID_TOKEN" },
788
+ { pageId: "240649813", title: "Padr\xE3o de Certificados" },
789
+ { pageId: "1799946375", title: "Diretrizes para valida\xE7\xE3o de certificados digitais" },
790
+ { pageId: "240650189", title: "Assinaturas" },
791
+ { pageId: "240650255", title: "Casos de Erro" },
792
+ { pageId: "240650317", title: "Redirecionamento App-to-App" },
793
+ { pageId: "240650381", title: "Gloss\xE1rio de Seguran\xE7a" },
794
+ { pageId: "240650571", title: "Versionamento - Tipos" },
795
+ { pageId: "240650601", title: "Versionamento - Ciclo de Vida" },
796
+ { pageId: "240650643", title: "Versionamento - Fluxo de especifica\xE7\xE3o" },
797
+ { pageId: "240650712", title: "Versionamento - Change log" }
798
+ ]
799
+ };
800
+
801
+ // src/domains/seguranca/index.ts
802
+ var segurancaDomain = createConfluenceSectionsDomain(segurancaConfig);
803
+
804
+ // src/domains/participantes/config.ts
805
+ var participantesConfig = {
806
+ id: "participantes",
807
+ title: "Diret\xF3rio de Participantes",
808
+ description: "Diret\xF3rio p\xFAblico de participantes do Open Finance Brasil: organiza\xE7\xF5es, marcas (authorisation servers) e fam\xEDlias de API suportadas com vers\xF5es. Um item por organiza\xE7\xE3o. search devolve resumo (nome, CNPJ, status, fam\xEDlias de API); use get_item para o n\xF3 completo com AuthorisationServers, endpoints e certifica\xE7\xF5es.",
809
+ url: "https://data.directory.openbankingbrasil.org.br/participants",
810
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3]
811
+ };
812
+
813
+ // src/domains/participantes/parser.ts
814
+ function parseParticipants(jsonText) {
815
+ const orgs = JSON.parse(jsonText);
816
+ return orgs.map((org) => {
817
+ const servers = org.AuthorisationServers ?? [];
818
+ const families = /* @__PURE__ */ new Set();
819
+ for (const server2 of servers) {
820
+ for (const resource of server2.ApiResources ?? []) {
821
+ if (!resource.ApiFamilyType) continue;
822
+ families.add(
823
+ resource.ApiVersion ? `${resource.ApiFamilyType} ${resource.ApiVersion}` : resource.ApiFamilyType
824
+ );
825
+ }
826
+ }
827
+ return {
828
+ id: org.OrganisationId,
829
+ name: org.OrganisationName ?? "",
830
+ cnpj: org.RegistrationNumber ?? null,
831
+ status: org.Status ?? null,
832
+ servers: servers.length,
833
+ serverNames: servers.map((s) => s.CustomerFriendlyName ?? "").filter(Boolean),
834
+ apiFamilies: [...families].sort(),
835
+ detail: org
836
+ };
837
+ });
838
+ }
839
+
840
+ // src/domains/participantes/index.ts
841
+ function summarize4(item) {
842
+ const { detail: _detail, ...rest } = item;
843
+ return rest;
844
+ }
845
+ var participantesDomain = {
846
+ id: participantesConfig.id,
847
+ title: participantesConfig.title,
848
+ description: participantesConfig.description,
849
+ ttlHours: 24,
850
+ filters: [
851
+ { name: "name", description: "Substring no nome da organiza\xE7\xE3o ou na marca (CustomerFriendlyName)" },
852
+ { name: "api", description: "Substring na fam\xEDlia de API (ex.: payments, automatic-payments)" },
853
+ { name: "status", description: "Status exato da organiza\xE7\xE3o (ex.: Active)" },
854
+ { name: "cnpj", description: "Substring no CNPJ (RegistrationNumber, s\xF3 d\xEDgitos)" }
855
+ ],
856
+ async extract(ctx) {
857
+ if (ctx?.signal?.aborted) throw new Error("Extra\xE7\xE3o cancelada pelo cliente");
858
+ ctx?.onProgress?.(0, 1, "Baixando diret\xF3rio de participantes");
859
+ const response = await fetchWithRetry(participantesConfig.url, {
860
+ retryDelaysMs: participantesConfig.retryDelaysMs,
861
+ signal: ctx?.signal
862
+ });
863
+ const jsonText = await response.text();
864
+ ctx?.onProgress?.(1, 1);
865
+ return { items: parseParticipants(jsonText) };
866
+ },
867
+ search(data, query, filters = {}) {
868
+ const name = filters.name ? normalize(filters.name) : void 0;
869
+ const api = filters.api ? normalize(filters.api) : void 0;
870
+ const status = filters.status ? normalize(filters.status) : void 0;
871
+ const cnpj = filters.cnpj?.replace(/\D/g, "");
872
+ return data.items.filter((item) => {
873
+ if (name && !normalize([item.name, ...item.serverNames].join(" ")).includes(name)) return false;
874
+ if (api && !item.apiFamilies.some((f) => normalize(f).includes(api))) return false;
875
+ if (status && normalize(item.status ?? "") !== status) return false;
876
+ if (cnpj && !(item.cnpj ?? "").includes(cnpj)) return false;
877
+ if (query?.trim()) {
878
+ const haystack = [item.name, ...item.serverNames, ...item.apiFamilies, item.cnpj ?? ""].join(" ");
879
+ if (!matchesQuery(haystack, query)) return false;
880
+ }
881
+ return true;
882
+ }).map(summarize4);
883
+ },
884
+ getItem(data, id) {
885
+ return data.items.find((i) => i.id === id) ?? null;
886
+ }
887
+ };
888
+
889
+ // src/domains/portal/config.ts
890
+ var portalConfig = {
891
+ id: "portal",
892
+ title: "Portal do Desenvolvedor (busca ao vivo)",
893
+ description: "Busca ao vivo em todo o Portal do Desenvolvedor do Open Finance Brasil (Confluence, espa\xE7o OF). Use quando os dom\xEDnios espec\xEDficos n\xE3o cobrirem o assunto. search exige query e consulta a fonte a cada chamada (sempre atualizado, sem cache); get_item baixa a p\xE1gina inteira estruturada em se\xE7\xF5es. Retorna no m\xE1ximo os 25 resultados mais relevantes; matches nunca excede 25.",
894
+ confluenceBaseUrl: "https://openfinancebrasil.atlassian.net",
895
+ space: "OF",
896
+ searchLimit: 25,
897
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3]
898
+ };
899
+
900
+ // src/domains/portal/parser.ts
901
+ function buildCql(query, space) {
902
+ const escaped = query.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
903
+ return `siteSearch ~ "${escaped}" AND type = page AND space = "${space}"`;
904
+ }
905
+ function parseSearchResults(json, baseUrl) {
906
+ const results = json.results ?? [];
907
+ return results.filter((r) => r.content?.id).map((r) => ({
908
+ id: String(r.content.id),
909
+ title: r.content?.title ?? r.title ?? "",
910
+ excerpt: (r.excerpt ?? "").replace(/@@@(end)?hl@@@/g, ""),
911
+ url: r.url ? `${baseUrl}/wiki${r.url}` : null,
912
+ lastModified: r.lastModified ?? null
913
+ }));
914
+ }
915
+
916
+ // src/domains/portal/index.ts
917
+ var portalDomain = {
918
+ id: portalConfig.id,
919
+ title: portalConfig.title,
920
+ description: portalConfig.description,
921
+ filters: [],
922
+ live: {
923
+ async search(query, _filters, ctx) {
924
+ const cql = buildCql(query, portalConfig.space);
925
+ const url = `${portalConfig.confluenceBaseUrl}/wiki/rest/api/search?cql=${encodeURIComponent(cql)}&limit=${portalConfig.searchLimit}`;
926
+ const response = await fetchWithRetry(url, {
927
+ retryDelaysMs: portalConfig.retryDelaysMs,
928
+ signal: ctx?.signal
929
+ });
930
+ return parseSearchResults(await response.json(), portalConfig.confluenceBaseUrl);
931
+ },
932
+ async getItem(id, ctx) {
933
+ if (!/^\d+$/.test(id)) return null;
934
+ try {
935
+ const { html, title, url } = await fetchConfluencePage(
936
+ portalConfig.confluenceBaseUrl,
937
+ id,
938
+ portalConfig.retryDelaysMs,
939
+ ctx?.signal
940
+ );
941
+ return { id, title: title ?? null, url, sections: parseSections(html) };
942
+ } catch (err) {
943
+ if (err.message.includes("HTTP 404")) return null;
944
+ throw err;
945
+ }
946
+ }
947
+ }
948
+ };
949
+
950
+ // src/core/registry.ts
951
+ var domains = [
952
+ pcmDomain,
953
+ paymentsDomain,
954
+ paymentsV5Domain,
955
+ enrollmentsV2Domain,
956
+ automaticPaymentsV2Domain,
957
+ consentsV3Domain,
958
+ pcmOpenapiDomain,
959
+ pcmBusinessRulesDomain,
960
+ jornadaOtimizadaDomain,
961
+ mqdDomain,
962
+ webhookDomain,
963
+ segurancaDomain,
964
+ participantesDomain,
965
+ portalDomain
966
+ ];
967
+
968
+ // src/core/server.ts
969
+ function compact(item) {
970
+ const out = {};
971
+ for (const [k, v] of Object.entries(item)) {
972
+ if (v === null) continue;
973
+ if (Array.isArray(v) && v.length === 0) continue;
974
+ out[k] = v;
975
+ }
976
+ return out;
977
+ }
978
+ function text(obj) {
979
+ return { content: [{ type: "text", text: JSON.stringify(obj, null, 1) }] };
980
+ }
981
+ function errorText(message) {
982
+ return { content: [{ type: "text", text: message }], isError: true };
983
+ }
984
+ function findDomain(id) {
985
+ return domains.find((d) => d.id === id);
986
+ }
987
+ var validIds = () => domains.map((d) => d.id).join(", ");
988
+ var domainIdSchema = z.enum(domains.map((d) => d.id)).describe("Id do dom\xEDnio (ver list_domains)");
989
+ function extractContext(extra) {
990
+ const progressToken = extra._meta?.progressToken;
991
+ return {
992
+ signal: extra.signal,
993
+ onProgress: progressToken === void 0 ? void 0 : (progress, total, message) => {
994
+ extra.sendNotification({
995
+ method: "notifications/progress",
996
+ params: { progressToken, progress, total, message }
997
+ }).catch(() => {
998
+ });
999
+ }
1000
+ };
1001
+ }
1002
+ function createServer() {
1003
+ const server2 = new McpServer(
1004
+ { name: "opf-br-mcp", version: PACKAGE_VERSION },
1005
+ {
1006
+ instructions: "Conhecimento regulat\xF3rio do Open Finance Brasil. Fluxo: list_domains para descobrir dom\xEDnios e filtros \u2192 search(domain, ...) para buscar \u2192 get_item(domain, id) para o registro completo. Os ids n\xE3o s\xE3o adivinh\xE1veis \u2014 sempre venha de search. A primeira consulta a um dom\xEDnio extrai das fontes p\xFAblicas e pode levar ~30s; as seguintes usam cache."
1007
+ }
1008
+ );
1009
+ server2.registerTool(
1010
+ "list_domains",
1011
+ {
1012
+ title: "Listar dom\xEDnios",
1013
+ description: "Lista os dom\xEDnios de conhecimento do Open Finance Brasil dispon\xEDveis neste server, com os filtros aceitos por cada um e o estado do cache local. Comece por aqui; depois use search(domain, ...) e get_item(domain, id).",
1014
+ annotations: {
1015
+ readOnlyHint: true,
1016
+ destructiveHint: false,
1017
+ idempotentHint: true,
1018
+ openWorldHint: false
1019
+ }
1020
+ },
1021
+ async () => {
1022
+ const out = domains.map((d) => {
1023
+ if (d.live) {
1024
+ return { id: d.id, title: d.title, description: d.description, filters: d.filters, live: true };
1025
+ }
1026
+ const cached = readCache(d.id);
1027
+ return {
1028
+ id: d.id,
1029
+ title: d.title,
1030
+ description: d.description,
1031
+ filters: d.filters,
1032
+ cachedItems: cached?.data.items.length ?? 0,
1033
+ extractedAt: cached?.extractedAt ?? null
1034
+ };
1035
+ });
1036
+ return text(out);
1037
+ }
1038
+ );
1039
+ server2.registerTool(
1040
+ "search",
1041
+ {
1042
+ title: "Buscar em um dom\xEDnio",
1043
+ description: "Busca filtrada em um dom\xEDnio. `filters` aceita as chaves listadas em list_domains para o dom\xEDnio (combinadas em AND); `query` busca substring nos campos textuais. Retorno compacto (omite nulls). Cada resultado tem `id` para usar em get_item. Na primeira consulta o dom\xEDnio \xE9 extra\xEDdo das fontes p\xFAblicas (pode levar ~30s).",
1044
+ inputSchema: {
1045
+ domain: domainIdSchema,
1046
+ query: z.string().optional().describe("Substring em campos textuais"),
1047
+ filters: z.record(z.string()).optional().describe("Filtros espec\xEDficos do dom\xEDnio"),
1048
+ limit: z.number().int().min(1).max(100).default(20).describe("M\xE1x. de resultados (1-100, default 20)"),
1049
+ offset: z.number().int().min(0).default(0).describe("Pula os N primeiros resultados (pagina\xE7\xE3o com limit)")
1050
+ },
1051
+ annotations: {
1052
+ readOnlyHint: true,
1053
+ destructiveHint: false,
1054
+ idempotentHint: true,
1055
+ openWorldHint: true
1056
+ }
1057
+ },
1058
+ async ({ domain, query, filters, limit, offset }, extra) => {
1059
+ const d = findDomain(domain);
1060
+ if (!d) return errorText(`Dom\xEDnio desconhecido: "${domain}". V\xE1lidos: ${validIds()}`);
1061
+ if (filters) {
1062
+ const valid = new Set(d.filters.map((f) => f.name));
1063
+ const unknown = Object.keys(filters).filter((k) => !valid.has(k));
1064
+ if (unknown.length > 0) {
1065
+ return errorText(
1066
+ `Filtros inv\xE1lidos para ${domain}: ${unknown.join(", ")}. V\xE1lidos: ${[...valid].join(", ")}`
1067
+ );
1068
+ }
1069
+ }
1070
+ const max = limit ?? 20;
1071
+ const off = offset ?? 0;
1072
+ if (d.live) {
1073
+ if (!query?.trim()) {
1074
+ return errorText(`O dom\xEDnio ${domain} \xE9 busca ao vivo: informe \`query\`.`);
1075
+ }
1076
+ try {
1077
+ const results = await d.live.search(query, filters, extractContext(extra));
1078
+ const page = results.slice(off, off + max);
1079
+ return text({ matches: results.length, returned: page.length, results: page.map(compact) });
1080
+ } catch (err) {
1081
+ return errorText(
1082
+ `Falha na busca ao vivo em ${domain}: ${err.message}. Tente novamente (dom\xEDnios ao vivo n\xE3o t\xEAm cache; refresh n\xE3o se aplica).`
1083
+ );
1084
+ }
1085
+ }
1086
+ try {
1087
+ const { data, stale, extractedAt } = await getDomainData(d, false, extractContext(extra));
1088
+ const results = d.search(data, query, filters);
1089
+ const page = results.slice(off, off + max);
1090
+ return text({
1091
+ matches: results.length,
1092
+ returned: page.length,
1093
+ ...stale ? { stale: true, staleNote: `Fontes inacess\xEDveis; usando cache de ${extractedAt}` } : {},
1094
+ ...results.length === 0 ? {
1095
+ hint: 'Sem resultados; tente search(domain: "portal", query: ...) para buscar ao vivo em todo o Portal do Desenvolvedor.'
1096
+ } : {},
1097
+ results: page.map(compact)
1098
+ });
1099
+ } catch (err) {
1100
+ return errorText(
1101
+ `Falha ao obter dados de ${domain}: ${err.message}. Verifique a conex\xE3o com a internet e tente novamente (ou use a tool refresh).`
1102
+ );
1103
+ }
1104
+ }
1105
+ );
1106
+ server2.registerTool(
1107
+ "get_item",
1108
+ {
1109
+ title: "Detalhar um item",
1110
+ description: "Devolve o registro completo de um item pelo `id` retornado por search (nos dom\xEDnios *-openapi e participantes inclui o n\xF3 integral da spec em `detail`).",
1111
+ inputSchema: {
1112
+ domain: domainIdSchema,
1113
+ id: z.string().describe("Id do item (vindo de search)")
1114
+ },
1115
+ annotations: {
1116
+ readOnlyHint: true,
1117
+ destructiveHint: false,
1118
+ idempotentHint: true,
1119
+ openWorldHint: true
1120
+ }
1121
+ },
1122
+ async ({ domain, id }, extra) => {
1123
+ const d = findDomain(domain);
1124
+ if (!d) return errorText(`Dom\xEDnio desconhecido: "${domain}". V\xE1lidos: ${validIds()}`);
1125
+ if (d.live) {
1126
+ try {
1127
+ const item = await d.live.getItem(id, extractContext(extra));
1128
+ if (!item) {
1129
+ return errorText(`Item n\xE3o encontrado em ${domain}: "${id}". Use search para descobrir ids.`);
1130
+ }
1131
+ return text(item);
1132
+ } catch (err) {
1133
+ return errorText(`Falha ao obter dados de ${domain}: ${err.message}.`);
1134
+ }
1135
+ }
1136
+ try {
1137
+ const { data, stale, extractedAt } = await getDomainData(d, false, extractContext(extra));
1138
+ const item = d.getItem(data, id);
1139
+ if (!item) {
1140
+ return errorText(`Item n\xE3o encontrado em ${domain}: "${id}". Use search para descobrir ids.`);
1141
+ }
1142
+ return text(stale ? { stale: true, staleNote: `cache de ${extractedAt}`, item } : item);
1143
+ } catch (err) {
1144
+ return errorText(`Falha ao obter dados de ${domain}: ${err.message}.`);
1145
+ }
1146
+ }
1147
+ );
1148
+ server2.registerTool(
1149
+ "refresh",
1150
+ {
1151
+ title: "Re-extrair fontes",
1152
+ description: "For\xE7a re-extra\xE7\xE3o das fontes p\xFAblicas (ignora o TTL de 24h do cache). Sem `domain`, atualiza todos. Use quando suspeitar de dados desatualizados.",
1153
+ inputSchema: {
1154
+ domain: domainIdSchema.optional().describe("Id do dom\xEDnio; omita para todos")
1155
+ },
1156
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }
1157
+ },
1158
+ async ({ domain }, extra) => {
1159
+ if (domain) {
1160
+ const d = findDomain(domain);
1161
+ if (!d) return errorText(`Dom\xEDnio desconhecido: "${domain}". V\xE1lidos: ${validIds()}`);
1162
+ if (d.live) return errorText(`O dom\xEDnio ${domain} \xE9 busca ao vivo: n\xE3o h\xE1 cache para re-extrair.`);
1163
+ }
1164
+ const targets = domains.filter(
1165
+ (d) => !d.live && (!domain || d.id === domain)
1166
+ );
1167
+ const report = {};
1168
+ for (const d of targets) {
1169
+ try {
1170
+ const { data } = await getDomainData(d, true, extractContext(extra));
1171
+ report[d.id] = `ok: ${data.items.length} itens`;
1172
+ } catch (err) {
1173
+ report[d.id] = `erro: ${err.message}`;
1174
+ }
1175
+ }
1176
+ return text(report);
1177
+ }
1178
+ );
1179
+ return server2;
1180
+ }
1181
+
1182
+ // src/index.ts
1183
+ var server = createServer();
1184
+ await server.connect(new StdioServerTransport());
1185
+ console.error("[opf-br-mcp] server pronto (stdio)");
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "opf-br-mcp",
3
+ "version": "0.4.0",
4
+ "description": "MCP server local com as regras do Open Finance Brasil (PCM additionalInfo, specs OpenAPI de Pagamentos)",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "jrogeriosilva",
8
+ "keywords": [
9
+ "mcp",
10
+ "model-context-protocol",
11
+ "open-finance",
12
+ "open-banking",
13
+ "open-finance-brasil",
14
+ "openapi",
15
+ "pcm",
16
+ "pix"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/jrogeriosilva/opf-br-mcp.git"
21
+ },
22
+ "homepage": "https://github.com/jrogeriosilva/opf-br-mcp#readme",
23
+ "bugs": {
24
+ "url": "https://github.com/jrogeriosilva/opf-br-mcp/issues"
25
+ },
26
+ "bin": {
27
+ "opf-br-mcp": "dist/index.js"
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "engines": {
33
+ "node": ">=20.0.0"
34
+ },
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "test": "vitest run",
38
+ "typecheck": "tsc --noEmit"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.17.0",
42
+ "cheerio": "^1.0.0",
43
+ "yaml": "^2.5.0",
44
+ "zod": "^3.25.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^26.1.0",
48
+ "domhandler": "^6.0.1",
49
+ "tsup": "^8.0.0",
50
+ "typescript": "^5.5.0",
51
+ "vitest": "^3.0.0"
52
+ }
53
+ }