senado-br-mcp 3.5.1 → 3.7.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![Cloudflare Workers](https://img.shields.io/badge/Cloudflare%20Workers-F38020?logo=cloudflare&logoColor=white)
4
4
  ![MCP](https://img.shields.io/badge/MCP-Streamable%20HTTP-1f6feb)
5
- ![Tools](https://img.shields.io/badge/tools-67-2ea44f)
5
+ ![Tools](https://img.shields.io/badge/tools-69-2ea44f)
6
6
  [![CI](https://github.com/SidneyBissoli/senado-br-mcp-cloudflare/actions/workflows/ci.yml/badge.svg)](https://github.com/SidneyBissoli/senado-br-mcp-cloudflare/actions/workflows/ci.yml)
7
7
  [![MCP Registry](https://img.shields.io/badge/MCP-Registry-blue)](https://registry.modelcontextprotocol.io)
8
8
  [![LobeHub](https://lobehub.com/badge/mcp/sidneybissoli-senado-br-mcp-cloudflare)](https://lobehub.com/mcp/sidneybissoli-senado-br-mcp-cloudflare)
@@ -17,7 +17,7 @@
17
17
 
18
18
  A **public, hosted** MCP server that gives AI assistants live, structured access to **Brazilian Senate open data** — **no installation, no account, no API key**. Point your MCP client at the hosted endpoint and start asking about senators, bills, votes, expenses, and more. It runs on Cloudflare Workers over Streamable HTTP.
19
19
 
20
- It exposes **67 tools**, **4 prompts**, and **5 resources** across two domains:
20
+ It exposes **69 tools**, **4 prompts**, and **5 resources** across two domains:
21
21
 
22
22
  - **Legislative** — senators; bills and their tramitation; votes; committees; plenary sessions, results and presidential vetoes; party-bloc voting orientation; speeches and stenographic transcripts; blocs and leadership; federal legislation; and citizen participation via the e-Cidadania portal.
23
23
  - **Administrative** — CEAPS parliamentary-quota expenses; housing allowance; civil servants and payroll; overtime; interns; procurement contracts and biddings; outsourced staff; petty-cash funds; and budget execution.
@@ -68,6 +68,16 @@ Public legal URLs for app review:
68
68
  - Privacy policy: `https://senado.sidneybissoli.com/privacy`
69
69
  - Terms of use: `https://senado.sidneybissoli.com/terms`
70
70
 
71
+ ### ChatGPT (Deep Research)
72
+
73
+ ChatGPT deep research (and company knowledge, and research workflows over the Responses API) only uses an MCP server that exposes exactly `search` and `fetch` — this server does, on top of the `senado_*` tools, on the full `/mcp` surface (not on the curated app profile). Point the connector at the hosted endpoint, no key required:
74
+
75
+ ```
76
+ https://senado.sidneybissoli.com/mcp
77
+ ```
78
+
79
+ `search` ranks the query against the senators in office and the active committees of the Senate and the National Congress and returns `{ id, title, url }` (`sen:<código>` / `com:<código>`); `fetch` returns the document as readable Markdown — the senator's biography and mandates, or the committee's summary and board — with the canonical public page (the senator's profile on www25.senado.leg.br or the committee page on legis.senado.leg.br), which is what ChatGPT cites. Both carry the same provenance block as every other tool, in `structuredContent` and `_meta` (the text channel is the contract's JSON). In ChatGPT's developer mode (Settings → Security and login → Developer mode) any tool is callable — the `senado_*` tools remain the ones to use for data.
80
+
71
81
  ### Install (any client)
72
82
 
73
83
  For clients that launch MCP servers as a command — and for one-command setup — use the
@@ -87,7 +97,7 @@ own instance** — it is **not** required to use this public server.
87
97
 
88
98
  Prefer not to route queries through a third-party host (e.g. a newsroom policy)? The **same server**
89
99
  also runs as a **local stdio process** that talks **directly to the official government APIs** — same
90
- 67 tools, same provenance envelope, no Cloudflare in the loop. This is the npm/stdio channel, published
100
+ 69 tools, same provenance envelope, no Cloudflare in the loop. This is the npm/stdio channel, published
91
101
  as [`senado-br-mcp`](https://www.npmjs.com/package/senado-br-mcp).
92
102
 
93
103
  Point a command-based client (Claude Desktop/Code, etc.) at the package — npm fetches and runs it,
@@ -125,7 +135,7 @@ list/corpus tools: without D1 they fall back to a live scrape of the ~5 REST hig
125
135
 
126
136
  This repo bundles a Claude [Agent Skill](https://platform.claude.com/docs/en/docs/agents-and-tools/agent-skills/overview)
127
137
  at [`.claude/skills/senado-br/`](.claude/skills/senado-br/SKILL.md) that teaches Claude **when** to reach for
128
- this server and **how** to use its 67 tools well — a themed tool map, common question→tool playbooks, the
138
+ this server and **how** to use its 69 tools well — a themed tool map, common question→tool playbooks, the
129
139
  provenance contract, and gotchas (dates, the `codigoMateria` bridge, e-Cidadania's open-set listing, pagination).
130
140
  It points back to the server's own `senado://catalogo` / `senado://guia` resources rather than duplicating them.
131
141
 
@@ -156,7 +166,7 @@ Claude Code auto-discovers it when you work in this repo. To use it elsewhere, c
156
166
 
157
167
  ### Prerequisites
158
168
 
159
- - Node.js 20+
169
+ - Node.js 22+ (`engines.node`)
160
170
  - [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/) v4+
161
171
  - Cloudflare account
162
172
 
@@ -535,7 +545,7 @@ In addition to the `provenance` envelope, `structuredContent` carries a top-leve
535
545
 
536
546
  `retrieved_at` fidelity is provided by the cache layer (`cachedFetchWithMeta`), which persists the fetch timestamp alongside the value, so it reflects the real upstream extraction even on a cache hit. Two exceptions report an honest live timestamp instead: the e-Cidadania **list** tools (read from D1) use the corpus's `lastScrapedAt` — the true age of the stored data — while e-Cidadania **detail** tools, scraped live, use the fetch time and a level-3 canonical item URL. The only path that falls back to the build-time default is the in-code static reference catalog (`senado_tabelas_referencia` `tipos-materia`), which has no upstream extraction instant.
537
547
 
538
- Coverage is **universal**: all 67 tools carry the envelope (verify with `grep -c 'resultWithProvenance(' src/tools/*.ts`). The **⊕** marks in the inventory below denote the original pilot tools (votes, bills, processes); the envelope now extends to every tool, so the marks are historical.
548
+ Coverage is **universal**: all 69 tools carry the envelope — the 67 `senado_*` tools via `resultWithProvenance(` (verify with `grep -c 'resultWithProvenance(' src/tools/*.ts`) and the two Deep Research tools via `provenanceExtras` (same block, in `structuredContent`/`_meta`, since their text channel is the contract's JSON). The **⊕** marks in the inventory below denote the original pilot tools (votes, bills, processes); the envelope now extends to every tool, so the marks are historical.
539
549
 
540
550
  ## Citable dataset (e-Cidadania participation)
541
551
 
@@ -739,7 +749,16 @@ Reads a bundled snapshot of the Senate's organizational tree (crawled from the i
739
749
  |------|-------------|
740
750
  | `senado_estrutura_organizacional` | Organizational chart (organograma) resolved for a `unidade` (sigla like `DGER` or name): returns its `caminho` (ancestors) and every subordinate unit (`subordinadas[]` — secretarias, coordenações, serviços, núcleos — with `nivel`). Pairs with `senado_servidores`'s `subordinadasA` filter, which counts/lists all servants under a whole directorate (a servant sits in a leaf serviço, so filtering `lotacao` by the parent sigla returns 0). |
741
751
 
742
- **Total: 67 tools**
752
+ ### Group U — Deep Research (2 tools)
753
+
754
+ The [OpenAI Deep Research contract](#chatgpt-deep-research): the only two tools without the `senado_` prefix, because the names are fixed by the contract. Registered through the same shim as the others (read-only annotations, permissive `outputSchema`, per-tool telemetry) and served on `/mcp` only — the curated ChatGPT app profile does not include them. The index (senators in office + active committees, ~300 documents) is built on first use from the same two list endpoints the `senado_listar_*` tools read, and kept for 24 h.
755
+
756
+ | Tool | Description |
757
+ |------|-------------|
758
+ | `search` | Ranks the query (natural language or keywords, pt/en, accent-insensitive) against senators in office and active committees; returns up to 10 `{ id, title, url }` — `sen:<código>` with the public profile URL, `com:<código>` with the public committee page. Provenance of both lists in `structuredContent`/`_meta`. |
759
+ | `fetch` | Returns the document for an id from `search` as `{ id, title, text, url, metadata }`: the senator's biography and mandates (same read as `senado_obter_senador`) or the committee's summary and board (same read as `senado_obter_comissao`), as Markdown, with that read's provenance. Unknown id → error. |
760
+
761
+ **Total: 69 tools**
743
762
 
744
763
  ### Prompts (4)
745
764
 
@@ -759,7 +778,7 @@ Static context documents/tables (MCP `resources` capability), defined in `src/re
759
778
  | URI | Type | Content |
760
779
  | --- | --- | --- |
761
780
  | `senado://guia` | markdown | Visão geral e qual ferramenta usar por objetivo. |
762
- | `senado://catalogo` | markdown | As 67 ferramentas agrupadas por domínio. |
781
+ | `senado://catalogo` | markdown | As 69 ferramentas agrupadas por domínio. |
763
782
  | `senado://glossario` | markdown | Siglas e termos do Senado (PEC, CEAPS, CCJ, RCN…). |
764
783
  | `senado://tabelas/tipos-materia` | json | Tipos de proposição (sigla/nome/descrição). |
765
784
  | `senado://tabelas/ufs` | json | As 27 unidades federativas. |
@@ -848,7 +867,7 @@ tests/ # Vitest unit tests mirroring src/ (parsers, cache, th
848
867
  ## Connecting MCP Clients
849
868
 
850
869
  This is a **remote** server (Streamable HTTP, no install, open access) — point any MCP client at
851
- `https://senado.sidneybissoli.com/mcp`. Besides 67 tools, it exposes **prompts** (ready-made pt-BR
870
+ `https://senado.sidneybissoli.com/mcp`. Besides 69 tools, it exposes **prompts** (ready-made pt-BR
852
871
  workflows: `senado_gastos_senador`, `senado_tramitacao_materia`, `senado_votos_senador`,
853
872
  `senado_panorama_ecidadania`) and **resources** (`senado://guia`, `senado://catalogo`,
854
873
  `senado://glossario`, `senado://tabelas/tipos-materia`, `senado://tabelas/ufs`).
package/README.pt-BR.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![Cloudflare Workers](https://img.shields.io/badge/Cloudflare%20Workers-F38020?logo=cloudflare&logoColor=white)
4
4
  ![MCP](https://img.shields.io/badge/MCP-Streamable%20HTTP-1f6feb)
5
- ![Tools](https://img.shields.io/badge/tools-67-2ea44f)
5
+ ![Tools](https://img.shields.io/badge/tools-69-2ea44f)
6
6
  [![CI](https://github.com/SidneyBissoli/senado-br-mcp-cloudflare/actions/workflows/ci.yml/badge.svg)](https://github.com/SidneyBissoli/senado-br-mcp-cloudflare/actions/workflows/ci.yml)
7
7
  [![MCP Registry](https://img.shields.io/badge/MCP-Registry-blue)](https://registry.modelcontextprotocol.io)
8
8
  [![LobeHub](https://lobehub.com/badge/mcp/sidneybissoli-senado-br-mcp-cloudflare)](https://lobehub.com/mcp/sidneybissoli-senado-br-mcp-cloudflare)
@@ -16,7 +16,7 @@
16
16
 
17
17
  Um servidor MCP **público e hospedado** que dá aos assistentes de IA acesso ao vivo e estruturado aos **dados abertos do Senado Federal do Brasil** — **sem instalação, sem conta, sem chave de API**. Aponte o seu cliente MCP para o endpoint hospedado e comece a perguntar sobre senadores, matérias, votações, despesas e muito mais. Roda em Cloudflare Workers via Streamable HTTP.
18
18
 
19
- Expõe **67 ferramentas**, **4 prompts** e **5 recursos** em dois domínios:
19
+ Expõe **69 ferramentas**, **4 prompts** e **5 recursos** em dois domínios:
20
20
 
21
21
  - **Legislativo** — senadores; matérias e sua tramitação; votações; comissões; sessões plenárias, resultados e vetos presidenciais; orientação de bancada nas votações; discursos e notas taquigráficas; blocos e lideranças; legislação federal; e participação cidadã pelo portal e-Cidadania.
22
22
  - **Administrativo** — despesas da cota parlamentar (CEAPS); auxílio-moradia; servidores e remunerações; horas extras; estagiários; contratos e licitações; terceirizados; suprimento de fundos; e execução orçamentária.
@@ -50,7 +50,7 @@ Para submissao e revisao pelo OpenAI Apps SDK, o Worker tambem expoe uma superfi
50
50
  https://senado.sidneybissoli.com/mcp/openai-app-v2
51
51
  ```
52
52
 
53
- Esse endpoint preserva o servidor MCP publico completo em `/mcp`, mas limita a descoberta a 25
53
+ Esse endpoint preserva o servidor MCP publico completo em `/mcp`, mas limita a descoberta a 27
54
54
  ferramentas de alto sinal, organizadas por intencao de usuario, para uso como app do ChatGPT.
55
55
  `/mcp/openai-app` continua disponivel como alias legado, mas novas configuracoes do app no ChatGPT
56
56
  devem usar `/mcp/openai-app-v2` para forcar a leitura do schema atual das ferramentas. As ferramentas
@@ -58,7 +58,7 @@ continuam chamando os mesmos handlers e retornando o mesmo envelope de provenien
58
58
  anunciada fica mais estreita. Qualquer listagem de ChatGPT App deve apresentar isto como um app
59
59
  independente de pesquisa em dados abertos, nao como conector oficial do Senado, da OpenAI ou do ChatGPT.
60
60
 
61
- Para ChatGPT Apps, essas 25 ferramentas tambem anunciam um template UI compartilhado do MCP Apps em
61
+ Para ChatGPT Apps, essas 27 ferramentas tambem anunciam um template UI compartilhado do MCP Apps em
62
62
  `ui://senado-br-mcp/openai-app-dashboard-v2.html`. O widget e autocontido e renderiza o
63
63
  `structuredContent` retornado como painel compacto com metricas, registros principais e fonte/proveniencia,
64
64
  sem adicionar outra ferramenta de dados visivel ao modelo.
@@ -68,6 +68,16 @@ URLs publicas legais para revisao do app:
68
68
  - Politica de privacidade: `https://senado.sidneybissoli.com/privacy`
69
69
  - Termos de uso: `https://senado.sidneybissoli.com/terms`
70
70
 
71
+ ### ChatGPT (Deep Research)
72
+
73
+ O deep research do ChatGPT (e o company knowledge, e os workflows de pesquisa da API Responses) só usa um servidor MCP que exponha exatamente `search` e `fetch` — este servidor expõe, além das ferramentas `senado_*`, na superfície completa `/mcp` (não no perfil curado do app). Aponte o conector para o endpoint hospedado, sem chave:
74
+
75
+ ```
76
+ https://senado.sidneybissoli.com/mcp
77
+ ```
78
+
79
+ `search` ranqueia a consulta contra os senadores em exercício e as comissões ativas do Senado e do Congresso Nacional e devolve `{ id, title, url }` (`sen:<código>` / `com:<código>`); `fetch` devolve o documento em Markdown legível — a biografia e os mandatos do senador, ou o resumo e a mesa da comissão — com a página pública canônica (o perfil do senador em www25.senado.leg.br ou a página da comissão em legis.senado.leg.br), que é o que o ChatGPT cita. As duas carregam o mesmo bloco de proveniência das outras ferramentas, em `structuredContent` e `_meta` (o canal de texto é o JSON do contrato). No modo desenvolvedor do ChatGPT (Settings → Security and login → Developer mode) qualquer ferramenta é chamável — as `senado_*` continuam sendo as ferramentas para dados.
80
+
71
81
  ### Instalação (qualquer cliente)
72
82
 
73
83
  Para clientes que iniciam servidores MCP como um comando — e para uma configuração de um único comando — use a
@@ -87,7 +97,7 @@ própria instância** — **não** é necessário para usar este servidor públi
87
97
 
88
98
  Prefere não rotear suas consultas por um servidor de terceiros (ex.: política de uma redação)? O **mesmo
89
99
  servidor** também roda como um **processo local stdio** que bate **direto nas APIs oficiais do governo** —
90
- as mesmas 67 ferramentas, o mesmo envelope de proveniência, sem Cloudflare no caminho. É o canal npm/stdio,
100
+ as mesmas 69 ferramentas, o mesmo envelope de proveniência, sem Cloudflare no caminho. É o canal npm/stdio,
91
101
  publicado como [`senado-br-mcp`](https://www.npmjs.com/package/senado-br-mcp).
92
102
 
93
103
  Aponte um cliente baseado em comando (Claude Desktop/Code, etc.) para o pacote — o npm baixa e executa,
@@ -125,7 +135,7 @@ Os logs vão para o **stderr** — o stdout carrega apenas o fluxo do protocolo
125
135
 
126
136
  Este repositório inclui uma [Agent Skill](https://platform.claude.com/docs/en/docs/agents-and-tools/agent-skills/overview)
127
137
  do Claude em [`.claude/skills/senado-br/`](.claude/skills/senado-br/SKILL.md) que ensina o Claude **quando**
128
- recorrer a este servidor e **como** usar bem as 67 ferramentas — um mapa de ferramentas por tema, playbooks de
138
+ recorrer a este servidor e **como** usar bem as 69 ferramentas — um mapa de ferramentas por tema, playbooks de
129
139
  pergunta→ferramenta, o contrato de proveniência e os gotchas (datas, a ponte `codigoMateria`, a listagem
130
140
  só-abertas do e-Cidadania, paginação). Ela aponta para os recursos `senado://catalogo` / `senado://guia` do
131
141
  próprio servidor em vez de duplicá-los.
@@ -138,7 +148,7 @@ O Claude Code a descobre automaticamente ao trabalhar neste repositório. Para u
138
148
 
139
149
  - **Runtime:** Cloudflare Workers (ESM)
140
150
  - **Transporte:** Streamable HTTP (spec MCP 2025-03-26) via `createMcpHandler` de `agents/mcp`
141
- - **Protocolo:** MCP sobre JSON-RPC — `/mcp` serve o catalogo publico completo; `/mcp/openai-app-v2` expoe um perfil curado de 25 ferramentas mais um widget MCP Apps compartilhado para revisao/submissao como app OpenAI (`/mcp/openai-app` permanece como alias legado)
151
+ - **Protocolo:** MCP sobre JSON-RPC — `/mcp` serve o catalogo publico completo; `/mcp/openai-app-v2` expoe um perfil curado de 27 ferramentas mais um widget MCP Apps compartilhado para revisao/submissao como app OpenAI (`/mcp/openai-app` permanece como alias legado)
142
152
  - **SDK:** `@modelcontextprotocol/sdk` 1.26.0+ (instâncias de McpServer por requisição)
143
153
  - **Validação:** schemas Zod para todas as entradas das ferramentas
144
154
  - **Cache:** 2 camadas (L0 memória + L1 Cache API) com chaveamento SHA-256
@@ -156,7 +166,7 @@ O Claude Code a descobre automaticamente ao trabalhar neste repositório. Para u
156
166
 
157
167
  ### Pré-requisitos
158
168
 
159
- - Node.js 20+
169
+ - Node.js 22+ (`engines.node`)
160
170
  - [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/) v4+
161
171
  - Conta Cloudflare
162
172
 
@@ -507,6 +517,37 @@ Campos do bloco `concise` (por resposta — uma ferramenta, uma fonte): `source`
507
517
 
508
518
  A cobertura abrange as quatro fontes upstream: **Dados Abertos Legislativo** (`legis.senado.leg.br`), **Dados Abertos Administrativo** (`adm.senado.gov.br`), **Execução Orçamentária** (feed Arquimedes/Financeiro em `senado.gov.br`) e **Portal e-Cidadania** (`www12.senado.leg.br/ecidadania`). Nas listas do e-Cidadania (lidas do D1) o `retrieved_at` é o `lastScrapedAt` do corpus — a idade real do dado; nos detalhes (raspados ao vivo) é o instante da chamada, com a URL canônica do item. A fidelidade do `retrieved_at` vem da camada de cache (`cachedFetchWithMeta`), que persiste o timestamp junto ao valor.
509
519
 
520
+ ## Conjunto de dados citável (participação no e-Cidadania)
521
+
522
+ Além do servidor ao vivo, este projeto publica um **conjunto de dados congelado, versionado e citável** da camada de participação do e-Cidadania (consultas públicas, ideias legislativas, eventos interativos + seus comentários, votos históricos por estado) — a camada que o pacote R `congressbr` nunca cobriu. Cada valor carrega um envelope de procedência por campo (`{ value, sourceEndpoint, sourceField, retrievedAt, license, schemaVersion }`); a licença dos dados (Dados Abertos do Senado Federal) é mantida **separada** da licença do código (MIT).
523
+
524
+ - **Como citar** — [`CITATION.cff`](CITATION.cff) (conjunto de dados; cite o DOI da versão do snapshot que você usou, e o DOI de conceito para o conjunto através das versões).
525
+ - **O que vai em cada release** — [`CHANGELOG-dataset.md`](CHANGELOG-dataset.md) (cumulativo, só acrescenta; amarra cada release ao seu `schemaVersion`).
526
+ - **Dicionário de variáveis e procedência de campo** — [`docs/dataset-dictionary.md`](docs/dataset-dictionary.md) (gerado de `src/dataset/schema.ts`, a fonte única de verdade).
527
+ - **Licença dos dados** — [`LICENSE-DATA.md`](LICENSE-DATA.md).
528
+ - **Como cortar uma release** (congelar → checksums → GitHub Release → DOI no Zenodo) — [`docs/release-runbook.md`](docs/release-runbook.md); a maquinaria está em `src/dataset/`, `scripts/build-dataset/` e `.github/workflows/release-dataset.yml`.
529
+
530
+ ### Inventário — schema v2 (`schemaVersion` `2.0.0`)
531
+
532
+ Cada release entrega um recurso NDJSON por entidade (um `HarmonizedRecord` por linha: identidade + um envelope de procedência por campo), mais um manifesto `datapackage.json` e uma cópia do dicionário. Cinco recursos:
533
+
534
+ | Recurso (`*.ndjson`) | Grão | Variáveis principais | Fonte(s) |
535
+ |---|---|---|---|
536
+ | `consultas` | 1 consulta pública (matéria) | `materia`, `ementa`, `votosSim`/`votosNao`/`totalVotos`, `percentual*`, **`autoria`ⁿ**, **`relator`ⁿ**, `status`, `url`, `firstSeenAt` | listagem `pesquisamateria` + **detalhe** (`visualizacaomateria`)ⁿ + `/processo?tramitando=S` para o status |
537
+ | `ideias` | 1 ideia legislativa (~113,7 mil) | `titulo`, `apoios`, `status`, **`dataPublicacao`ⁿ**, **`autorUf`ⁿ**, **`descricao`ⁿ**, **`plConvertido`ⁿ**, `url`, `firstSeenAt` | listagem `pesquisaideia` + **detalhe** (`visualizacaoideia`) por um backfill retomávelⁿ |
538
+ | `eventos` | 1 evento interativo (audiência) | `titulo`, **`data`ᶜ**, **`hora`ᶜ**, `comissao`, **`comissaoNomeCompleto`ⁿ**, **`local`ⁿ**, **`descricao`ⁿ**, **`pauta`ⁿ**, **`convidados`ⁿ**, **`videoUrl`ⁿ**, **`comentarios`ᶜ**, `status`, `url`, `firstSeenAt` | listagem `principalaudiencia` + **detalhe** (`visualizacaoaudiencia`)ⁿ + **fragmento AJAX de comentários**ᶜ |
539
+ | **`eventos_comentarios`ⁿ** | 1 comentário (nível de comentário) | `eventoId`, `comentarioId`, `uf`, `texto`, `data`, `hora`, `momentoVideoUrl`, `convidadoAssociado` | fragmento AJAX `ajaxcolecaocomentarioaudiencia?audienciaId=` |
540
+ | `consultas_votos` | 1 matéria (acervo histórico) | `materia`, `ementa`, `autoria`, `votosSim`/`votosNao`/`totalVotos`, `votosPorUf`, `status`, `url`, `referencePeriod` | CSV do Arquimedes `Proposições-com-votos.csv` (reingerido semanalmente) |
541
+
542
+ **ⁿ = novo ou reaberto na v2 · ᶜ = fonte corrigida para a canônica na v2.** O que a v2 (`2.0.0`) mudou — a ingestão saiu de **só listagem** para **listagem + detalhe (+ comentários por AJAX nos eventos)**:
543
+
544
+ - **Eventos corrigidos e enriquecidos.** `data`/`hora` agora vêm da página de detalhe (a canônica — o [estudo A3](docs/estudo-a3-reconciliacao-eventos.md) achou a listagem divergente em 57% dos casos no `hora`), mais seis campos novos de detalhe (`comissaoNomeCompleto`, `local`, `descricao`, `pauta`, `convidados`, `videoUrl`). `comentarios` passou a ser a **contagem canônica do AJAX** (a contagem da listagem era `0` espúrio em 82% dos eventos, capturando só ~6,7% do engajamento).
545
+ - **Recurso novo em nível de comentário**, `eventos_comentarios` — uma linha por comentário de audiência, o sinal de participação que ninguém mais publica versionado.
546
+ - **Campos que só existem no detalhe foram reabertos** para `ideias` (`dataPublicacao`, `autorUf`, `descricao`, `plConvertido`) e `consultas` (`autoria`, `relator`) — antes sempre `null` por decisão de projeto.
547
+ - **Postura de privacidade pela origem do dado.** Conteúdo de cidadão (comentários de audiência, autores de ideia) guarda **apenas a UF — nunca o nome**, descartado já no parser; agente público (autoria/relatoria de consulta, convidados de evento) guarda o nome (público pela função). O racional campo a campo está em `docs/schema-v2-inventario.md` (alvo aprovado).
548
+
549
+ O NDJSON congelado **não** é commitado (é construído sob demanda a partir do corpus soberano no D1); uma release marcada `dataset-v*` anexa o tarball + `SHA256SUMS` + `release.json` e os arquiva no Zenodo.
550
+
510
551
  ## Inventário de ferramentas
511
552
 
512
553
  ### Grupo H — Referência/Metadados (1 ferramenta)
@@ -670,7 +711,24 @@ A cobertura abrange as quatro fontes upstream: **Dados Abertos Legislativo** (`l
670
711
  |------|-------------|
671
712
  | `senado_execucao_orcamentaria` | Execução orçamentária desde 2013 (dotação, empenhado/liquidado/pago) e receitas próprias desde 2012 (previsto vs. arrecadado) — agregadas por ano, ação, grupo de despesa, fonte ou origem de receita; `estatisticas=true` devolve a distribuição do conjunto (min/máx/média/mediana/percentis) + ranking top/bottom, ou um ranking de grupos por soma do `campo` via `agruparPor`, com `campo` (padrão pago / arrecadada) e `topN` |
672
713
 
673
- **Total: 67 ferramentas**
714
+ ### Grupo T — Estrutura Organizacional (1 ferramenta)
715
+
716
+ Lê um retrato embutido da árvore organizacional do Senado (varrida do portal institucional até o nível de serviço por `npm run ingest:estrutura`), porque a API de dados abertos só publica unidades até Secretaria e nunca liga uma unidade-folha ao pai dela. Unidades que o portal lista apenas por nome, sem página própria (os núcleos da CONLEG/CONORF), são capturadas como nós sintéticos a partir da listagem indentada da página. Órgãos do Congresso onde o cadastro de servidores registra lotações (CMO, CPCMS, CMMC) vêm de um complemento curado (`src/estrutura/complemento-cn.ts`, fonte pública: congressonacional.leg.br) sob uma raiz **Congresso Nacional** (`CN`) separada — nunca sob a árvore do Senado, de modo que `subordinadasA: "DGER"` os exclui e `subordinadasA: "CN"` os conta. Servidores registrados sob as pseudo-unidades situacionais "Servidores Afastados/em Trânsito - SF" são reportados à parte, como `afastadosOuEmTransito`, por `senado_servidores`.
717
+
718
+ | Ferramenta | Descrição |
719
+ |------|-------------|
720
+ | `senado_estrutura_organizacional` | Organograma resolvido para uma `unidade` (sigla como `DGER` ou nome): devolve o `caminho` (ancestrais) e todas as unidades subordinadas (`subordinadas[]` — secretarias, coordenações, serviços, núcleos — com `nivel`). Faz par com o filtro `subordinadasA` de `senado_servidores`, que conta/lista todos os servidores sob uma diretoria inteira (o servidor fica num serviço-folha, então filtrar `lotacao` pela sigla do pai devolve 0). |
721
+
722
+ ### Grupo U — Deep Research (2 ferramentas)
723
+
724
+ O [contrato Deep Research da OpenAI](#chatgpt-deep-research): as únicas duas ferramentas sem o prefixo `senado_`, porque os nomes são fixados pelo contrato. Registradas pelo mesmo shim das outras (annotations somente-leitura, `outputSchema` permissivo, telemetria por ferramenta) e servidas só em `/mcp` — o perfil curado do app do ChatGPT não as inclui. O índice (senadores em exercício + comissões ativas, ~300 documentos) é construído no primeiro uso a partir dos mesmos dois endpoints de lista que as ferramentas `senado_listar_*` leem, e mantido por 24 h.
725
+
726
+ | Ferramenta | Descrição |
727
+ |------|-------------|
728
+ | `search` | Ranqueia a consulta (linguagem natural ou palavras-chave, pt/en, sem distinguir acentos) contra senadores em exercício e comissões ativas; devolve até 10 `{ id, title, url }` — `sen:<código>` com a URL do perfil público, `com:<código>` com a página pública da comissão. Proveniência das duas listas em `structuredContent`/`_meta`. |
729
+ | `fetch` | Devolve o documento de um id vindo de `search` como `{ id, title, text, url, metadata }`: a biografia e os mandatos do senador (a mesma leitura de `senado_obter_senador`) ou o resumo e a mesa da comissão (a mesma leitura de `senado_obter_comissao`), em Markdown, com a proveniência dessa leitura. Id desconhecido → erro. |
730
+
731
+ **Total: 69 ferramentas**
674
732
 
675
733
  ### Prompts (4)
676
734
 
@@ -690,7 +748,7 @@ Documentos/tabelas de contexto estáticos (capacidade MCP `resources`), definido
690
748
  | URI | Tipo | Conteúdo |
691
749
  | --- | --- | --- |
692
750
  | `senado://guia` | markdown | Visão geral e qual ferramenta usar por objetivo. |
693
- | `senado://catalogo` | markdown | As 67 ferramentas agrupadas por domínio. |
751
+ | `senado://catalogo` | markdown | As 69 ferramentas agrupadas por domínio. |
694
752
  | `senado://glossario` | markdown | Siglas e termos do Senado (PEC, CEAPS, CCJ, RCN…). |
695
753
  | `senado://tabelas/tipos-materia` | json | Tipos de proposição (sigla/nome/descrição). |
696
754
  | `senado://tabelas/ufs` | json | As 27 unidades federativas. |
@@ -774,7 +832,7 @@ tests/ # Testes unitários Vitest espelhando src/ (parsers, c
774
832
  ## Conectando clientes MCP
775
833
 
776
834
  Este é um servidor **remoto** (Streamable HTTP, sem instalação, acesso aberto) — aponte qualquer cliente MCP para
777
- `https://senado.sidneybissoli.com/mcp`. Além das 67 ferramentas, ele expõe **prompts** (workflows prontos em pt-BR:
835
+ `https://senado.sidneybissoli.com/mcp`. Além das 69 ferramentas, ele expõe **prompts** (workflows prontos em pt-BR:
778
836
  `senado_gastos_senador`, `senado_tramitacao_materia`, `senado_votos_senador`,
779
837
  `senado_panorama_ecidadania`) e **recursos** (`senado://guia`, `senado://catalogo`,
780
838
  `senado://glossario`, `senado://tabelas/tipos-materia`, `senado://tabelas/ufs`).
package/dist/cli.js CHANGED
@@ -14,7 +14,8 @@
14
14
  * This file is NOT imported by the Worker entrypoint (`src/index.ts`) and never
15
15
  * touches `agents/mcp`. It only depends on `createServer` + the stdio transport.
16
16
  */
17
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
17
+ import { serveStdio, StdioServerTransport } from "@modelcontextprotocol/server/stdio";
18
+ import { unknownCursorError } from "./pagination.js";
18
19
  import { createServer } from "./server.js";
19
20
  // No Cloudflare bindings in Node: createServer defaults the base URLs when these
20
21
  // are empty, and every binding-dependent path is guarded. CACHE_KV is declared on
@@ -26,8 +27,30 @@ const env = {
26
27
  };
27
28
  async function main() {
28
29
  // No `ctx` → e-Cidadania detail write-through is a no-op (fire-and-forget skipped).
29
- const server = createServer(env);
30
- await server.connect(new StdioServerTransport());
30
+ // `serveStdio`, e não `server.connect(transport)` direto: ele serve a
31
+ // abertura MODERNA e a de 2025 no mesmo processo. Conectar o transporte na
32
+ // mão atende só o ciclo legado, e o mcpscore conta a prontidão para a spec
33
+ // 2026-07-28 dentro da nota principal — medido em 30/08/2026: 127/144 com
34
+ // `connect` direto contra 146/148 nos irmãos que usam `serveStdio`, sem UMA
35
+ // falha de diferença. Eram 17 pontos de regras que sequer eram avaliadas.
36
+ //
37
+ // O transporte é construído aqui, e não deixado a cargo do `serveStdio`, para
38
+ // que o guarda de cursor possa se pendurar nele. Cursor de paginação inválido
39
+ // -> -32602, o MESMO guarda que o Worker aplica no POST (src/pagination.ts).
40
+ // Substitui `onmessage` em vez de somar um ouvinte: só quem está NO lugar do
41
+ // `onmessage` pode interromper a entrega ao SDK, e é a interrupção que produz
42
+ // a recusa.
43
+ const transport = new StdioServerTransport();
44
+ serveStdio(() => createServer(env), { transport });
45
+ const entregaAoServidor = transport.onmessage;
46
+ transport.onmessage = (message) => {
47
+ const recusa = unknownCursorError(message);
48
+ if (recusa) {
49
+ void transport.send(recusa);
50
+ return;
51
+ }
52
+ entregaAoServidor?.(message);
53
+ };
31
54
  // Logs go to stderr (see src/utils/logger.ts); stdout carries only JSON-RPC.
32
55
  console.error(JSON.stringify({ level: "info", msg: "stdio_ready", ts: new Date().toISOString() }));
33
56
  }
@@ -1,7 +1,7 @@
1
1
  export const ESTRUTURA_ORGANIZACIONAL = {
2
- "extraidoEm": "2026-08-01T08:10:35.063Z",
2
+ "extraidoEm": "2026-09-01T10:56:16.383Z",
3
3
  "fonteUrl": "https://www12.senado.leg.br/institucional/estrutura/orgaosenado",
4
- "total": 824,
4
+ "total": 823,
5
5
  "orgaos": [
6
6
  {
7
7
  "cod": -2072346465,
@@ -1947,12 +1947,6 @@ export const ESTRUTURA_ORGANIZACIONAL = {
1947
1947
  "nome": "Ouvidoria do Senado Federal",
1948
1948
  "codSuperior": null
1949
1949
  },
1950
- {
1951
- "cod": 9426,
1952
- "sigla": null,
1953
- "nome": "Assessoria Técnica da Strans",
1954
- "codSuperior": 8852
1955
- },
1956
1950
  {
1957
1951
  "cod": 9430,
1958
1952
  "sigla": null,
@@ -2217,12 +2211,6 @@ export const ESTRUTURA_ORGANIZACIONAL = {
2217
2211
  "nome": "Serviço de Atendimento Eletrônico",
2218
2212
  "codSuperior": 9600
2219
2213
  },
2220
- {
2221
- "cod": 9596,
2222
- "sigla": null,
2223
- "nome": "Serviço de Manutenção Eletromecânica",
2224
- "codSuperior": 10741
2225
- },
2226
2214
  {
2227
2215
  "cod": 9600,
2228
2216
  "sigla": null,
@@ -2457,12 +2445,6 @@ export const ESTRUTURA_ORGANIZACIONAL = {
2457
2445
  "nome": "Serviço de Pesquisa e Análise",
2458
2446
  "codSuperior": 9807
2459
2447
  },
2460
- {
2461
- "cod": 9810,
2462
- "sigla": null,
2463
- "nome": "Observatório da Mulher Contra a Violência",
2464
- "codSuperior": 9807
2465
- },
2466
2448
  {
2467
2449
  "cod": 9868,
2468
2450
  "sigla": "IFI",
@@ -4785,18 +4767,6 @@ export const ESTRUTURA_ORGANIZACIONAL = {
4785
4767
  "nome": "Serviço de Suporte aos Sistemas de Saúde",
4786
4768
  "codSuperior": 10620
4787
4769
  },
4788
- {
4789
- "cod": 10801,
4790
- "sigla": "GSBBONET",
4791
- "nome": "Gabinete do Senador Bruno Bonetti",
4792
- "codSuperior": 108
4793
- },
4794
- {
4795
- "cod": 10803,
4796
- "sigla": null,
4797
- "nome": "Escritório de Apoio Nº 01 do Senador Bruno Bonetti",
4798
- "codSuperior": 10801
4799
- },
4800
4770
  {
4801
4771
  "cod": 10806,
4802
4772
  "sigla": "GSRACIOL",
@@ -4946,6 +4916,30 @@ export const ESTRUTURA_ORGANIZACIONAL = {
4946
4916
  "sigla": null,
4947
4917
  "nome": "Coordenação do Observatório da Mulher Contra a Violência",
4948
4918
  "codSuperior": 8852
4919
+ },
4920
+ {
4921
+ "cod": 10860,
4922
+ "sigla": "GSLPEREI",
4923
+ "nome": "Gabinete da Senadora Lourdinha Pereira",
4924
+ "codSuperior": 108
4925
+ },
4926
+ {
4927
+ "cod": 10861,
4928
+ "sigla": null,
4929
+ "nome": "Escritório de Apoio Nº 01 da Senadora Lourdinha Pereira",
4930
+ "codSuperior": 10860
4931
+ },
4932
+ {
4933
+ "cod": 10862,
4934
+ "sigla": "GSSREGIN",
4935
+ "nome": "Gabinete do Senador Sargento Reginauro",
4936
+ "codSuperior": 108
4937
+ },
4938
+ {
4939
+ "cod": 10863,
4940
+ "sigla": null,
4941
+ "nome": "Escritório de Apoio Nº 01 do Senador Sargento Reginauro",
4942
+ "codSuperior": 10862
4949
4943
  }
4950
4944
  ]
4951
4945
  };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `server/discover` anuncia TODAS as revisões que o servidor atende, não só as
3
+ * modernas.
4
+ *
5
+ * O ACHADO. O `mcpscore` reprovava `protocol_version_supported_versions_include_negotiated`:
6
+ * o servidor negocia 2025-11-25 pelo handshake `initialize` — é a revisão que o
7
+ * próprio auditor negocia, e a que a maioria dos clientes fala hoje — mas o
8
+ * `server/discover` anunciava só `["2026-07-28"]`. A mensagem do auditor é
9
+ * explícita sobre as duas saídas: "add it to supportedVersions, or disable the
10
+ * legacy lifecycle". Desligar o ciclo legado trocaria 1 ponto por uma regressão
11
+ * de compatibilidade com quase todos os clientes; sobra anunciar a verdade.
12
+ *
13
+ * POR QUE O SDK NÃO FAZ ISSO. O `_ondiscover()` do
14
+ * `@modelcontextprotocol/server` 2.0.0 devolve
15
+ * `modernProtocolVersions(this._supportedProtocolVersions)`, um filtro que
16
+ * descarta por desenho toda revisão pré-2026 ("2025-era versions are negotiated
17
+ * via `initialize`"). Não há opção que o desligue. É uma leitura mais estrita
18
+ * que a do auditor, que se apoia em Basic §Versioning — onde o exemplo de
19
+ * `UnsupportedProtocolVersionError` lista revisões legadas em `supported`.
20
+ *
21
+ * POR QUE PATCH DE MÉTODO, E NÃO TROCA DE HANDLER. Registrar o nosso próprio
22
+ * handler de `server/discover` não gruda: a entrada HTTP do SDK chama
23
+ * `installModernOnlyHandlers` A CADA REQUEST, depois da nossa factory, e ela
24
+ * reinstala `() => server._ondiscover()` por cima. Como o que ela instala é uma
25
+ * closure que chama o MÉTODO, trocar o método vale para os dois caminhos — o
26
+ * handler do construtor e o reinstalado — e sobrevive à reinstalação. Trocar o
27
+ * handler valeria só no stdio, justamente onde o achado não aparece.
28
+ *
29
+ * A GUARDA. Isto mexe em `_ondiscover`, que é interno do SDK. O risco de um
30
+ * patch assim é virar silêncio: o SDK renomeia o método, o patch não aplica
31
+ * nada e ninguém percebe. Por isso `announceServedVersions` FALHA ALTO se a
32
+ * forma esperada não estiver lá — o servidor nem constrói — em vez de seguir
33
+ * com o anúncio antigo. `tests/discover.test.ts` prende o comportamento, e a
34
+ * catraca do mcpscore no CI pega a regressão pelo lado de fora.
35
+ */
36
+ /**
37
+ * Ordena revisões datadas da mais nova para a mais antiga. São strings
38
+ * `AAAA-MM-DD`, então a ordem lexicográfica É a cronológica.
39
+ */
40
+ export function maisNovaPrimeiro(versoes) {
41
+ return [...new Set(versoes)].sort().reverse();
42
+ }
43
+ /**
44
+ * Troca o `_ondiscover` da instância por um que anuncia todas as revisões
45
+ * atendidas. Só a lista muda: capacidades e instruções continuam vindo do
46
+ * cálculo do próprio SDK.
47
+ */
48
+ export function announceServedVersions(server) {
49
+ const low = server.server;
50
+ if (typeof low._ondiscover !== "function") {
51
+ throw new Error("SDK mudou: Server._ondiscover sumiu — o anúncio de versões em src/discover.ts " +
52
+ "deixaria de aplicar em silêncio. Reveja o módulo contra a versão nova do " +
53
+ "@modelcontextprotocol/server antes de seguir.");
54
+ }
55
+ if (!Array.isArray(low._supportedProtocolVersions) ||
56
+ low._supportedProtocolVersions.some((v) => typeof v !== "string")) {
57
+ throw new Error("SDK mudou: Server._supportedProtocolVersions não é mais uma lista de strings — " +
58
+ "src/discover.ts não tem de onde tirar as revisões atendidas.");
59
+ }
60
+ const original = low._ondiscover.bind(low);
61
+ low._ondiscover = () => {
62
+ const base = original();
63
+ // LER NA HORA DA CHAMADA, não aqui na aplicação do patch. O
64
+ // `installDiscoverHandler` do SDK ACRESCENTA as revisões modernas a
65
+ // `_supportedProtocolVersions` por REATRIBUIÇÃO, já com o servidor
66
+ // construído: uma referência capturada agora ficaria presa ao array
67
+ // anterior. Foi o que aconteceu na primeira versão deste módulo — o anúncio
68
+ // saiu com as cinco revisões legadas e SEM a 2026-07-28, e a regra do
69
+ // auditor passou assim mesmo, porque ela só confere se a negociada está na
70
+ // lista. Trocar uma omissão por outra não é corrigir.
71
+ const agora = low._supportedProtocolVersions;
72
+ if (!Array.isArray(agora) || agora.some((v) => typeof v !== "string"))
73
+ return base;
74
+ // A lista vem do próprio SDK, nunca de literais nossas: anunciar uma
75
+ // revisão que o servidor não atende seria a mentira simétrica da que este
76
+ // módulo corrige.
77
+ return { ...base, supportedVersions: maisNovaPrimeiro(agora) };
78
+ };
79
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * As três coisas que um rastreador procura antes de decidir se vale a pena
3
+ * visitar um domínio: `robots.txt`, `sitemap.xml` e a chave do IndexNow.
4
+ *
5
+ * POR QUE ESTE ARQUIVO EXISTE. Em 31/08/2026 a landing foi reescrita para ser
6
+ * indexável — meta description, og:, JSON-LD, texto de verdade nos dois
7
+ * idiomas. E aí a pergunta certa apareceu: indexável por quem? Medido no mesmo
8
+ * dia, `sitemap.xml` respondia **404** nos quatro domínios do portfólio, e o
9
+ * `robots.txt` servido era o bloco de *content signals* que a Cloudflare injeta
10
+ * — 1.248 bytes que são só COMENTÁRIO, sem uma única diretiva ativa: nenhum
11
+ * `Sitemap:`, nenhum `Allow`. Não bloqueia nada, e também não convida nada. Os
12
+ * links de entrada para o domínio são finos, então esperar rastreamento
13
+ * espontâneo era esperar por algo que podia não acontecer — e, pior, ler o
14
+ * silêncio como resposta.
15
+ *
16
+ * O IndexNow é a peça que não depende de conta em painel de buscador: publica-se
17
+ * uma chave num arquivo do próprio domínio e avisa-se por HTTP quando uma URL
18
+ * muda. Bing, Yandex, Seznam e Naver honram o protocolo. A chave é PÚBLICA por
19
+ * desenho — ela prova posse do domínio justamente por estar servida nele.
20
+ *
21
+ * Aqui o sitemap tem DUAS URLs: a raiz e a página de termos. Privacidade fica
22
+ * de fora por decisão registrada em `tests/landing.test.ts` — páginas de
23
+ * obrigação não disputam a busca com a landing —, mas os termos descrevem o
24
+ * serviço e são o que um avaliador de diretório procura.
25
+ */
26
+ import { SITE_URL } from "./landing.js";
27
+ /**
28
+ * Chave do IndexNow. É PÚBLICA por desenho: prova posse do domínio por estar
29
+ * servida em `/<chave>.txt`, então versioná-la não é vazamento.
30
+ */
31
+ export const INDEXNOW_KEY = "a5286cd351e45bcfa411debef2957eaf";
32
+ /** Quando a superfície mudou pela última vez — vira o `lastmod` do sitemap. */
33
+ const ULTIMA_MUDANCA = "2026-08-31";
34
+ function texto(corpo, tipo = "text/plain; charset=utf-8") {
35
+ return new Response(corpo, {
36
+ status: 200,
37
+ headers: { "Content-Type": tipo, "Cache-Control": "public, max-age=3600" },
38
+ });
39
+ }
40
+ /**
41
+ * `robots.txt` PRÓPRIO. Ele diz duas coisas que o injetado não dizia: que o
42
+ * rastreamento é liberado, e onde está o sitemap. As rotas de máquina ficam
43
+ * fora do índice — `/mcp` é JSON-RPC e as demais são sondas operacionais.
44
+ */
45
+ export function robotsTxt() {
46
+ return [
47
+ "User-agent: *",
48
+ "Allow: /",
49
+ "Disallow: /mcp",
50
+ "Disallow: /health",
51
+ "Disallow: /status",
52
+ "Disallow: /metrics",
53
+ "Disallow: /.well-known/",
54
+ "",
55
+ `Sitemap: ${SITE_URL}/sitemap.xml`,
56
+ "",
57
+ ].join("\n");
58
+ }
59
+ export function sitemapXml() {
60
+ const urls = [
61
+ { loc: `${SITE_URL}/`, prioridade: "1.0", freq: "weekly" },
62
+ { loc: `${SITE_URL}/terms`, prioridade: "0.3", freq: "yearly" },
63
+ ];
64
+ const corpo = urls
65
+ .map((u) => ` <url>
66
+ <loc>${u.loc}</loc>
67
+ <lastmod>${ULTIMA_MUDANCA}</lastmod>
68
+ <changefreq>${u.freq}</changefreq>
69
+ <priority>${u.prioridade}</priority>
70
+ </url>`)
71
+ .join("\n");
72
+ return `<?xml version="1.0" encoding="UTF-8"?>
73
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
74
+ ${corpo}
75
+ </urlset>
76
+ `;
77
+ }
78
+ /**
79
+ * Responde as rotas de descoberta, ou `null` se o caminho não for uma delas.
80
+ * Chamada ANTES da auth: um rastreador não tem credencial.
81
+ */
82
+ export function discoveryResponseForPath(pathname) {
83
+ if (pathname === "/robots.txt")
84
+ return texto(robotsTxt());
85
+ if (pathname === "/sitemap.xml")
86
+ return texto(sitemapXml(), "application/xml; charset=utf-8");
87
+ if (pathname === `/${INDEXNOW_KEY}.txt`)
88
+ return texto(INDEXNOW_KEY);
89
+ return null;
90
+ }