opf-br-mcp 0.7.0 → 0.9.3

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 (3) hide show
  1. package/README.md +26 -3
  2. package/dist/index.js +204 -42
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -21,6 +21,10 @@ acesso token-eficiente às regras do Open Finance Brasil.
21
21
  | `payments-common-rules` | Confluence público OFB (Serviços - SV) | Conteúdo comum aos produtos de Iniciação de Pagamentos (atores, Idempotência, Como Assinar o Payload, Convenções de data/fuso, Polling) — item por seção |
22
22
  | `payments-implementation-guides` | Confluence público OFB (Serviços - SV) | Guias de Implementação (Pix Automático, Agendamento Recorrente, Transferências Inteligentes, Liquidação de QR Codes) — item por seção |
23
23
  | `consents-v3-openapi` | GitHub Pages openbanking-brasil.github.io | Spec OpenAPI 3.3.1 da API de Consentimentos (Dados Cadastrais e Transacionais) |
24
+ | `resources-v3-openapi` | GitHub Pages openbanking-brasil.github.io | Spec OpenAPI 3.1.0 da API de Recursos (`GET /resources`: status dos recursos compartilhados) |
25
+ | `resources-v3-business-rules` | Confluence público OFB (Dados - DC) | Regras de negócio da API de Recursos 3.1.0 (Informações Gerais, Orientações) — item por seção |
26
+ | `accounts-v2-openapi` | GitHub Pages openbanking-brasil.github.io | Spec OpenAPI 2.4.2 da API de Contas (listagem, saldos, transações, limites de cheque especial) |
27
+ | `accounts-v2-business-rules` | Confluence público OFB (Dados - DC) | Regras de negócio da API de Contas 2.4.2 (Informações Gerais, Orientações — contraparte/IN BCB nº 371) — item por seção |
24
28
  | `pcm-openapi` | GitHub OpenBanking-Brasil/pcm-specs | Spec OpenAPI da PCM (reportes, hybrid-flow, opendata, consents/stock, credit-portabilities, payments/status) |
