MCP server for Brazilian Federal Senate open data (legislative, administrative, e-Cidadania).
One address for any MCP client. No sign-in needed on this server.
2 read48 other
Grouped by tool name. At most: only reads or looks up information.
Odel charges nothing per call. The publisher may charge for its own service.
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.
It exposes 69 tools, 4 prompts, and 5 resources across two domains:
Data comes from three official sources — the legislative open-data API, the administrative open-data API, and the e-Cidadania portal. All tool responses are in Portuguese (pt-BR). See CHANGELOG.md for release history.
Point a client at the endpoint and ask in plain language — English or Portuguese:
senado_search_votacoessenado_buscar_materias + senado_obter_materiasenado_ceapsThe answers come live from the Senate's official open-data APIs — exact figures with provenance, not numbers guessed from training data.
Other Brazilian MCP servers also touch the Senate — mcp-brasil and mcp-dados-br are multi-source aggregators in which the Senate is one source among many. The difference is coverage and how you run it. Measured in each one's source code on 2026-10-11:
| Senate tools | Covers | Where it runs | |
|---|---|---|---|
| senado-br-mcp (this) | 69 | legislative, administrative (CEAPS, staff, contracts, budget) and e-Cidadania, with provenance on every answer | hosted, no install or key; or npx |
| mcp-brasil 0.14.0 | 26 | legislative (senators, bills, floor votes, committees, agenda) — plus dozens of other government sources | local or self-hosted (Python) |
| mcp-dados-br 0.6.0 | 3 | sitting senators, bill list, vote tally — plus IBGE, BCB, INMET, Chamber of Deputies | local (Python) |
Pick an aggregator when the question crosses the Senate with the Chamber of Deputies, the electoral court (TSE) or the Transparency Portal. Pick this server for expenses (CEAPS), staff, contracts, budget, speeches, committee votes, vetoes or e-Cidadania — areas the others don't cover. Full per-area table, side-by-side examples and how to re-measure (in Portuguese): docs/comparativo-alternativas.md.
This is a remote, hosted, open-access server. To use it, point any MCP client at the Streamable HTTP endpoint — no install, no account, no API key, no configuration:
https://senado.sidneybissoli.com/mcp
For OpenAI Apps SDK submission and review, the Worker also exposes a curated MCP surface:
https://senado.sidneybissoli.com/mcp/openai-app-v2
This endpoint intentionally keeps the full public MCP server intact at /mcp, but limits tool discovery
to 27 high-signal, intent-oriented tools for ChatGPT app use. /mcp/openai-app remains available as a
legacy alias, but new ChatGPT app configurations should use /mcp/openai-app-v2 so clients fetch the
current tool schema. The tools still call the same handlers and return the same provenance envelope; only
the advertised surface is narrower. Any ChatGPT app listing should present this as an independent open-data
research app, not as an official Senate, OpenAI or ChatGPT connector.
For ChatGPT Apps, those 27 tools also advertise a shared MCP Apps UI template at
ui://senado-br-mcp/openai-app-dashboard-v2.html. The self-contained widget renders the returned
structuredContent as a compact dashboard with metrics, main records, and source/provenance, without
adding another model-visible data tool.
Public legal URLs for app review:
https://senado.sidneybissoli.com/privacyhttps://senado.sidneybissoli.com/termsChatGPT 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:
https://senado.sidneybissoli.com/mcp
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.
For clients that launch MCP servers as a command — and for one-command setup — use the
mcp-remote bridge. No build, no config, no key:
npx -y mcp-remote https://senado.sidneybissoli.com/mcp
Everything below Architecture (Prerequisites, Setup, Deploy) is only for optionally self-hosting your own instance — it is not required to use this public server.
Prefer not to route queries through a third-party host (e.g. a newsroom policy)? The same server
also runs as a local stdio process that talks directly to the official government APIs — same
69 tools, same provenance envelope, no Cloudflare in the loop. This is the npm/stdio channel, published
as senado-br-mcp.
Point a command-based client (Claude Desktop/Code, etc.) at the package — npm fetches and runs it, no clone or build:
{
"mcpServers": {
"senado-br": {
"command": "npx",
"args": ["-y", "senado-br-mcp"]
}
}
}
To run it directly or hack on it, use the source checkout instead:
git clone https://github.com/SidneyBissoli/senado-br-mcp-cloudflare
cd senado-br-mcp-cloudflare
npm install
npm run build
node dist/cli.js # serves MCP over stdio (Ctrl+C to stop)
Parity with the hosted server: the legislative and administrative tools are identical (same
upstream APIs, same throttle/cache/provenance) — locally the L1 Cloudflare cache is a no-op, but the L0
in-memory cache still works, so results are the same. The only difference is the e-Cidadania
list/corpus tools: without D1 they fall back to a live scrape of the ~5 REST highlights, flagged via
meta.fonte / possivelDesatualizacao; the detail tools (obter_*) are identical. Logs go to
stderr — stdout carries only the JSON-RPC protocol stream.
This repo bundles a Claude Agent Skill
at .claude/skills/senado-br/ that teaches Claude when to reach for
this server and how to use its 69 tools well — a themed tool map, common question→tool playbooks, the
provenance contract, and gotchas (dates, the codigoMateria bridge, e-Cidadania's open-set listing, pagination).
It points back to the server's own senado://catalogo / senado://guia resources rather than duplicating them.
Claude Code auto-discovers it when you work in this repo. To use it elsewhere, copy
.claude/skills/senado-br/ into your ~/.claude/skills/, or zip the folder and upload it in claude.ai
(Settings → Features). The skill assumes the senado-br MCP server is connected (hosted or via npx).
createMcpHandler from agents/mcp/mcp handles the full public server; /mcp/openai-app-v2 exposes a curated 27-tool profile plus a shared MCP Apps widget for OpenAI app review/submission (/mcp/openai-app remains as a legacy alias)@modelcontextprotocol/server 2.x (per-request McpServer instances; the v1 @modelcontextprotocol/sdk is not installed — agents still lists it as a peer, satisfied via legacy-peer-deps)Retry-After (portfolio shared fetch @sbissoli/mcp-upstream); every response's provenance carries the measured retrieval diagnostics (trips, attempts, anomalies)API_KEY secret; open access when unset). Constant-time comparison./metrics; per-tool call telemetry (selection, error rate, cache-vs-live) in Cloudflare Analytics Engine, PII-free/health and /status (version + last-deploy id/timestamp) make uptime and the current build verifiable; the status badge above pings the live endpointNot needed to use the server — it is already hosted at
https://senado.sidneybissoli.com/mcp(open access). Follow this section only if you want to run your own private instance.
engines.node)npm install
# Create the KV namespace
wrangler kv namespace create CACHE_KV
# Note the ID from the output, e.g.:
# { binding = "CACHE_KV", id = "abc123..." }
Replace the placeholder KV namespace ID:
[[kv_namespaces]]
binding = "CACHE_KV"
id = "YOUR_KV_NAMESPACE_ID_HERE"
Optionally set ALLOWED_ORIGIN to restrict CORS:
[vars]
ALLOWED_ORIGIN = "https://your-app.example.com"
The e-Cidadania pipeline needs a D1 database and a Cron Trigger (both already declared in wrangler.toml — replace the database ID):
[[d1_databases]]
binding = "ECIDADANIA_DB"
database_name = "senado-ecidadania"
database_id = "YOUR_D1_DATABASE_ID_HERE"
[triggers]
crons = ["0 */2 * * *"]
Create the database (paste the returned ID above) and apply the schema:
npx wrangler d1 create senado-ecidadania
npx wrangler d1 migrations apply senado-ecidadania --remote
The list tools fall back to live scraping when D1 is empty, so the server works before the first Cron run.
wrangler secret put API_KEY
# Clients must then send: Authorization: Bearer <key>
# When API_KEY is not set, the server is open access.
npm run dev
# Dev server runs locally on port 8787 (local only).
# The public MCP endpoint is https://senado.sidneybissoli.com/mcp
npm test # run all tests once
npm run test:watch # watch mode
npm run typecheck # tsc --noEmit
npm run deploy
# Serves at https://senado.sidneybissoli.com (custom domain) and
# https://senado-br-mcp.sidneybissoli.workers.dev (workers.dev fallback)
| Path | Methods | Description |
|---|---|---|
/ | GET | Landing page (pt-BR) — identifies the client behind the outgoing User-Agent: what the service is, load posture, contact (always public) |
/mcp | POST, GET, DELETE, OPTIONS | MCP Streamable HTTP endpoint (managed by createMcpHandler) |
/health | GET | Health check — returns ok (always public) |
/status | GET | JSON: status, version, and last-deploy metadata (deploy.id/tag/timestamp) — liveness + current build, no MCP handshake needed (always public) |
/metrics | GET | JSON counters: requests, tool calls, cache hits/misses, upstream calls/retries/errors, auth failures (always public) |
/.well-known/mcp/server-card.json | GET | MCP server card for directory scanners (Smithery): serverInfo, authentication, tools/resources/prompts — generated from the live full profile by @sbissoli/mcp-surface/card (always public) |
Every release publishes, in its MCP Registry entry,
the sha256 of the surface /mcp serves (initialize, tools, resources, prompts of the full
profile) and which methods answer there without a credential. A surface change without a
version bump fails the build; the fingerprint in the registry lets a client check the same thing
on its own side. Canonical form and procedure:
SPEC.md of @sbissoli/mcp-surface.
To check it yourself (Node 18+, no dependencies):
curl -sO https://raw.githubusercontent.com/SidneyBissoli/mcp-br-commons/main/packages/mcp-surface/exemplos/verify.mjs
node verify.mjs io.github.SidneyBissoli/senado-br-mcp-cloudflare
All requests go to POST /mcp with JSON-RPC 2.0 format.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "senado_listar_senadores",
"arguments": {
"uf": "SP",
"emExercicio": true
}
}
}
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "senado_buscar_materias",
"arguments": {
"palavraChave": "inteligência artificial",
"tramitando": true
}
}
}
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "senado_search_votacoes",
"arguments": {
"dias": 7
}
}
}
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "senado_ecidadania_listar_ideias",
"arguments": {
"ordenarPor": "apoios",
"ordem": "desc",
"status": "aberta"
}
}
}
The server consumes two classes of upstream endpoints from the Senado API:
.json suffix, PascalCase responses)Used by Groups A, E, F, H, I, J, K, L, M, N. The .json suffix is appended automatically by upstream.ts. None of these is marked deprecated upstream.
| Upstream path | Used by |
|---|---|
/senador/lista/atual | senado_listar_senadores |
/senador/lista/legislatura/{legislatura} | senado_listar_senadores (param legislatura) |
/senador/{codigo} (+ /mandatos) | senado_obter_senador (biografia + mandatos via chamada extra) |
/senador/{codigo}/licencas, /comissoes, /cargos, /historicoAcademico, /filiacoes, /profissao | senado_senador_historico (tipo enum) |
/senador/afastados | senado_senadores_afastados |
/senador/{codigo}/apartes | senado_discursos_senador (tipo=apartes) |
/comissao/lista/colegiados | senado_listar_comissoes (+ sigla-to-code resolution) |
/comissao/{codigo} | senado_obter_comissao (secao=resumo; numeric code, not sigla) |
/composicao/comissao/{codigo} (+ ?ativas=S) | senado_obter_comissao (secao=membros) |
/comissao/agenda/{data} | senado_agenda_comissoes |
/comissao/agenda/{dataInicio}/{dataFim} | senado_reunioes_comissao |
/comissao/reuniao/{codigoReuniao} | senado_reuniao_comissao |
/comissao/cpi/{sigla}/requerimentos | senado_requerimentos_cpi (upstream often empty even for active CPIs — empty result carries an aviso) |
/materia/distribuicao/autoria, /distribuicao/relatoria/{sigla} | senado_distribuicao_materias |
/plenario/agenda/dia/{data}, /agenda/mes/{data}, /agenda/cn/... | senado_agenda_plenario |
/plenario/resultado/{data}, /resultado/cn/{data}, /resultado/mes/{data} | senado_resultado_plenario |
/plenario/resultado/veto/{codigo} (+ /materia/, /dispositivo/) | senado_resultado_veto |
/plenario/votacao/orientacaoBancada/{data} (+ período) | senado_orientacao_bancada |
/plenario/encontro/{codigo} (+ /pauta, /resultado, /resumo) | senado_encontro_plenario |
/plenario/tiposSessao, /lista/tiposComparecimento, /lista/legislaturas | senado_tabelas_plenario |
/materia/vetos/{ano}, /vetos/aposrcn, /vetos/antesrcn, /vetos/encerrados | senado_vetos |
/taquigrafia/notas/{sessao|reuniao}/{id} | senado_notas_taquigraficas |
/taquigrafia/videos/{sessao|reuniao}/{id} | senado_videos_taquigrafia |
/senador/{codigo}/discursos | senado_discursos_senador |
/plenario/lista/discursos/{dataInicio}/{dataFim} | senado_discursos_plenario |
/discurso/texto-integral/{codigo} | senado_discurso_texto (plain text, fetched directly) |
/senador/lista/tiposUsoPalavra | senado_tabelas_referencia (tabela=tipos-uso-palavra) |
/composicao/lista/blocos | senado_listar_blocos |
/composicao/bloco/{codigo} | senado_obter_bloco |
/composicao/lideranca | senado_liderancas |
/composicao/mesaSF | senado_mesa (casa=senado) |
/composicao/mesaCN | senado_mesa (casa=congresso) |
/orcamento/lista | senado_orcamento_parlamentar (tipo=emendas) |
/orcamento/oficios | senado_orcamento_parlamentar (tipo=oficios) |
/legislacao/lista | senado_buscar_legislacao |
/legislacao/{codigo} | senado_obter_legislacao |
/legislacao/tiposNorma | senado_tabelas_referencia (tabela=tipos-norma) |
/votacaoComissao/comissao/{sigla} | senado_votacao_comissao (por=comissao) |
/votacaoComissao/parlamentar/{codigo} | senado_votacao_comissao (por=senador) |
/votacaoComissao/materia/{sigla}/{numero}/{ano} | senado_votacao_comissao (por=materia) |
/autor/lista/atual | senado_autores_atuais |
Used by Groups B, C, D. Dates must be in ISO format (YYYY-MM-DD) — tools accept YYYYMMDD and convert. The codigoMateria query param bridges legacy matéria codes to v3 processes.
| Upstream path | Used by |
|---|---|
/votacao | senado_obter_votacao, senado_search_votacoes, senado_votos_materia, senado_votacoes_senador |
/processo | senado_search_processos, senado_buscar_materias |
/processo/{id} | senado_obter_processo, senado_obter_materia (secao=detalhe/tramitacao) |
/processo/documento | senado_obter_materia (secao=textos) |
/processo/emenda | senado_processo_detalhe (secao=emendas) |
/processo/relatoria | senado_processo_detalhe (secao=relatorias), senado_obter_materia (rapporteur) |
/processo/prazo | senado_processo_detalhe (secao=prazos) |
/processo/{siglas,assuntos,classes,destinos,entes,tipos-*} | senado_tabelas_processo (12 reference tables) |
Used by Groups O, P, Q, R via admFetch (no .json suffix; HTTP 404 treated as empty collection). Base URL configurable via SENADO_ADM_BASE_URL.
| Upstream path | Used by |
|---|---|
/api/v1/senadores/despesas_ceaps/{ano} | senado_ceaps (~10 MB/year, cached + aggregated in-Worker) |
/api/v1/senadores/{auxilio-moradia,escritorios,aposentados} | senado_senadores_admin (tipo enum) |
/api/v1/servidores/servidores/{ativos,efetivos,comissionados,inativos} | senado_servidores |
/api/v1/servidores/remuneracoes/{ano}/{mes} | senado_remuneracoes_servidores (~5.5 MB/month) |
/api/v1/servidores/horas-extras/{ano}/{mes} | senado_horas_extras |
/api/v1/servidores/quantitativos/*, /previsao-aposentadoria, /api/v1/senadores/quantitativos/senadores | senado_pessoal_tabelas (quantitativos) |
/api/v1/servidores/{estagiarios,pensionistas,lotacoes,cargos} | senado_pessoal_tabelas (listas nominais) |
/api/v1/contratacoes/contratos (+ /{id}/aditivos) | senado_contratos, senado_contratacao_detalhe |
/api/v1/contratacoes/{tipo}/{id}/{itens,pagamentos,garantias} | senado_contratacao_detalhe |
/api/v1/contratacoes/licitacoes | senado_licitacoes |
/api/v1/contratacoes/terceirizados | senado_terceirizados |
/api/v1/contratacoes/empresas | senado_empresas_contratadas (~13 MB, requires filter) |
/api/v1/contratacoes/{atas_registro_preco,notas_empenho,menores_aprendizes} | senado_contratacoes_lista |
/api/v1/supridos/{ano} (+ atosConcessao, empenhos, movimentacoes, transacoes) | senado_suprimento_fundos |
senado.gov.br/bi-arqs/Arquimedes/Financeiro/{Despesa,Receitas}SenadoDadosAbertos.json | senado_execucao_orcamentaria (daily JSON feeds, Brazilian decimal strings normalized) |
The e-Cidadania list data is persisted in a D1 database (ecidadania_current/_history/_scrape_runs, discriminated by entidade; plus ecidadania_comentarios for the audiência comment level and ecidadania_detalhe_cursor for the resumable detail backfill — added in schema v2) and read from there instead of being scraped on every call. Three cadences write into it:
consultas, eventos, ideias; see below) — the source of truth. Daily (not weekly) because the first-seen series MIN(scraped_at) is the only measurable entry-rhythm signal and every skipped day permanently shortens it (ROADMAP Etapa 2, decisão D3);.github/workflows/verify-consultas-votos.yml — historical filename) for the consultas_votos acervo: the Senado republishes the Arquimedes CSV periodically (confirmed 2026-07-20), so the weekly run re-ingests the current vintage under the same anomaly guards as the other corpora (see below);0 */2 * * *, src/scraper/pipeline.ts → refreshEcidadania) does only a targeted metric splice of the ~5 REST highlights per live entity (restcolecaomaismateria/ideia/audiencia — votos/comentários/apoios), recorded as ok-metrica so it never re-breaks the corpus baseline and never touches the long tail. In v2 the eventos splice preserves the corpus's canonical comment count (the daily crawl is the source of truth for comentarios, so the splice can't ping-pong it against the degraded REST count).Both writers build payloads through the canonical buildXResumo builders + shared contentHash, so their rows are byte-identical. Each write:
ecidadania_current (one row per item — what the tools read),ecidadania_history only when an item's content_hash changes (time-series-ready),ecidadania_scrape_runs.An anomaly guard (src/scraper/anomaly.ts, classifyRun) ensures a failed or anomalous corpus run (zero rows, or fewer than ECIDADANIA_CORPUS_MIN_PCT% of the last good run) never overwrites the last good state.
The list / analysis tools (listar_*, consultas_analise, sugerir_tema_enquete, consultas_votos) read from D1 via resolveList (src/scraper/store.ts): D1-first. Because every entity is now a full corpus, a stale corpus is served from D1 flagged (possivelDesatualizacao: true) rather than collapsing to the ~5-item live highlights (the original coverage bug); the live scrape is reserved for an empty D1 (cold start, before the first weekly run). Staleness uses ECIDADANIA_CORPUS_STALE_MAX_MIN (~10 days). Every list response carries an additive meta (fonte, lastScrapedAt, possivelDesatualizacao) so callers always see the data's real age and never get stale data silently.
The detail tools (obter_*) stay live (HTML scraped with CSS-class-targeted regex) for freshness, and write their richer payload through to ecidadania_detalhe fire-and-forget (deduped by content_hash), so detail history accrues without adding latency to the response.
The three live e-Cidadania corpora are owned by the daily Action (.github/workflows/ingest-ecidadania.yml), each with its own scripts/ingest-ecidadania/index-*.ts orchestrator emitting batched out-*.sql the apply step bulk-loads:
consultas — open consultations (detailed below). In v2 each crawled matter is also enriched from its detail page (visualizacaomateria) for autoria/relator; those are immutable, so only rows not yet enriched are fetched.eventos — audiências/eventos from the principalaudiencia?p=N HTML listing; status comes straight from the listing block (no /processo bridge). In v2 every event is enriched from its detail page (canonical data/hora + comissaoNomeCompleto/local/descricao/pauta/convidados/videoUrl) and its AJAX comment fragment (canonical count + one ecidadania_comentarios row per comment, diffed against the stored hashes and emitted as out-eventos-comentarios-*.sql).ideias — ideias legislativas (~113.7k) from pesquisaideia?situacao=N&p=M, crawled per situacao bucket (the listing has no inline status) and emitted in ~10k-statement batches. In v2 the listing crawl preserves the immutable detail fields, and a separate resumable backfill (index-ideias-detalhe.ts, run via ingest:ecidadania:ideias-detalhe) fills them a chunk per run — because ~113.7k detail fetches don't fit one Action, it persists a cursor in ecidadania_detalhe_cursor and wraps around at the end.The fourth entity, consultas_votos, is a separate historical acervo of votes-by-UF parsed from the ~33 MB Arquimedes CSV (Proposições-com-votos.csv), aggregated to one record per matéria with a votosPorUf breakdown. The CSV's "dados atualizados até" stamp becomes the provenance data_vintage; it is excluded from the row hash (consultaVotoCore) so a re-ingest with unchanged votes doesn't churn _history. STATUS ATUAL is uniformly "Descontinuado", hence archival, not a migration of the open consultations. Served by senado_ecidadania_consultas_votos with provenance pointing at the CSV (ECIDADANIA_ARQUIMEDES). It is excluded from the daily job and owned by its own weekly ingest Action (.github/workflows/verify-consultas-votos.yml — the filename keeps the historical verify- prefix): the acervo was originally treated as a frozen single vintage (ROADMAP Etapa 2, decisão D1) and the weekly run only verified it, but on 2026-07-20 the Senado republished the CSV as a fresh vintage (+43 matérias, 648 updated), so the scheduled run now re-ingests the current vintage under the standard anomaly guards (empty/truncated CSV and the catastrophic floor still fail without writing; force dispatch overrides the floor). The script's verify mode (INGEST_CONSULTAS_VOTOS_VERIFY=1 / --verify) remains available as an on-demand integrity check.
The consultas job is the reference implementation:
consultas covers the full set of OPEN consultations — every matter currently in tramitação (~7.7k), not just the ~5 highlights. Confirmed on the first run: the pesquisamateria listing is in-tramitação-only, so closed/historical consultations are not captured by this source (a pre-ingestion historical backfill is out of scope). Three settled design decisions:
scripts/ingest-ecidadania/, run by a daily GitHub Action — .github/workflows/ingest-ecidadania.yml) that paginates the HTML listing (pesquisamateria?p=1..N, the only full-coverage source for open consultations) for ids + vote counts and bulk-loads D1; the Worker only reads. The brittle, long crawl is kept out of the request/Cron path./processo, not HTML. A consultation runs from presentation until the end of tramitação, so status is a function of the matter: aberta ⟺ the codigoMateria is in the /processo tramitando=S set, derived from robust JSON (never scraped). Every consultation enters as aberta (the listing only yields in-tramitação matters); on each complete run the job re-derives status for all stored rows by /processo membership (not by listing-absence, which can be transient), so a consultation whose matter leaves tramitação flips to encerrada. The encerrada/todas sets therefore grow over time; consultations that closed before the first ingestion aren't captured (out of scope). The list/analysis tools default to status: aberta.contentHash + the ConsultaResumo builder + classifyRun from src/scraper/, so its rows are byte-identical to the Cron's. The daily job owns the long tail; the 2h Cron keeps the ~5 hot/open highlights fresh via a targeted metric splice (recorded as ok-metrica, bypassing the corpus classifyRun baseline). Corpus freshness (possivelDesatualizacao) is computed from the last status='ok' run and uses a larger window (ECIDADANIA_CORPUS_STALE_MAX_MIN), and a stale consultas corpus is served from D1 flagged rather than collapsing back to the live highlights.Write guards on the load: an incomplete crawl (any page failed) or an incomplete /processo status universe writes only an erro run row; even a complete crawl is rejected by a catastrophic floor (ECIDADANIA_CORPUS_MIN_PCT, default 80% of the last good corpus) to guard against a degraded page — overridable with --force / INGEST_FORCE=1 for a legitimate large shrink. Run daily via the Action, or manually:
CLOUDFLARE_API_TOKEN=… npm run ingest:ecidadania # writes scripts/ingest-ecidadania/out.sql
npx wrangler d1 execute senado-ecidadania --remote --file=scripts/ingest-ecidadania/out.sql
| Layer | Storage | Scope | TTL range | Purpose |
|---|---|---|---|---|
| L0 | In-memory Map | Per-isolate | 30-300s | Ultra-fast, eliminates redundant requests within a Worker isolate |
| L1 | Cloudflare Cache API (caches.default) | Per-colo (PoP) | 60-600s | Shared across requests at the same edge location |
| L2 | KV (optional) | Global | Variable | Reserved for rare, low-write data |
| Category | L0 TTL | L1 TTL | Used for |
|---|---|---|---|
| STATIC | 300s | 600s | Legislation types, static reference |
| SEMI_STATIC | 120s | 300s | Party list, UF list, committee details |
| DYNAMIC | 30s | 60s | Agendas, recent votes, meeting lists |
| ON_DEMAND | 30s | 120s | Specific bill/senator/vote lookups |
MCP uses POST for all tools/call requests. Caching POST responses is not natively supported by the Cache API, which requires GET requests. The solution:
https://senado-br-mcp.internal/__cache/{tool}/{hash} is constructedcaches.default.match() and caches.default.put(), allowing standard Cache API operations on POST-originated dataThis caching happens at the tool level (inside each tool's callback), not at the MCP transport level.
Every tool attaches a provenance envelope so a result is traceable back to its official source — provenance is treated as a first-class part of the answer, not an optional extra (the audience is journalists and political-science researchers, for whom an un-sourced figure is unusable). The envelope implements the portfolio-wide provenance contract (@sbissoli/mcp-provenance): the server builds and validates a full canonical model per response, and emits its concise projection — a fixed-order block with explicit null for unknown fields; responses that merge several upstream reads also list each part with its own extraction instant (field_sources), and the block's retrieved_at is then the oldest of them. The block lives in structuredContent.provenance (parseable by clients; note the advertised per-tool output schema is permissive, so contract validation happens server-side at build time, in the package) and is mirrored as a compact source footer in the text content for clients that only render text — the data JSON itself is not duplicated with the envelope, to keep the per-response token cost low.
Coverage spans all four upstream sources, each with its own source/citation/license (in src/utils/provenance.ts):
legis.senado.leg.br/dadosabertosadm.senado.gov.br/adm-dadosabertossenado.gov.brwww12.senado.leg.br/ecidadaniaFields of the concise block (contract v1.3 since 3.18.5; per response — one tool, one source; keys in this fixed order; the first seven are always present, with null when the source does not expose the value; the optional ones are absent, never empty, when they do not apply):
| Field | Meaning |
|---|---|
source | Official source name (e.g. Senado Federal — Dados Abertos (Legislativo)) |
source_url | Canonical endpoint/item URL consulted (e.g. …/processo/{id}) |
data_vintage | Vintage/competência of the data (e.g. 2024-03-15, 2019) — named reference_period before v3.5.0 |
retrieved_at | ISO-8601 of the upstream extraction — carried through the cache, so it reflects when the data was actually fetched, not the build or the cache-hit time; on merged responses, the oldest of the parts |
retrieval | Measured diagnostics of the trip to the source (attempts, outcome), or null when nothing was fetched in the call (pure cache, local data) — since 3.11.0 (v1.1) |
citation | Ready-to-use citation string (human-readable) |
license | Source terms (Dados Abertos do Senado Federal) |
field_sources | Optional. Per-field sub-sources, each with its own extraction instant — only on responses that merge reads (v1.2, since 3.17.0; see below) |
derived / derivation_note | Optional. true + a fixed pt-BR note naming the calculation whenever the server computed values instead of copying them (since 3.19.0, the whole class): the estatisticas modes (distribution, aggregates, rankings); group sums, counts and averages — CEAPS valorTotal and its per-senator/type/month/supplier modes, the budget execution aggregates (por-ano, the default, included), the payroll resumo, the overtime valorTotal, the relatoria distribution, seated senators per party/UF, the historical e-Cidadania national totals; the payroll gross (bruto, sum of seven components); servants assigned to an org subtree (subordinadasA); a vote tally counted from nominal votes when the source publishes none (plenary open roll calls, committee votes without totals); e-Cidadania percentages, consensus/polarisation rankings and the estimated idea creation date; the current legislature's period. Absent when every number is the source's own (raw lists, detalhe of the budget, published tallies). The individual values cited remain the Senate's |
revision | Whether the figure may still change at the source: status is current for every Senate source (the version in force at extraction; none is claimed final), and note says, for the administrative and budget sources, that the Senate may correct published data later |
No Senate source publishes notices alongside the data, so notices (also part of v1.3) does not appear. The text footer gains a line only for exceptions — here, the computed-values line on the estatisticas modes.
The canonical model behind the block also carries dataset.id (item/series identifier, e.g. codigoMateria=137808) and api_version; those are validated on every build and inform attribution (below), but are not part of the concise projection.
In addition to the provenance envelope, structuredContent carries a top-level attribution list — the distinct source URLs behind the response. This mirrors the naming proposed in modelcontextprotocol#711 (where attribution is a list of source references at the response level), so the server stays forward-compatible if that RFC lands; the richer provenance object remains this server's own extension.
Out-of-band mirror in _meta. The same provenance and attribution are also mirrored on the result's _meta under namespaced keys (com.sidneybissoli.senado/provenance and com.sidneybissoli.senado/attribution). The MCP spec keeps _meta for metadata about a result that should not steer the model, which is where the still-incubating trust/attribution work from #711 (split into an experimental extensions track, not yet in core) points; mirroring there gives audit and UI consumers the provenance without reading the model-facing data channel, at no model-token cost, while structuredContent keeps it visible so the model can cite the source. The mirror survives the ChatGPT-app profile minimizer (which only strips structuredContent.meta).
Field-level granularity. Most tools are single-source, so one envelope suffices. The few that merge reads in one response fill field_sources — a list of { fields, source_url, dataset_id, data_vintage, retrieved_at, served_from_cache } attributing specific output fields to their real origin, and the block's retrieved_at is the oldest of them. served_from_cache is true/false for reads through the server cache and null where the notion does not apply (the bundled org-chart snapshot, the e-Cidadania corpus in D1). The merging tools (since 3.18.2): senado_obter_materia secao=detalhe (/processo/{id} + ementa from /processo + relator from /processo/relatoria), senado_obter_senador and the Deep Research fetch of a senator (biography + mandatos/emExercicio from /senador/{codigo}/mandatos), senado_reunioes_comissao over a window that crosses the year (one read per year), senado_servidores with subordinadasA (payroll + org-chart snapshot), senado_suprimento_fundos statistics on atos-concessao (acts + beneficiary names from the supridos registry), senado_ecidadania_obter_evento (live detail + comentarios from the corpus) and senado_ecidadania_sugerir_tema_enquete (consultas and ideias corpora). Only a read that becomes a field of the answer is a sub-source: a read that only resolves or checks an identifier (a committee acronym → its code, the parent check in senado_contratacao_detalhe) is left out, otherwise a cached lookup table would make the whole answer claim to be as old as it. attribution always lists every distinct underlying source_url.
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, or the live scrape instant when the corpus is empty or stale and the list falls back to the portal (since 3.18.3; before, the stale corpus's instant leaked into the live answer) — 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.
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.
Beyond the live server, this project publishes a frozen, versioned, citable dataset of the e-Cidadania participation layer (public consultations, legislative ideas, interactive events + their comments, historical votes by state) — the layer the R package congressbr never covered. Each value carries a per-field provenance envelope ({ value, sourceEndpoint, sourceField, retrievedAt, license, schemaVersion }); the data license (Dados Abertos do Senado Federal) is kept separate from the code license (MIT).
CITATION.cff (dataset; cite the version-DOI of the snapshot you used, the concept-DOI for the dataset across versions).CHANGELOG-dataset.md (cumulative, append-only; binds each release to its schemaVersion).docs/dataset-dictionary.md (generated from src/dataset/schema.ts, the single source of truth).LICENSE-DATA.md.docs/release-runbook.md; machinery in src/dataset/, scripts/build-dataset/, and .github/workflows/release-dataset.yml.schemaVersion 2.0.0)Each release ships one NDJSON resource per entity (one HarmonizedRecord per line: identity + a provenance envelope per field), plus a datapackage.json manifest and a copy of the dictionary. Five resources:
Resource (*.ndjson) | Grain | Key variables | Source(s) |
|---|---|---|---|
consultas | 1 public consultation (matéria) | materia, ementa, votosSim/votosNao/totalVotos, percentual*, autoriaⁿ, relatorⁿ, status, url, firstSeenAt | pesquisamateria listing + detail (visualizacaomateria)ⁿ + /processo?tramitando=S for status |
ideias | 1 legislative idea (~113.7k) | titulo, apoios, status, dataPublicacaoⁿ (the support deadline — the only date the portal publishes; creation ≈ deadline − 120 days, see the dictionary), autorUfⁿ, descricaoⁿ, plConvertidoⁿ, url, firstSeenAt | pesquisaideia listing + detail (visualizacaoideia) via a resumable backfillⁿ |
eventos | 1 interactive event (audiência) | titulo, dataᶜ, horaᶜ, comissao, comissaoNomeCompletoⁿ, localⁿ, descricaoⁿ, pautaⁿ, convidadosⁿ, videoUrlⁿ, comentariosᶜ, status, url, firstSeenAt | principalaudiencia listing + detail (visualizacaoaudiencia)ⁿ + AJAX comment fragmentᶜ |
eventos_comentariosⁿ | 1 comment (comment-level) | eventoId, comentarioId, uf, texto, data, hora, momentoVideoUrl, convidadoAssociado | AJAX fragment ajaxcolecaocomentarioaudiencia?audienciaId= |
consultas_votos | 1 matéria (historical acervo) | materia, ementa, autoria, votosSim/votosNao/totalVotos, votosPorUf, status, url, referencePeriod | Arquimedes CSV Proposições-com-votos.csv (re-ingested weekly) |
ⁿ = new/reopened in v2 · ᶜ = source corrected to the canonical one in v2. What v2 (2.0.0) changed — the ingestion moved from listing-only to listing + detail (+ AJAX comments for events):
data/hora now come from the detail page (canonical — the estudo A3 found the listing 57% divergent on hora), plus six new detail fields (comissaoNomeCompleto, local, descricao, pauta, convidados, videoUrl). comentarios is now the canonical AJAX count (the listing count was 0-spurious in 82% of events, capturing only ~6.7% of engagement).eventos_comentarios — one row per audiência comment, the participation signal nobody else publishes versioned.ideias (dataPublicacao, autorUf, descricao, plConvertido) and consultas (autoria, relator) — previously always null by design.docs/schema-v2-inventario.md for the field-by-field rationale (approved target).The frozen NDJSON is not committed (built from the sovereign D1 corpus on demand); a tagged dataset-v* release attaches the tarball + SHA256SUMS + release.json and archives them on Zenodo.
| Tool | Description |
|---|---|
senado_tabelas_referencia | Tabelas de referência via tabela enum: tipos-materia, partidos, ufs, legislatura-atual, tipos-norma, tipos-uso-palavra |
| Tool | Description |
|---|---|
senado_listar_senadores | Lista senadores em exercício/por legislatura, com filtros nome (busca parcial sem acento), uf e partido |
senado_obter_senador | Detalhe biográfico de um senador: bio, mandatos, partido, contato |
senado_votacoes_senador ⊕ | Como um senador votou em cada matéria (via v3 /votacao) |
senado_senador_historico | Histórico funcional via tipo enum: licencas, comissoes, cargos, historico-academico, filiacoes, profissoes |
senado_senadores_afastados | Senadores atualmente afastados (fora de exercício) |
| Tool | Description |
|---|---|
senado_buscar_materias ⊕ | Busca matérias por tipo, número, ano, palavra-chave, autor ou tramitação (via v3 /processo) |
senado_obter_materia ⊕ | Dados de uma matéria via secao enum: detalhe (situação/relator), tramitacao (histórico) ou textos (documentos) |
| Tool | Description |
|---|---|
senado_search_processos ⊕ | Busca processos legislativos (complementar à busca de matérias) |
senado_obter_processo ⊕ | Detalhes completos de um processo legislativo específico |
senado_processo_detalhe | Aspecto de um processo via secao enum: emendas, relatorias ou prazos |
senado_autores_atuais | Parlamentares autores de processos em tramitação, ordenados por produção |
senado_tabelas_processo | 12 tabelas de referência (siglas, assuntos, classes, tipos-*) via tabela enum |
| Tool | Description |
|---|---|
senado_obter_votacao ⊕ | Detalhes de uma votação com votos nominais. Aceita codigoVotacao (codigoSessao da sessão plenária). |
senado_votos_materia ⊕ | Votações de uma matéria (via v3 /votacao?codigoMateria), com votos nominais opcionais |
senado_search_votacoes ⊕ | Busca/listagem flexível de votações do plenário por dias, período, processo, matéria ou senador |
| Tool | Description |
|---|---|
senado_listar_comissoes | Lista comissões (colegiados) ativas, filtráveis por tipo |
senado_obter_comissao | Dados de uma comissão via secao enum: resumo (mesa/totais) ou membros (composição). Resolve sigla para código internamente. |
senado_reunioes_comissao | Reuniões de uma comissão num período (lida com intervalos entre anos) |
senado_agenda_comissoes | Agenda de reuniões de todas as comissões numa data |
senado_reuniao_comissao | Detalhe completo de uma reunião: partes, itens, convidados, resultados, links pauta/ata |
senado_requerimentos_cpi | Requerimentos protocolados numa CPI em atividade, paginados (upstream costuma vir vazio mesmo para CPIs ativas; retorno vazio traz aviso) |
senado_distribuicao_materias | Estatísticas de carga por senador numa comissão: autoria ou relatoria |
| Tool | Description |
|---|---|
senado_agenda_plenario | Plenary schedule — by day, month or Congress (escopo dia/mes/cn) |
senado_resultado_plenario | Session results: items deliberated, opinions, outcomes (SF/CN/month) |
senado_orientacao_bancada | Party leadership voting instructions per vote, with tallies |
senado_vetos | Presidential vetoes by year or tramitation status |
senado_resultado_veto | Nominal veto vote results (by veto, vetoed bill or device) |
senado_encontro_plenario | Legislative session detail, agenda items, results or summary |
senado_tabelas_plenario | Session types, attendance types, legislatures list |
| Tool | Description |
|---|---|
senado_ecidadania_listar_consultas | Consultas públicas (conjunto completo das abertas — matérias em tramitação) com votação sim/não; filtro status (padrão aberta) |
senado_ecidadania_obter_consulta | Detalhe de uma consulta: votos, autor, relator, comentários |
senado_ecidadania_consultas_analise | Analisa o conjunto completo de consultas abertas via modo (consenso/polarizada); status padrão aberta |
senado_ecidadania_listar_ideias | Ideias legislativas de cidadãos; ranking das mais apoiadas via ordenarPor: apoios |
senado_ecidadania_obter_ideia | Detalhe de uma ideia: texto, apoios, data em que foi proposta, linha do tempo (meta, Sugestão, decisão da CDH, encerramento) e proposições em que virou |
senado_ecidadania_listar_eventos | Eventos interativos (audiências, sabatinas, lives); ranking dos mais comentados via ordenarPor |
senado_ecidadania_obter_evento | Detalhe de um evento: pauta, convidados, link de vídeo |
senado_ecidadania_sugerir_tema_enquete | Sugere temas para enquete mensal a partir de critérios configuráveis |
senado_ecidadania_consultas_votos | Acervo histórico de votos das consultas com quebra por UF (CSV Arquimedes); ranking por total/sim/nao, filtro uf/materia |
| Tool | Description |
|---|---|
senado_discursos_senador | Pronunciamentos de um senador via tipo enum: discursos (próprios) ou apartes (intervenções) |
senado_discursos_plenario | Todos os discursos em plenário num intervalo de datas |
senado_discurso_texto | Texto integral de um pronunciamento/discurso específico |
| Tool | Description |
|---|---|
senado_listar_blocos | Blocos parlamentares do Senado e seus partidos membros |
senado_obter_bloco | Detalhes de um bloco parlamentar específico |
senado_liderancas | Lideranças do Senado/Câmara/Congresso (líderes, vice-líderes) com o bloco/partido liderado, filtráveis |
senado_mesa | Membros da Mesa Diretora via casa enum: senado (Mesa do SF) ou congresso (Mesa do CN) |
| Tool | Description |
|---|---|
senado_orcamento_parlamentar | Emendas parlamentares ao orçamento via tipo enum: emendas (lotes por autor) ou oficios (indicação de destino — filtrável por ano da emenda, paginado, incluirEmendas opcional) |
| Tool | Description |
|---|---|
senado_buscar_legislacao | Busca normas jurídicas federais por tipo, número, ano ou data (ao menos um obrigatório) |
senado_obter_legislacao | Detalhes de uma norma jurídica federal específica |
| Tool | Description |
|---|---|
senado_votacao_comissao | Votações em comissões via por enum: comissao, senador ou materia; período opcional |
| Tool | Description |
|---|---|
senado_notas_taquigraficas | Official transcripts of plenary sessions or committee meetings — summary mode with excerpts, full-text mode paginated in blocks, speaker filter |
senado_videos_taquigrafia | Video/audio units per session or meeting, with speaker and media links |
| Tool | Description |
|---|---|
senado_ceaps | CEAPS parliamentary quota expenses by year — aggregated by senator, expense type, month or supplier, or itemized detail; estatisticas=true returns whole-set distribution stats (min/max/mean/median/percentiles) + top/bottom ranking, or a group ranking by total spend via agruparPor (senator/type/month/supplier) with topN; filters by senator/month/type/supplier |
senado_senadores_admin | Dados administrativos dos senadores via tipo enum: auxilio-moradia, escritorios-apoio ou aposentados |
| Tool | Description |
|---|---|
senado_servidores | Civil servants by status (active/effective/commissioned/inactive), filterable by name, unit, position |
senado_remuneracoes_servidores | Monthly payroll — summary by payroll type or per-person composition with computed gross; estatisticas=true returns whole-payroll stats (min/max/mean/median/percentiles) + top/bottom ranking, with campo, consolidarPorServidor, agruparPor and topN |
senado_horas_extras | Overtime payments by month with totals; estatisticas=true returns whole-set distribution stats (min/max/mean/median/percentiles) + top/bottom ranking, or a per-servant ranking by summed value via agruparPor (name/competência), with topN |
senado_pessoal_tabelas | Tabelas de pessoal via tabela enum: quantitativos (pessoal, cargos-funcoes, previsao-aposentadoria, senadores) e listas (estagiarios, pensionistas, lotacoes, cargos) |
| Tool | Description |
|---|---|
senado_contratos | Contracts filtered in-Worker over the full base (accent-insensitive): supplier, CNPJ, year, number, object, labor |
senado_contratacao_detalhe | Items, payments, guarantees, amendments or activations of a contract/ata/empenho |
senado_licitacoes | Biddings by number or object text |
senado_terceirizados | Outsourced collaborators by name, company or unit |
senado_empresas_contratadas | Companies contracting with the Senate (requires name/CNPJ filter) |
senado_contratacoes_lista | Price-registration atas, commitment notes, young apprentices |
| Tool | Description |
|---|---|
senado_suprimento_fundos | Petty-cash advances by year: recipients, concession acts, commitments, movements, card transactions; estatisticas=true (tipo transacoes/empenhos/atos-concessao) returns whole-set distribution stats (min/max/mean/median/percentiles) + top/bottom ranking, or a ranking by summed value via agruparPor (e.g. supplier), with contextual campo and topN |
| Tool | Description |
|---|---|
senado_execucao_orcamentaria | Budget execution since 2013 (allocation, committed/settled/paid) and own revenues since 2012 (forecast vs collected) — aggregated by year, action, expense group, source or revenue origin; estatisticas=true returns whole-set distribution stats (min/max/mean/median/percentiles) + top/bottom ranking, or a group ranking by summed campo via agruparPor, with campo (default paid / collected) and topN |
Reads a bundled snapshot of the Senate's organizational tree (crawled from the institutional portal down to serviço level by npm run ingest:estrutura), since the open-data API only publishes units down to Secretaria and never links a leaf unit to its parent. Units the portal lists only by name, without a page of their own (the CONLEG/CONORF núcleos), are captured as synthetic nodes from the page's indented listing. Congress-wide bodies where the servant registry records lotações (CMO, CPCMS, CMMC) come from a curated complement (src/estrutura/complemento-cn.ts, public source: congressonacional.leg.br) under a separate Congresso Nacional root (CN) — never under the Senate tree, so subordinadasA: "DGER" excludes them while subordinadasA: "CN" counts them. Servants recorded under the situational pseudo-units "Servidores Afastados/em Trânsito - SF" are reported separately as afastadosOuEmTransito by senado_servidores.
| Tool | Description |
|---|---|
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). |
The OpenAI Deep Research contract: 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, the shared output envelope — open data plus required provenance + attribution —, 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.
| Tool | Description |
|---|---|
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. |
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. |
Total: 69 tools
Reusable pt-BR workflow templates (MCP prompts capability), defined in src/prompts.ts:
| Prompt | Args | What it guides |
|---|---|---|
senado_gastos_senador | senador, ano | Resolve o senador e agrega/detalha despesas CEAPS. |
senado_tramitacao_materia | sigla, numero, ano | Obtém situação atual + histórico de tramitação da matéria. |
senado_votos_senador | senador, periodo? | Lista os votos nominais do senador no período. |
senado_panorama_ecidadania | — | Consolida consultas (consenso/polarização), ideias e eventos populares. |
Static context documents/tables (MCP resources capability), defined in src/resources.ts:
| URI | Type | Content |
|---|---|---|
senado://guia | markdown | Visão geral e qual ferramenta usar por objetivo. |
senado://catalogo | markdown | As 69 ferramentas agrupadas por domínio. |
senado://glossario | markdown | Siglas e termos do Senado (PEC, CEAPS, CCJ, RCN…). |
senado://tabelas/tipos-materia | json | Tipos de proposição (sigla/nome/descrição). |
senado://tabelas/ufs | json | As 27 unidades federativas. |
src/
├── index.ts # Worker entrypoint (fetch handler + scheduled/Cron handler)
├── server.ts # McpServer factory (creates per-request instance)
├── auth.ts # Optional Bearer token auth (constant-time compare)
├── metrics.ts # In-memory counters served at /metrics
├── types.ts # Env, cache categories, safeguard constants
├── cache/
│ ├── l0-memory.ts # In-memory Map cache with TTL + LRU eviction
│ ├── l1-cache-api.ts # Cloudflare Cache API wrapper (synthetic GET keys)
│ └── manager.ts # Cache orchestrator (L0 → L1 → upstream)
├── throttle/
│ ├── token-bucket.ts # Token bucket rate limiter (global + per-client)
│ └── upstream.ts # Upstream fetch with concurrency limit, retry, timeout
├── scraper/
│ ├── ecidadania.ts # Isolated e-Cidadania scraper (REST lists + regex HTML detail; buildConsultaResumo)
│ ├── pipeline.ts # 2h Cron: targeted highlight metric splice (consultas/eventos/ideias); corpora owned by the off-Worker jobs
│ ├── anomaly.ts # Run classification (anomalous run never overwrites current)
│ └── store.ts # D1 reads (resolveList + per-entity staleness, lastGoodRunAt) + detail write-through
├── instrument.ts # Per-tool call telemetry (in-memory + Analytics Engine)
├── utils/
│ ├── logger.ts # Structured JSON logging
│ └── validation.ts # toolResult, toolError, errorFrom, buildParams, en