25
29
  | `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 |
26
30
  | `jornada-otimizada` | Confluence público OFB | Regras da Jornada Otimizada (Orientações Gerais, Transferências Inteligentes, Jornada sem Redirecionamento) — item por seção |
@@ -32,13 +36,27 @@ acesso token-eficiente às regras do Open Finance Brasil.
32
36
 
33
37
  ## Tools
34
38
 
35
- - `list_domains()` — descoberta: domínios, filtros e estado do cache
39
+ - `list_domains()` — descoberta: domínios, filtros, versão da spec de origem e estado do cache
36
40
  - `search(domain, query?, filters?, limit?, offset?)` — busca filtrada, retorno compacto
37
41
  - `get_item(domain, id)` — registro completo
38
- - `refresh(domain?)` — força re-extração das fontes
42
+ - `refresh(domain?)` — força re-extração das fontes. Prefira passar `domain`:
43
+ sem ele o server atualiza o que couber em 45s (o timeout padrão do cliente MCP
44
+ é 60s) e devolve o restante em `pendentes`, para o agente retomar um a um
39
45
 
40
46
  Fluxo recomendado para o agente: `list_domains` → `search` → `get_item`.
41
47
 
48
+ ### Qual versão estou usando?
49
+
50
+ `list_domains` devolve `server: { name, version }` junto do catálogo, então o
51
+ agente descobre a versão do server na mesma chamada com que descobre os domínios.
52
+ Cada domínio que embrulha uma spec traz também o seu `specVersion`.
53
+
54
+ Pela linha de comando:
55
+
56
+ ```bash
57
+ npx opf-br-mcp --version
58
+ ```
59
+
42
60
  Domínios marcados como `live` (ex.: `portal`) consultam a fonte a cada chamada:
43
61
  não têm cache nem `refresh`, e `search` exige `query`. Quando um `search` em
44
62
  domínio comum retorna 0 resultados, a resposta inclui um `hint` sugerindo o
@@ -51,7 +69,12 @@ bem na janela de contexto de um agente, e despejá-la desperdiça tokens. A solu
51
69
  é **revelação progressiva** — o agente nunca recebe a spec completa de uma vez,
52
70
  apenas o mínimo necessário em cada etapa do funil:
53
71
 
54
- 1. **`list_domains`** — catálogo barato: quais domínios e filtros existem.
72
+ 1. **`list_domains`** — catálogo barato: quais domínios e filtros existem. Os
73
+ filtros idênticos a uma família inteira de domínios (os 8 `*-openapi`, os 11
74
+ de seções do Confluence) saem uma única vez em `filterSets`; cada domínio
75
+ traz `filterSet` e só lista inline o que é próprio dele (em `*-openapi`,
76
+ apenas `path`, cujo exemplo varia por API). Os filtros aceitos por um domínio
77
+ são a união dos dois.
55
78
  2. **`search`** — índice **pesquisável e resumido**. Cada resultado traz só os
56
79
  campos leves (`id`, `type`, `path`, `method`, `summary`/`name`, `required`,
57
80
  `in`); o nó pesado da spec (`detail`) e a lista `refs` são removidos do
package/dist/index.js CHANGED
@@ -18,14 +18,24 @@ function cacheDir() {
18
18
  function cachePath(domainId) {
19
19
  return join(cacheDir(), `${domainId}.json`);
20
20
  }
21
+ function isCacheEntry(value) {
22
+ const entry = value;
23
+ return typeof entry === "object" && entry !== null && typeof entry.extractedAt === "string" && typeof entry.packageVersion === "string" && typeof entry.data === "object" && entry.data !== null && Array.isArray(entry.data.items);
24
+ }
21
25
  function readCache(domainId) {
22
26
  const path = cachePath(domainId);
23
27
  if (!existsSync(path)) return null;
28
+ let parsed;
24
29
  try {
25
- return JSON.parse(readFileSync(path, "utf8"));
30
+ parsed = JSON.parse(readFileSync(path, "utf8"));
26
31
  } catch {
27
32
  return null;
28
33
  }
34
+ if (!isCacheEntry(parsed)) {
35
+ console.error(`[opf-br-mcp] cache de ${domainId} tem forma inesperada; ignorando (${path})`);
36
+ return null;
37
+ }
38
+ return parsed;
29
39
  }
30
40
  function writeCache(domainId, data, packageVersion) {
31
41
  const entry = {
@@ -46,7 +56,7 @@ function isFresh(entry, ttlHours, now = /* @__PURE__ */ new Date()) {
46
56
  }
47
57
 
48
58
  // src/core/version.ts
49
- var PACKAGE_VERSION = "0.7.0";
59
+ var PACKAGE_VERSION = "0.9.3";
50
60
 
51
61
  // src/core/data.ts
52
62
  async function getDomainData(domain, force = false, ctx) {
@@ -66,15 +76,52 @@ async function getDomainData(domain, force = false, ctx) {
66
76
  }
67
77
  }
68
78
 
79
+ // src/core/filter-sets.ts
80
+ var FILTER_SETS = {
81
+ openapi: [
82
+ { name: "method", description: "Verbo HTTP exato (ex.: POST)" },
83
+ { name: "schema", description: "Substring no nome do schema (case-insensitive)" },
84
+ {
85
+ name: "response",
86
+ description: "Substring no nome do components.responses, a chave usada no $ref (ex.: 202Webhook)"
87
+ },
88
+ {
89
+ name: "parameter",
90
+ description: "Substring no nome do components.parameters, a chave usada no $ref (ex.: xFapiInteractionId)"
91
+ },
92
+ {
93
+ name: "header",
94
+ description: "Substring no nome do components.headers, a chave usada no $ref (ex.: X-V)"
95
+ }
96
+ ],
97
+ sections: [
98
+ { name: "page", description: "Substring no t\xEDtulo da p\xE1gina Confluence" },
99
+ { name: "heading", description: "Substring no t\xEDtulo da se\xE7\xE3o (heading)" },
100
+ { name: "contains", description: "Substring no conte\xFAdo da se\xE7\xE3o" }
101
+ ]
102
+ };
103
+
69
104
  // src/core/http.ts
70
- function sleep(ms) {
71
- return new Promise((resolve) => setTimeout(resolve, ms));
105
+ function sleep(ms, signal) {
106
+ if (signal?.aborted) return Promise.resolve();
107
+ return new Promise((resolve) => {
108
+ const done = () => {
109
+ clearTimeout(timer);
110
+ signal?.removeEventListener("abort", done);
111
+ resolve();
112
+ };
113
+ const timer = setTimeout(done, ms);
114
+ signal?.addEventListener("abort", done, { once: true });
115
+ });
72
116
  }
73
117
  async function fetchWithRetry(url, opts) {
74
118
  let lastError;
75
119
  for (let attempt = 0; attempt <= opts.retryDelaysMs.length; attempt++) {
76
120
  if (opts.signal?.aborted) break;
77
- if (attempt > 0) await sleep(opts.retryDelaysMs[attempt - 1]);
121
+ if (attempt > 0) {
122
+ await sleep(opts.retryDelaysMs[attempt - 1], opts.signal);
123
+ if (opts.signal?.aborted) break;
124
+ }
78
125
  const timeout = AbortSignal.timeout(3e4);
79
126
  const signal = opts.signal ? AbortSignal.any([opts.signal, timeout]) : timeout;
80
127
  let response;
@@ -134,8 +181,14 @@ async function fetchConfluencePage(baseUrl, pageId, retryDelaysMs, signal) {
134
181
  const apiUrl = `${baseUrl}/wiki/rest/api/content/${pageId}?expand=body.view`;
135
182
  const response = await fetchWithRetry(apiUrl, { retryDelaysMs, headers: HEADERS, signal });
136
183
  const json = await response.json();
184
+ const html = json?.body?.view?.value;
185
+ if (typeof html !== "string") {
186
+ throw new Error(
187
+ `resposta da p\xE1gina ${pageId} n\xE3o traz body.view.value; n\xE3o \xE9 conte\xFAdo Confluence expandido`
188
+ );
189
+ }
137
190
  return {
138
- html: json.body?.view?.value ?? "",
191
+ html,
139
192
  title: json.title,
140
193
  url: `${baseUrl}/wiki${json._links?.webui ?? ""}`
141
194
  };
@@ -310,8 +363,8 @@ var pcmDomain = {
310
363
  const total = pcmConfig.pages.length;
311
364
  const pages = [];
312
365
  for (const [i, page] of pcmConfig.pages.entries()) {
366
+ if (i > 0) await sleep(pcmConfig.interRequestDelayMs, ctx?.signal);
313
367
  if (ctx?.signal?.aborted) throw new Error("Extra\xE7\xE3o cancelada pelo cliente");
314
- if (i > 0) await sleep(pcmConfig.interRequestDelayMs);
315
368
  ctx?.onProgress?.(i, total, `Extraindo "${page.title}"`);
316
369
  const { html, url } = await fetchConfluencePage(
317
370
  pcmConfig.confluenceBaseUrl,
@@ -380,7 +433,13 @@ function refsOf(node, specName) {
380
433
  return [...out];
381
434
  }
382
435
  function parseOpenApiSpec(yamlText, specName) {
383
- const spec = parse(yamlText);
436
+ const parsed = parse(yamlText);
437
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed) || !("paths" in parsed) && !("components" in parsed)) {
438
+ throw new Error(
439
+ `resposta de ${specName} n\xE3o \xE9 uma spec OpenAPI (sem "paths" nem "components"); in\xEDcio do conte\xFAdo: ${JSON.stringify(yamlText.slice(0, 80))}`
440
+ );
441
+ }
442
+ const spec = parsed;
384
443
  const items = [];
385
444
  for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {
386
445
  for (const method of HTTP_METHODS) {
@@ -433,23 +492,12 @@ function createOpenApiDomain(config) {
433
492
  id: config.id,
434
493
  title: config.title,
435
494
  description: config.description,
495
+ specVersion: config.specVersion,
436
496
  ttlHours: 72,
497
+ filterSet: "openapi",
437
498
  filters: [
438
499
  { name: "path", description: `Substring no path do endpoint (ex.: ${config.pathExample})` },
439
- { name: "method", description: "Verbo HTTP exato (ex.: POST)" },
440
- { name: "schema", description: "Substring no nome do schema (case-insensitive)" },
441
- {
442
- name: "response",
443
- description: "Substring no nome do components.responses, a chave usada no $ref (ex.: 202Webhook)"
444
- },
445
- {
446
- name: "parameter",
447
- description: "Substring no nome do components.parameters, a chave usada no $ref (ex.: xFapiInteractionId)"
448
- },
449
- {
450
- name: "header",
451
- description: "Substring no nome do components.headers, a chave usada no $ref (ex.: X-V)"
452
- }
500
+ ...FILTER_SETS.openapi
453
501
  ],
454
502
  async extract(ctx) {
455
503
  if (ctx?.signal?.aborted) throw new Error("Extra\xE7\xE3o cancelada pelo cliente");
@@ -589,18 +637,16 @@ function createConfluenceSectionsDomain(config) {
589
637
  id: config.id,
590
638
  title: config.title,
591
639
  description: config.description,
640
+ specVersion: config.specVersion,
592
641
  ttlHours: 72,
593
- filters: [
594
- { name: "page", description: "Substring no t\xEDtulo da p\xE1gina Confluence" },
595
- { name: "heading", description: "Substring no t\xEDtulo da se\xE7\xE3o (heading)" },
596
- { name: "contains", description: "Substring no conte\xFAdo da se\xE7\xE3o" }
597
- ],
642
+ filterSet: "sections",
643
+ filters: [...FILTER_SETS.sections],
598
644
  async extract(ctx) {
599
645
  const total = config.pages.length;
600
646
  const pages = [];
601
647
  for (const [i, page] of config.pages.entries()) {
648
+ if (i > 0) await sleep(config.interRequestDelayMs, ctx?.signal);
602
649
  if (ctx?.signal?.aborted) throw new Error("Extra\xE7\xE3o cancelada pelo cliente");
603
- if (i > 0) await sleep(config.interRequestDelayMs);
604
650
  ctx?.onProgress?.(i, total, `Extraindo "${page.title}"`);
605
651
  const { html, url } = await fetchConfluencePage(
606
652
  config.confluenceBaseUrl,
@@ -637,6 +683,7 @@ function createConfluenceSectionsDomain(config) {
637
683
  var paymentsV5BusinessRulesConfig = {
638
684
  id: "payments-v5-business-rules",
639
685
  title: "API de Pagamentos v5 \u2014 Regras de neg\xF3cio (SV)",
686
+ specVersion: "5.0.0",
640
687
  description: "Regras de neg\xF3cio da API de Inicia\xE7\xE3o de Pagamentos (Pix) vers\xE3o 5.0.0 do Open Finance Brasil, extra\xEDdas das p\xE1ginas Confluence de Servi\xE7os (SV): Informa\xE7\xF5es Gerais (vis\xE3o geral dos endpoints e dicion\xE1rio de dados), Escopo da API (formas de inicia\xE7\xE3o suportadas: Pix imediato, agendado, agendado recorrente, QR Code est\xE1tico/din\xE2mico, Pix Saque, Pix Troco, Pix Composto), M\xE1quina de Estados (status do consentimento e do pagamento), Diagrama de Sequ\xEAncia, Valida\xE7\xE3o de informa\xE7\xF5es no DICT e Adapta\xE7\xF5es para consultas de recursos entre 4.0.1 e 5.0.0. Complementa payments-v5-openapi, que traz a spec (endpoints e schemas) da mesma vers\xE3o. Conte\xFAdo que vale para todos os produtos de inicia\xE7\xE3o (idempot\xEAncia, assinatura de payload, polling, formatos de data) est\xE1 em payments-common-rules. 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.",
641
688
  confluenceBaseUrl: "https://openfinancebrasil.atlassian.net",
642
689
  interRequestDelayMs: 2e3,
@@ -673,6 +720,7 @@ var enrollmentsV2Domain = createOpenApiDomain(enrollmentsV2Config);
673
720
  var enrollmentsV2BusinessRulesConfig = {
674
721
  id: "enrollments-v2-business-rules",
675
722
  title: "API de V\xEDnculo de Dispositivo v2 \u2014 Regras de neg\xF3cio (SV)",
723
+ specVersion: "2.2.0",
676
724
  description: "Regras de neg\xF3cio da API de V\xEDnculo de Dispositivo (Enrollments / Jornada Sem Redirecionamento) vers\xE3o 2.2.0 do Open Finance Brasil, extra\xEDdas das p\xE1ginas Confluence de Servi\xE7os (SV): Informa\xE7\xF5es Gerais, M\xE1quina de estados (vincula\xE7\xE3o de conta, autoriza\xE7\xE3o de consentimento e do consentimento recorrente), Edi\xE7\xE3o do v\xEDnculo de dispositivo (campos edit\xE1veis e condi\xE7\xF5es) e FAQ - JSR (perguntas e respostas por categoria). Complementa enrollments-v2-openapi, que traz a spec (endpoints e schemas) do mesmo major \u2014 hoje na 2.3.0, enquanto estas p\xE1ginas do Confluence ainda s\xE3o as da 2.2.0. 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.",
677
725
  confluenceBaseUrl: "https://openfinancebrasil.atlassian.net",
678
726
  interRequestDelayMs: 2e3,
@@ -709,6 +757,7 @@ var automaticPaymentsV2Domain = createOpenApiDomain(automaticPaymentsV2Config);
709
757
  var automaticPaymentsV2BusinessRulesConfig = {
710
758
  id: "automatic-payments-v2-business-rules",
711
759
  title: "API de Pagamentos Autom\xE1ticos v2 \u2014 Regras de neg\xF3cio (SV)",
760
+ specVersion: "2.2.0",
712
761
  description: "Regras de neg\xF3cio da API de Pagamentos Autom\xE1ticos (Pix Autom\xE1tico e Transfer\xEAncias Inteligentes) vers\xE3o 2.2.0 do Open Finance Brasil, extra\xEDdas das p\xE1ginas Confluence de Servi\xE7os (SV): Informa\xE7\xF5es Gerais (vis\xE3o geral dos endpoints e dicion\xE1rio de dados), M\xE1quina de Estados (status do consentimento recorrente e do pagamento), Edi\xE7\xE3o do consentimento (campos edit\xE1veis e condi\xE7\xF5es), Tentativas Intradia e Extradia para Pix Autom\xE1tico (janelas de liquida\xE7\xE3o, retentativas, e2eID) e Adapta\xE7\xF5es para consultas de recursos entre 1.0.0 e 2.2.0. Complementa automatic-payments-v2-openapi, que traz a spec (endpoints e schemas) da mesma vers\xE3o. O guia de implementa\xE7\xE3o passo a passo do Pix Autom\xE1tico est\xE1 em payments-implementation-guides. 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.",
713
762
  confluenceBaseUrl: "https://openfinancebrasil.atlassian.net",
714
763
  interRequestDelayMs: 2e3,
@@ -784,18 +833,91 @@ var consentsV3Config = {
784
833
  // src/domains/consents-v3-openapi/index.ts
785
834
  var consentsV3Domain = createOpenApiDomain(consentsV3Config);
786
835
 
836
+ // src/domains/resources-v3-openapi/config.ts
837
+ var resourcesV3Config = {
838
+ id: "resources-v3-openapi",
839
+ title: "API de Recursos \u2014 spec OpenAPI 3.1.0",
840
+ description: "Spec OpenAPI oficial da API de Recursos (Resources) do Open Finance Brasil, vers\xE3o 3.1.0. Endpoint \xFAnico (GET /resources) que lista os recursos compartilhados pelo cliente e o status de cada um, considerando a disponibilidade do recurso e o status do consentimento relacionado \u2014 \xE9 o ponto de partida de qualquer jornada de Dados Cadastrais e Transacionais, pois diz o que a receptora pode de fato consultar. O consentimento que a habilita fica em consents-v3-openapi; as contas em si, em accounts-v2-openapi. Itens type=operation, type=schema (ex.: ResponseResourceList, ResourceData com enum de status) e os components alvos dos $ref: type=response e type=parameter. Regras de neg\xF3cio da mesma API em resources-v3-business-rules. search devolve resumos; use get_item para o n\xF3 completo da spec \u2014 nas opera\xE7\xF5es o campo `refs` traz os ids dos components referenciados, prontos para get_item.",
841
+ pathExample: "/resources",
842
+ specName: "resources",
843
+ specVersion: "3.1.0",
844
+ url: "https://openbanking-brasil.github.io/openapi/swagger-apis/resources/3.1.0.yml",
845
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3]
846
+ };
847
+
848
+ // src/domains/resources-v3-openapi/index.ts
849
+ var resourcesV3Domain = createOpenApiDomain(resourcesV3Config);
850
+
851
+ // src/domains/resources-v3-business-rules/config.ts
852
+ var resourcesV3BusinessRulesConfig = {
853
+ id: "resources-v3-business-rules",
854
+ title: "API de Recursos v3 \u2014 Regras de neg\xF3cio (DC)",
855
+ specVersion: "3.1.0",
856
+ description: "Regras de neg\xF3cio da API de Recursos (Resources) vers\xE3o 3.1.0 do Open Finance Brasil, extra\xEDdas das p\xE1ginas Confluence de Dados Cadastrais e Transacionais (DC): Informa\xE7\xF5es Gerais (vis\xE3o geral do GET /resources/v3/resources, status do recurso versus status do consentimento) e Orienta\xE7\xF5es - [DC] Recursos (quando um recurso volta a ficar dispon\xEDvel, uso do status code de erro apropriado quando a transmissora n\xE3o retorna as informa\xE7\xF5es) \u2014 esta \xFAltima n\xE3o \xE9 versionada e vale para todo o major v3. Complementa resources-v3-openapi, que traz a spec (endpoints e schemas) do mesmo major. 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.",
857
+ confluenceBaseUrl: "https://openfinancebrasil.atlassian.net",
858
+ interRequestDelayMs: 2e3,
859
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3],
860
+ pages: [
861
+ { pageId: "1267990545", title: "Informa\xE7\xF5es Gerais - [DC] Recursos - v3.1.0" },
862
+ { pageId: "219512943", title: "Orienta\xE7\xF5es - [DC] Recursos" }
863
+ ]
864
+ };
865
+
866
+ // src/domains/resources-v3-business-rules/index.ts
867
+ var resourcesV3BusinessRulesDomain = createConfluenceSectionsDomain(
868
+ resourcesV3BusinessRulesConfig
869
+ );
870
+
871
+ // src/domains/accounts-v2-openapi/config.ts
872
+ var accountsV2Config = {
873
+ id: "accounts-v2-openapi",
874
+ title: "API de Contas \u2014 spec OpenAPI 2.4.2",
875
+ description: "Spec OpenAPI oficial da API de Contas (Accounts) do Open Finance Brasil, vers\xE3o 2.4.2 \u2014 compartilhamento de contas de dep\xF3sito \xE0 vista, poupan\xE7a e pagamento pr\xE9-paga. Cobre listagem (GET /accounts), identifica\xE7\xE3o (GET /accounts/{accountId}), saldos (GET /accounts/{accountId}/balances), transa\xE7\xF5es (GET /accounts/{accountId}/transactions e /transactions-current) e limites de cheque especial (GET /accounts/{accountId}/overdraft-limits). \xC9 API de Dados Cadastrais e Transacionais: o consentimento que a habilita fica em consents-v3-openapi, e a listagem de recursos compartilhados em resources-v3-openapi. Itens type=operation (um por m\xE9todo+path), type=schema (payloads, ex.: AccountData, AccountBalancesData, AccountTransactionsData) e os components alvos dos $ref: type=response e type=parameter. Regras de neg\xF3cio da mesma API em accounts-v2-business-rules. search devolve resumos; use get_item para o n\xF3 completo da spec \u2014 nas opera\xE7\xF5es o campo `refs` traz os ids dos components referenciados, prontos para get_item.",
876
+ pathExample: "/accounts/{accountId}/transactions",
877
+ specName: "accounts",
878
+ specVersion: "2.4.2",
879
+ url: "https://openbanking-brasil.github.io/openapi/swagger-apis/accounts/2.4.2.yml",
880
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3]
881
+ };
882
+
883
+ // src/domains/accounts-v2-openapi/index.ts
884
+ var accountsV2Domain = createOpenApiDomain(accountsV2Config);
885
+
886
+ // src/domains/accounts-v2-business-rules/config.ts
887
+ var accountsV2BusinessRulesConfig = {
888
+ id: "accounts-v2-business-rules",
889
+ title: "API de Contas v2 \u2014 Regras de neg\xF3cio (DC)",
890
+ specVersion: "2.4.2",
891
+ description: "Regras de neg\xF3cio da API de Contas (Accounts) vers\xE3o 2.4.2 do Open Finance Brasil, extra\xEDdas das p\xE1ginas Confluence de Dados Cadastrais e Transacionais (DC): Informa\xE7\xF5es Gerais (vis\xE3o geral dos endpoints, contas de dep\xF3sito \xE0 vista/poupan\xE7a/pagamento pr\xE9-paga, saldos, transa\xE7\xF5es e limites) e Orienta\xE7\xF5es - [DC] Contas (obrigatoriedade de informa\xE7\xF5es de contraparte pela IN BCB n\xBA 371, tratamento de transa\xE7\xF5es e casos de borda) \u2014 esta \xFAltima n\xE3o \xE9 versionada e vale para todo o major v2. Complementa accounts-v2-openapi, que traz a spec (endpoints e schemas) do mesmo major. 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.",
892
+ confluenceBaseUrl: "https://openfinancebrasil.atlassian.net",
893
+ interRequestDelayMs: 2e3,
894
+ retryDelaysMs: [2e3, 4e3, 8e3, 16e3],
895
+ pages: [
896
+ { pageId: "979566625", title: "Informa\xE7\xF5es Gerais - [DC] Contas - v2.4.2" },
897
+ { pageId: "1739096252", title: "Orienta\xE7\xF5es - [DC] Contas" }
898
+ ]
899
+ };
900
+
901
+ // src/domains/accounts-v2-business-rules/index.ts
902
+ var accountsV2BusinessRulesDomain = createConfluenceSectionsDomain(
903
+ accountsV2BusinessRulesConfig
904
+ );
905
+
787
906
  // src/domains/pcm-openapi/config.ts
788
907
  var pcmOpenapiConfig = {
789
908
  specName: "pcm",
790
- specVersion: "1.0.0",
791
- url: "https://raw.githubusercontent.com/OpenBanking-Brasil/pcm-specs/refs/heads/feature/correct-paths/PCM-current.openapi.yaml",
909
+ // A spec da PCM não declara `info.version` (o campo `openapi:` traz um valor
910
+ // fora do padrão). Identificar pelo commit é o único rótulo verificável — e
911
+ // deixa explícito, em list_domains, que este domínio está congelado no SHA.
912
+ specVersion: "commit 8a7f7b0",
913
+ url: "https://raw.githubusercontent.com/OpenBanking-Brasil/pcm-specs/8a7f7b03ddfab7eaf97bcfe68e67fac22a5eeb68/PCM-current.openapi.yaml",
792
914
  retryDelaysMs: [2e3, 4e3, 8e3, 16e3]
793
915
  };
794
916
 
795
917
  // src/domains/pcm-openapi/index.ts
796
918
  var pcmOpenapiDomain = createOpenApiDomain({
797
919
  id: "pcm-openapi",
798
- title: `PCM \u2014 spec OpenAPI ${pcmOpenapiConfig.specVersion}`,
920
+ title: `PCM \u2014 spec OpenAPI (${pcmOpenapiConfig.specVersion})`,
799
921
  description: "Spec OpenAPI oficial da PCM (Plataforma de Coleta de M\xE9tricas) do Open Finance Brasil \u2014 a faceta swagger. 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), type=schema (payloads) e type=response (components reutiliz\xE1veis, alvos dos $ref). Outras facetas da PCM: tabelas de obrigatoriedade do campo additionalInfo em pcm-additional-info; regras de neg\xF3cio (reporte, processamento, diverg\xEAncias) em pcm-business-rules. search devolve resumos; use get_item para o n\xF3 completo da spec (opera\xE7\xF5es trazem `refs` com os ids dos components que referenciam).",
800
922
  pathExample: "/report-api/v1/private/report",
801
923
  ...pcmOpenapiConfig
@@ -922,6 +1044,11 @@ var participantesConfig = {
922
1044
  // src/domains/participantes/parser.ts
923
1045
  function parseParticipants(jsonText) {
924
1046
  const orgs = JSON.parse(jsonText);
1047
+ if (!Array.isArray(orgs) || orgs.length === 0) {
1048
+ throw new Error(
1049
+ `resposta do diret\xF3rio n\xE3o \xE9 uma lista de organiza\xE7\xF5es n\xE3o-vazia; in\xEDcio do conte\xFAdo: ${JSON.stringify(jsonText.slice(0, 80))}`
1050
+ );
1051
+ }
925
1052
  return orgs.map((org) => {
926
1053
  const servers = org.AuthorisationServers ?? [];
927
1054
  const families = /* @__PURE__ */ new Set();
@@ -1069,6 +1196,10 @@ var domains = [
1069
1196
  paymentsCommonRulesDomain,
1070
1197
  paymentsImplementationGuidesDomain,
1071
1198
  consentsV3Domain,
1199
+ resourcesV3Domain,
1200
+ resourcesV3BusinessRulesDomain,
1201
+ accountsV2Domain,
1202
+ accountsV2BusinessRulesDomain,
1072
1203
  pcmOpenapiDomain,
1073
1204
  pcmBusinessRulesDomain,
1074
1205
  jornadaOtimizadaDomain,
@@ -1099,6 +1230,7 @@ function findDomain(id) {
1099
1230
  return domains.find((d) => d.id === id);
1100
1231
  }
1101
1232
  var validIds = () => domains.map((d) => d.id).join(", ");
1233
+ var REFRESH_BUDGET_MS = 45e3;
1102
1234
  var domainIdSchema = z.enum(domains.map((d) => d.id)).describe("Id do dom\xEDnio (ver list_domains)");
1103
1235
  function extractContext(extra) {
1104
1236
  const progressToken = extra._meta?.progressToken;
@@ -1113,7 +1245,7 @@ function extractContext(extra) {
1113
1245
  }
1114
1246
  };
1115
1247
  }
1116
- function createServer() {
1248
+ function createServer(refreshBudgetMs = REFRESH_BUDGET_MS) {
1117
1249
  const server2 = new McpServer(
1118
1250
  { name: "opf-br-mcp", version: PACKAGE_VERSION },
1119
1251
  {
@@ -1124,7 +1256,7 @@ function createServer() {
1124
1256
  "list_domains",
1125
1257
  {
1126
1258
  title: "Listar dom\xEDnios",
1127
- 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).",
1259
+ description: "Lista os dom\xEDnios de conhecimento do Open Finance Brasil dispon\xEDveis neste server, com os filtros aceitos por cada um, a vers\xE3o da spec de origem e o estado do cache local. Devolve tamb\xE9m a vers\xE3o deste server em `server.version`. Os filtros aceitos por um dom\xEDnio s\xE3o a uni\xE3o de `filters` (os pr\xF3prios dele, pode vir ausente) com `filterSets[<filterSet>]` (o conjunto comum \xE0 fam\xEDlia, listado uma vez no topo). Comece por aqui; depois use search(domain, ...) e get_item(domain, id).",
1128
1260
  annotations: {
1129
1261
  readOnlyHint: true,
1130
1262
  destructiveHint: false,
@@ -1134,20 +1266,34 @@ function createServer() {
1134
1266
  },
1135
1267
  async () => {
1136
1268
  const out = domains.map((d) => {
1269
+ const shared = d.filterSet ? FILTER_SETS[d.filterSet] : void 0;
1270
+ const own = shared ? d.filters.filter((f) => !shared.some((s) => s.name === f.name && s.description === f.description)) : d.filters;
1271
+ const base = {
1272
+ id: d.id,
1273
+ title: d.title,
1274
+ description: d.description,
1275
+ ...d.filterSet ? { filterSet: d.filterSet } : {},
1276
+ ...own.length > 0 ? { filters: own } : {},
1277
+ ...d.specVersion ? { specVersion: d.specVersion } : {}
1278
+ };
1137
1279
  if (d.live) {
1138
- return { id: d.id, title: d.title, description: d.description, filters: d.filters, live: true };
1280
+ return { ...base, live: true };
1139
1281
  }
1140
1282
  const cached = readCache(d.id);
1141
1283
  return {
1142
- id: d.id,
1143
- title: d.title,
1144
- description: d.description,
1145
- filters: d.filters,
1284
+ ...base,
1146
1285
  cachedItems: cached?.data.items.length ?? 0,
1147
1286
  extractedAt: cached?.extractedAt ?? null
1148
1287
  };
1149
1288
  });
1150
- return text(out);
1289
+ const usedSets = Object.fromEntries(
1290
+ Object.entries(FILTER_SETS).filter(([name]) => domains.some((d) => d.filterSet === name))
1291
+ );
1292
+ return text({
1293
+ server: { name: "opf-br-mcp", version: PACKAGE_VERSION },
1294
+ filterSets: usedSets,
1295
+ domains: out
1296
+ });
1151
1297
  }
1152
1298
  );
1153
1299
  server2.registerTool(
@@ -1263,7 +1409,7 @@ function createServer() {
1263
1409
  "refresh",
1264
1410
  {
1265
1411
  title: "Re-extrair fontes",
1266
- description: "For\xE7a re-extra\xE7\xE3o das fontes p\xFAblicas (ignora o TTL de 72h do cache). Sem `domain`, atualiza todos. Use quando suspeitar de dados desatualizados.",
1412
+ description: "For\xE7a re-extra\xE7\xE3o das fontes p\xFAblicas (ignora o TTL de 72h do cache). Use quando suspeitar de dados desatualizados. Prefira passar `domain`: sem ele o server atualiza o que couber em 45s e devolve o restante em `pendentes`, que voc\xEA deve refazer chamando refresh(domain) para cada id.",
1267
1413
  inputSchema: {
1268
1414
  domain: domainIdSchema.optional().describe("Id do dom\xEDnio; omita para todos")
1269
1415
  },
@@ -1279,7 +1425,13 @@ function createServer() {
1279
1425
  (d) => !d.live && (!domain || d.id === domain)
1280
1426
  );
1281
1427
  const report = {};
1428
+ const pendentes = [];
1429
+ const inicio = Date.now();
1282
1430
  for (const d of targets) {
1431
+ if (!domain && Date.now() - inicio > refreshBudgetMs) {
1432
+ pendentes.push(d.id);
1433
+ continue;
1434
+ }
1283
1435
  try {
1284
1436
  const { data } = await getDomainData(d, true, extractContext(extra));
1285
1437
  report[d.id] = `ok: ${data.items.length} itens`;
@@ -1287,13 +1439,23 @@ function createServer() {
1287
1439
  report[d.id] = `erro: ${err.message}`;
1288
1440
  }
1289
1441
  }
1290
- return text(report);
1442
+ return text({
1443
+ atualizados: report,
1444
+ ...pendentes.length > 0 ? {
1445
+ pendentes,
1446
+ nota: `Parei em ${refreshBudgetMs / 1e3}s para n\xE3o estourar o timeout do cliente. Chame refresh(domain) para cada id em pendentes.`
1447
+ } : {}
1448
+ });
1291
1449
  }
1292
1450
  );
1293
1451
  return server2;
1294
1452
  }
1295
1453
 
1296
1454
  // src/index.ts
1455
+ if (process.argv.includes("--version") || process.argv.includes("-v")) {
1456
+ console.log(PACKAGE_VERSION);
1457
+ process.exit(0);
1458
+ }
1297
1459
  var server = createServer();
1298
1460
  await server.connect(new StdioServerTransport());
1299
- console.error("[opf-br-mcp] server pronto (stdio)");
1461
+ console.error(`[opf-br-mcp] server pronto (stdio) \u2014 v${PACKAGE_VERSION}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opf-br-mcp",
3
- "version": "0.7.0",
3
+ "version": "0.9.3",
4
4
  "description": "MCP server local com as regras do Open Finance Brasil (PCM additionalInfo, specs OpenAPI de Pagamentos)",
5
5
  "type": "module",
6
6
  "license": "MIT",