{
  "markdown": "# Senado Brasil MCP Server\n\n![Cloudflare Workers](https://img.shields.io/badge/Cloudflare%20Workers-F38020?logo=cloudflare&logoColor=white)\n![MCP](https://img.shields.io/badge/MCP-Streamable%20HTTP-1f6feb)\n![Tools](https://img.shields.io/badge/tools-69-2ea44f)\n[![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)\n[![MCP Registry](https://img.shields.io/badge/MCP-Registry-blue)](https://registry.modelcontextprotocol.io)\n[![LobeHub](https://lobehub.com/badge/mcp/sidneybissoli-senado-br-mcp-cloudflare)](https://lobehub.com/mcp/sidneybissoli-senado-br-mcp-cloudflare)\n[![smithery badge](https://smithery.ai/badge/sidneybissoli/senado-br-mcp-cloudflare)](https://smithery.ai/servers/sidneybissoli/senado-br-mcp-cloudflare)\n[![senado-br-mcp-cloudflare MCP server](https://glama.ai/mcp/servers/SidneyBissoli/senado-br-mcp-cloudflare/badges/score.svg)](https://glama.ai/mcp/servers/SidneyBissoli/senado-br-mcp-cloudflare)\n[![GitHub stars](https://img.shields.io/github/stars/SidneyBissoli/senado-br-mcp-cloudflare?style=flat&logo=github)](https://github.com/SidneyBissoli/senado-br-mcp-cloudflare)\n[![GitHub Sponsors](https://img.shields.io/github/sponsors/SidneyBissoli?logo=githubsponsors&label=Sponsor&color=db61a2)](https://github.com/sponsors/SidneyBissoli)\n[![License: MIT](https://img.shields.io/github/license/SidneyBissoli/senado-br-mcp-cloudflare)](LICENSE)\n[![Status](https://img.shields.io/website?url=https%3A%2F%2Fsenado.sidneybissoli.com%2Fhealth&up_message=online&down_message=offline&label=status)](https://senado.sidneybissoli.com/status)\n\n🇧🇷 [Leia em Português](README.pt-BR.md)\n\nA **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.\n\nIt exposes **69 tools**, **4 prompts**, and **5 resources** across two domains:\n\n- **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.\n- **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.\n\nData comes from three official sources — the [legislative open-data API](https://legis.senado.leg.br/dadosabertos/), the [administrative open-data API](https://adm.senado.gov.br/adm-dadosabertos/swagger-ui/index.html), and the e-Cidadania portal. All tool responses are in Portuguese (pt-BR). See [CHANGELOG.md](CHANGELOG.md) for release history.\n\n## See it in action\n\nPoint a client at the endpoint and ask in plain language — English or Portuguese:\n\n- *\"How did São Paulo's senators vote in the most recent floor votes?\"* → `senado_search_votacoes`\n- *\"Show the legislative progress of PEC 45/2019 (a constitutional amendment proposal).\"* → `senado_buscar_materias` + `senado_obter_materia`\n- *\"How much was spent on the CEAPS parliamentary allowance in 2024, broken down by expense type?\"* → `senado_ceaps`\n\nThe answers come live from the Senate's official open-data APIs — exact figures with provenance, not numbers guessed from training data.\n\n## Use it (hosted — no setup)\n\nThis is a **remote, hosted, open-access** server. To use it, point any MCP client at the Streamable\nHTTP endpoint — **no install, no account, no API key, no configuration**:\n\n```\nhttps://senado.sidneybissoli.com/mcp\n```\n\n### OpenAI / ChatGPT app surface\n\nFor OpenAI Apps SDK submission and review, the Worker also exposes a curated MCP surface:\n\n```\nhttps://senado.sidneybissoli.com/mcp/openai-app-v2\n```\n\nThis endpoint intentionally keeps the full public MCP server intact at `/mcp`, but limits tool discovery\nto 27 high-signal, intent-oriented tools for ChatGPT app use. `/mcp/openai-app` remains available as a\nlegacy alias, but new ChatGPT app configurations should use `/mcp/openai-app-v2` so clients fetch the\ncurrent tool schema. The tools still call the same handlers and return the same provenance envelope; only\nthe advertised surface is narrower. Any ChatGPT app listing should present this as an independent open-data\nresearch app, not as an official Senate, OpenAI or ChatGPT connector.\n\nFor ChatGPT Apps, those 27 tools also advertise a shared MCP Apps UI template at\n`ui://senado-br-mcp/openai-app-dashboard-v2.html`. The self-contained widget renders the returned\n`structuredContent` as a compact dashboard with metrics, main records, and source/provenance, without\nadding another model-visible data tool.\n\nPublic legal URLs for app review:\n\n- Privacy policy: `https://senado.sidneybissoli.com/privacy`\n- Terms of use: `https://senado.sidneybissoli.com/terms`\n\n### ChatGPT (Deep Research)\n\nChatGPT 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:\n\n```\nhttps://senado.sidneybissoli.com/mcp\n```\n\n`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.\n\n### Install (any client)\n\nFor clients that launch MCP servers as a command — and for one-command setup — use the\n[`mcp-remote`](https://www.npmjs.com/package/mcp-remote) bridge. **No build, no config, no key:**\n\n```bash\nnpx -y mcp-remote https://senado.sidneybissoli.com/mcp\n```\n\n- **One-click (LobeHub):** open the [server page](https://lobehub.com/mcp/sidneybissoli-senado-br-mcp-cloudflare) and click **Install**.\n- **Native remote URL** (Claude Desktop/Code and other Streamable-HTTP clients): see [Connecting MCP Clients](#connecting-mcp-clients).\n\nEverything below *Architecture* (Prerequisites, Setup, Deploy) is **only for optionally self-hosting your\nown instance** — it is **not** required to use this public server.\n\n## Run locally (npx · stdio)\n\nPrefer not to route queries through a third-party host (e.g. a newsroom policy)? The **same server**\nalso runs as a **local stdio process** that talks **directly to the official government APIs** — same\n69 tools, same provenance envelope, no Cloudflare in the loop. This is the npm/stdio channel, published\nas [`senado-br-mcp`](https://www.npmjs.com/package/senado-br-mcp).\n\nPoint a command-based client (Claude Desktop/Code, etc.) at the package — npm fetches and runs it,\nno clone or build:\n\n```json\n{\n  \"mcpServers\": {\n    \"senado-br\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"senado-br-mcp\"]\n    }\n  }\n}\n```\n\nTo run it directly or hack on it, use the source checkout instead:\n\n```bash\ngit clone https://github.com/SidneyBissoli/senado-br-mcp-cloudflare\ncd senado-br-mcp-cloudflare\nnpm install\nnpm run build\nnode dist/cli.js   # serves MCP over stdio (Ctrl+C to stop)\n```\n\n**Parity with the hosted server:** the legislative and administrative tools are **identical** (same\nupstream APIs, same throttle/cache/provenance) — locally the L1 Cloudflare cache is a no-op, but the L0\nin-memory cache still works, so results are the same. The **only** difference is the e-Cidadania\nlist/corpus tools: without D1 they fall back to a live scrape of the ~5 REST highlights, flagged via\n`meta.fonte` / `possivelDesatualizacao`; the detail tools (`obter_*`) are identical. Logs go to\n**stderr** — stdout carries only the JSON-RPC protocol stream.\n\n## Agent Skill (optional)\n\nThis repo bundles a Claude [Agent Skill](https://platform.claude.com/docs/en/docs/agents-and-tools/agent-skills/overview)\nat [`.claude/skills/senado-br/`](.claude/skills/senado-br/SKILL.md) that teaches Claude **when** to reach for\nthis server and **how** to use its 69 tools well — a themed tool map, common question→tool playbooks, the\nprovenance contract, and gotchas (dates, the `codigoMateria` bridge, e-Cidadania's open-set listing, pagination).\nIt points back to the server's own `senado://catalogo` / `senado://guia` resources rather than duplicating them.\n\nClaude Code auto-discovers it when you work in this repo. To use it elsewhere, copy\n`.claude/skills/senado-br/` into your `~/.claude/skills/`, or zip the folder and upload it in claude.ai\n(Settings → Features). The skill assumes the `senado-br` MCP server is connected (hosted or via npx).\n\n## Architecture\n\n- **Runtime:** Cloudflare Workers (ESM)\n- **Transport:** Streamable HTTP (MCP spec 2025-03-26) via `createMcpHandler` from `agents/mcp`\n- **Protocol:** MCP over JSON-RPC — `/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)\n- **SDK:** `@modelcontextprotocol/server` 2.x (per-request McpServer instances; the v1 `@modelcontextprotocol/sdk` remains only as a peer of `agents`, dev-time)\n- **Validation:** Zod schemas for all tool inputs\n- **Caching:** 2-layer (L0 memory + L1 Cache API) with SHA-256 keying\n- **e-Cidadania store:** D1 database refreshed by a Cron Trigger (every 2h) — list tools read from D1 with a live-scrape fallback and a staleness flag; detail tools stay live with write-through (see [e-Cidadania](#e-cidadania-d1-backed-cron-refreshed))\n- **Rate limiting:** Token bucket — global (8 req/s) + per-client (2 req/s)\n- **Upstream throttle:** Max 6 concurrent requests, 10s timeout, retry with exponential backoff\n- **Auth:** Optional Bearer token (set the `API_KEY` secret; open access when unset). Constant-time comparison.\n- **Observability:** Structured JSON logging + in-memory counters at `/metrics`; per-tool call telemetry (selection, error rate, cache-vs-live) in Cloudflare Analytics Engine, PII-free\n- **Liveness:** Runs on Cloudflare's own global network behind a custom domain — no third-party host that can go dark. Public `/health` and `/status` (version + last-deploy id/timestamp) make uptime and the current build verifiable; the **status** badge above pings the live endpoint\n- **Tests:** Vitest unit tests for parsers, helpers, cache, throttle, and auth\n\n## Self-hosting (optional)\n\n> **Not needed to use the server** — it is already hosted at `https://senado.sidneybissoli.com/mcp`\n> (open access). Follow this section only if you want to run your **own** private instance.\n\n### Prerequisites\n\n- Node.js 22+ (`engines.node`)\n- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/) v4+\n- Cloudflare account\n\n### Setup\n\n#### 1. Install dependencies\n\n```bash\nnpm install\n```\n\n#### 2. Create KV namespace\n\n```bash\n# Create the KV namespace\nwrangler kv namespace create CACHE_KV\n\n# Note the ID from the output, e.g.:\n# { binding = \"CACHE_KV\", id = \"abc123...\" }\n```\n\n#### 3. Configure wrangler.toml\n\nReplace the placeholder KV namespace ID:\n\n```toml\n[[kv_namespaces]]\nbinding = \"CACHE_KV\"\nid = \"YOUR_KV_NAMESPACE_ID_HERE\"\n```\n\nOptionally set `ALLOWED_ORIGIN` to restrict CORS:\n\n```toml\n[vars]\nALLOWED_ORIGIN = \"https://your-app.example.com\"\n```\n\nThe e-Cidadania pipeline needs a **D1 database** and a **Cron Trigger** (both already declared in `wrangler.toml` — replace the database ID):\n\n```toml\n[[d1_databases]]\nbinding = \"ECIDADANIA_DB\"\ndatabase_name = \"senado-ecidadania\"\ndatabase_id = \"YOUR_D1_DATABASE_ID_HERE\"\n\n[triggers]\ncrons = [\"0 */2 * * *\"]\n```\n\nCreate the database (paste the returned ID above) and apply the schema:\n\n```bash\nnpx wrangler d1 create senado-ecidadania\nnpx wrangler d1 migrations apply senado-ecidadania --remote\n```\n\nThe list tools fall back to live scraping when D1 is empty, so the server works before the first Cron run.\n\n#### 4. (Optional) Enable authentication\n\n```bash\nwrangler secret put API_KEY\n# Clients must then send: Authorization: Bearer <key>\n# When API_KEY is not set, the server is open access.\n```\n\n#### 5. Local development\n\n```bash\nnpm run dev\n# Dev server runs locally on port 8787 (local only).\n# The public MCP endpoint is https://senado.sidneybissoli.com/mcp\n```\n\n#### 6. Tests and typecheck\n\n```bash\nnpm test             # run all tests once\nnpm run test:watch   # watch mode\nnpm run typecheck    # tsc --noEmit\n```\n\n#### 7. Deploy\n\n```bash\nnpm run deploy\n# Serves at https://senado.sidneybissoli.com (custom domain) and\n# https://senado-br-mcp.sidneybissoli.workers.dev (workers.dev fallback)\n```\n\n## Endpoints\n\n| Path | Methods | Description |\n|------|---------|-------------|\n| `/` | GET | Landing page (pt-BR) — identifies the client behind the outgoing User-Agent: what the service is, load posture, contact (always public) |\n| `/mcp` | POST, GET, DELETE, OPTIONS | MCP Streamable HTTP endpoint (managed by `createMcpHandler`) |\n| `/health` | GET | Health check — returns `ok` (always public) |\n| `/status` | GET | JSON: `status`, `version`, and last-deploy metadata (`deploy.id`/`tag`/`timestamp`) — liveness + current build, no MCP handshake needed (always public) |\n| `/metrics` | GET | JSON counters: requests, tool calls, cache hits/misses, upstream calls/retries/errors, auth failures (always public) |\n\n## MCP Request Examples\n\nAll requests go to `POST /mcp` with JSON-RPC 2.0 format.\n\n### List available tools\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"tools/list\"\n}\n```\n\n### Call a tool — List senators from SP\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 2,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"senado_listar_senadores\",\n    \"arguments\": {\n      \"uf\": \"SP\",\n      \"emExercicio\": true\n    }\n  }\n}\n```\n\n### Call a tool — Search bills by keyword\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 3,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"senado_buscar_materias\",\n    \"arguments\": {\n      \"palavraChave\": \"inteligência artificial\",\n      \"tramitando\": true\n    }\n  }\n}\n```\n\n### Call a tool — Get recent plenary votes\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 4,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"senado_search_votacoes\",\n    \"arguments\": {\n      \"dias\": 7\n    }\n  }\n}\n```\n\n### Call a tool — Most popular citizen ideas\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 5,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"senado_ecidadania_listar_ideias\",\n    \"arguments\": {\n      \"ordenarPor\": \"apoios\",\n      \"ordem\": \"desc\",\n      \"status\": \"aberta\"\n    }\n  }\n}\n```\n\n## Upstream API Endpoints\n\nThe server consumes two classes of upstream endpoints from the Senado API:\n\n### Legacy endpoints (`.json` suffix, PascalCase responses)\n\nUsed 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.\n\n| Upstream path | Used by |\n|---------------|---------|\n| `/senador/lista/atual` | `senado_listar_senadores` |\n| `/senador/lista/legislatura/{legislatura}` | `senado_listar_senadores` (param `legislatura`) |\n| `/senador/{codigo}` (+ `/mandatos`) | `senado_obter_senador` (biografia + mandatos via chamada extra) |\n| `/senador/{codigo}/licencas`, `/comissoes`, `/cargos`, `/historicoAcademico`, `/filiacoes`, `/profissao` | `senado_senador_historico` (`tipo` enum) |\n| `/senador/afastados` | `senado_senadores_afastados` |\n| `/senador/{codigo}/apartes` | `senado_discursos_senador` (`tipo=apartes`) |\n| `/comissao/lista/colegiados` | `senado_listar_comissoes` (+ sigla-to-code resolution) |\n| `/comissao/{codigo}` | `senado_obter_comissao` (`secao=resumo`; numeric code, not sigla) |\n| `/composicao/comissao/{codigo}` (+ `?ativas=S`) | `senado_obter_comissao` (`secao=membros`) |\n| `/comissao/agenda/{data}` | `senado_agenda_comissoes` |\n| `/comissao/agenda/{dataInicio}/{dataFim}` | `senado_reunioes_comissao` |\n| `/comissao/reuniao/{codigoReuniao}` | `senado_reuniao_comissao` |\n| `/comissao/cpi/{sigla}/requerimentos` | `senado_requerimentos_cpi` (upstream often empty even for active CPIs — empty result carries an `aviso`) |\n| `/materia/distribuicao/autoria`, `/distribuicao/relatoria/{sigla}` | `senado_distribuicao_materias` |\n| `/plenario/agenda/dia/{data}`, `/agenda/mes/{data}`, `/agenda/cn/...` | `senado_agenda_plenario` |\n| `/plenario/resultado/{data}`, `/resultado/cn/{data}`, `/resultado/mes/{data}` | `senado_resultado_plenario` |\n| `/plenario/resultado/veto/{codigo}` (+ `/materia/`, `/dispositivo/`) | `senado_resultado_veto` |\n| `/plenario/votacao/orientacaoBancada/{data}` (+ período) | `senado_orientacao_bancada` |\n| `/plenario/encontro/{codigo}` (+ `/pauta`, `/resultado`, `/resumo`) | `senado_encontro_plenario` |\n| `/plenario/tiposSessao`, `/lista/tiposComparecimento`, `/lista/legislaturas` | `senado_tabelas_plenario` |\n| `/materia/vetos/{ano}`, `/vetos/aposrcn`, `/vetos/antesrcn`, `/vetos/encerrados` | `senado_vetos` |\n| `/taquigrafia/notas/{sessao\\|reuniao}/{id}` | `senado_notas_taquigraficas` |\n| `/taquigrafia/videos/{sessao\\|reuniao}/{id}` | `senado_videos_taquigrafia` |\n| `/senador/{codigo}/discursos` | `senado_discursos_senador` |\n| `/plenario/lista/discursos/{dataInicio}/{dataFim}` | `senado_discursos_plenario` |\n| `/discurso/texto-integral/{codigo}` | `senado_discurso_texto` (plain text, fetched directly) |\n| `/senador/lista/tiposUsoPalavra` | `senado_tabelas_referencia` (`tabela=tipos-uso-palavra`) |\n| `/composicao/lista/blocos` | `senado_listar_blocos` |\n| `/composicao/bloco/{codigo}` | `senado_obter_bloco` |\n| `/composicao/lideranca` | `senado_liderancas` |\n| `/composicao/mesaSF` | `senado_mesa` (`casa=senado`) |\n| `/composicao/mesaCN` | `senado_mesa` (`casa=congresso`) |\n| `/orcamento/lista` | `senado_orcamento_parlamentar` (`tipo=emendas`) |\n| `/orcamento/oficios` | `senado_orcamento_parlamentar` (`tipo=oficios`) |\n| `/legislacao/lista` | `senado_buscar_legislacao` |\n| `/legislacao/{codigo}` | `senado_obter_legislacao` |\n| `/legislacao/tiposNorma` | `senado_tabelas_referencia` (`tabela=tipos-norma`) |\n| `/votacaoComissao/comissao/{sigla}` | `senado_votacao_comissao` (`por=comissao`) |\n| `/votacaoComissao/parlamentar/{codigo}` | `senado_votacao_comissao` (`por=senador`) |\n| `/votacaoComissao/materia/{sigla}/{numero}/{ano}` | `senado_votacao_comissao` (`por=materia`) |\n| `/autor/lista/atual` | `senado_autores_atuais` |\n\n### v3 endpoints (flat JSON arrays/objects, camelCase)\n\nUsed 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.\n\n| Upstream path | Used by |\n|---------------|---------|\n| `/votacao` | `senado_obter_votacao`, `senado_search_votacoes`, `senado_votos_materia`, `senado_votacoes_senador` |\n| `/processo` | `senado_search_processos`, `senado_buscar_materias` |\n| `/processo/{id}` | `senado_obter_processo`, `senado_obter_materia` (`secao=detalhe`/`tramitacao`) |\n| `/processo/documento` | `senado_obter_materia` (`secao=textos`) |\n| `/processo/emenda` | `senado_processo_detalhe` (`secao=emendas`) |\n| `/processo/relatoria` | `senado_processo_detalhe` (`secao=relatorias`), `senado_obter_materia` (rapporteur) |\n| `/processo/prazo` | `senado_processo_detalhe` (`secao=prazos`) |\n| `/processo/{siglas,assuntos,classes,destinos,entes,tipos-*}` | `senado_tabelas_processo` (12 reference tables) |\n\n### Administrative API (adm.senado.gov.br/adm-dadosabertos, flat snake_case JSON)\n\nUsed 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`.\n\n| Upstream path | Used by |\n|---------------|---------|\n| `/api/v1/senadores/despesas_ceaps/{ano}` | `senado_ceaps` (~10 MB/year, cached + aggregated in-Worker) |\n| `/api/v1/senadores/{auxilio-moradia,escritorios,aposentados}` | `senado_senadores_admin` (`tipo` enum) |\n| `/api/v1/servidores/servidores/{ativos,efetivos,comissionados,inativos}` | `senado_servidores` |\n| `/api/v1/servidores/remuneracoes/{ano}/{mes}` | `senado_remuneracoes_servidores` (~5.5 MB/month) |\n| `/api/v1/servidores/horas-extras/{ano}/{mes}` | `senado_horas_extras` |\n| `/api/v1/servidores/quantitativos/*`, `/previsao-aposentadoria`, `/api/v1/senadores/quantitativos/senadores` | `senado_pessoal_tabelas` (quantitativos) |\n| `/api/v1/servidores/{estagiarios,pensionistas,lotacoes,cargos}` | `senado_pessoal_tabelas` (listas nominais) |\n| `/api/v1/contratacoes/contratos` (+ `/{id}/aditivos`) | `senado_contratos`, `senado_contratacao_detalhe` |\n| `/api/v1/contratacoes/{tipo}/{id}/{itens,pagamentos,garantias}` | `senado_contratacao_detalhe` |\n| `/api/v1/contratacoes/licitacoes` | `senado_licitacoes` |\n| `/api/v1/contratacoes/terceirizados` | `senado_terceirizados` |\n| `/api/v1/contratacoes/empresas` | `senado_empresas_contratadas` (~13 MB, requires filter) |\n| `/api/v1/contratacoes/{atas_registro_preco,notas_empenho,menores_aprendizes}` | `senado_contratacoes_lista` |\n| `/api/v1/supridos/{ano}` (+ atosConcessao, empenhos, movimentacoes, transacoes) | `senado_suprimento_fundos` |\n| `senado.gov.br/bi-arqs/Arquimedes/Financeiro/{Despesa,Receitas}SenadoDadosAbertos.json` | `senado_execucao_orcamentaria` (daily JSON feeds, Brazilian decimal strings normalized) |\n\n### e-Cidadania (D1-backed, Cron-refreshed)\n\nThe 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:\n\n- a **daily off-Worker GitHub Action** owns the **full corpus** of the three live entities (`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);\n- a **weekly ingest Action** (`.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);\n- an in-Worker **Cron Trigger** (`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).\n\nBoth writers build payloads through the canonical `buildXResumo` builders + shared `contentHash`, so their rows are byte-identical. Each write:\n\n- **upserts** `ecidadania_current` (one row per item — what the tools read),\n- **appends** `ecidadania_history` only when an item's `content_hash` changes (time-series-ready),\n- records each run in `ecidadania_scrape_runs`.\n\nAn **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.\n\nThe **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.\n\nThe **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.\n\n#### Full-corpus ingestion (off-Worker)\n\nThe 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:\n\n- **`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.\n- **`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`).\n- **`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.\n\nThe 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.\n\nThe `consultas` job is the reference implementation:\n\n`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:\n\n1. **Decoupled ingestion.** The open set is acquired by an **off-Worker TypeScript job** (`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.\n2. **Status from `/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`.\n3. **Two reconciled cadences (one shared writer contract).** The job **reuses** `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.\n\nWrite 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:\n\n```bash\nCLOUDFLARE_API_TOKEN=… npm run ingest:ecidadania                 # writes scripts/ingest-ecidadania/out.sql\nnpx wrangler d1 execute senado-ecidadania --remote --file=scripts/ingest-ecidadania/out.sql\n```\n\n## Caching\n\n### Layer architecture\n\n| Layer | Storage | Scope | TTL range | Purpose |\n|-------|---------|-------|-----------|---------|\n| **L0** | In-memory `Map` | Per-isolate | 30-300s | Ultra-fast, eliminates redundant requests within a Worker isolate |\n| **L1** | Cloudflare Cache API (`caches.default`) | Per-colo (PoP) | 60-600s | Shared across requests at the same edge location |\n| **L2** | KV (optional) | Global | Variable | Reserved for rare, low-write data |\n\n### Cache categories\n\n| Category | L0 TTL | L1 TTL | Used for |\n|----------|--------|--------|----------|\n| **STATIC** | 300s | 600s | Legislation types, static reference |\n| **SEMI_STATIC** | 120s | 300s | Party list, UF list, committee details |\n| **DYNAMIC** | 30s | 60s | Agendas, recent votes, meeting lists |\n| **ON_DEMAND** | 30s | 120s | Specific bill/senator/vote lookups |\n\n### POST caching approach\n\nMCP uses POST for all `tools/call` requests. Caching POST responses is not natively supported by the Cache API, which requires GET requests. The solution:\n\n1. **Hash parameters** — Tool name + sorted parameters are hashed with SHA-256\n2. **Synthetic GET key** — A synthetic URL `https://senado-br-mcp.internal/__cache/{tool}/{hash}` is constructed\n3. **Cache API match/put** — The synthetic GET URL is used with `caches.default.match()` and `caches.default.put()`, allowing standard Cache API operations on POST-originated data\n\nThis caching happens at the **tool level** (inside each tool's callback), not at the MCP transport level.\n\n## Provenance\n\n**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). Since **v3.5.0** the envelope implements the portfolio-wide **provenance contract v1.0** ([`@sbissoli/mcp-provenance`](https://www.npmjs.com/package/@sbissoli/mcp-provenance)): the server builds and validates a full canonical model per response, and emits its **`concise` projection** — a fixed 6-key block with explicit `null` for unknown fields. 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.\n\nCoverage spans all four upstream sources, each with its own `source`/`citation`/`license` (in `src/utils/provenance.ts`):\n\n- **Senado Federal — Dados Abertos (Legislativo)** — `legis.senado.leg.br/dadosabertos`\n- **Senado Federal — Dados Abertos (Administrativo)** — `adm.senado.gov.br/adm-dadosabertos`\n- **Senado Federal — Execução Orçamentária e Financeira** — Arquimedes/Financeiro feed at `senado.gov.br`\n- **Senado Federal — Portal e-Cidadania** — `www12.senado.leg.br/ecidadania`\n\nFields of the `concise` block (per response — one tool, one source; keys in this fixed order, `null` when the source does not expose the value):\n\n| Field | Meaning |\n|-------|---------|\n| `source` | Official source name (e.g. *Senado Federal — Dados Abertos (Legislativo)*) |\n| `source_url` | Canonical endpoint/item URL consulted (e.g. `…/processo/{id}`) |\n| `data_vintage` | Vintage/competência of the data (e.g. `2024-03-15`, `2019`) — named `reference_period` before v3.5.0 |\n| `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 |\n| `citation` | Ready-to-use citation string (human-readable) |\n| `license` | Source terms (Dados Abertos do Senado Federal) |\n\nThe canonical model behind the block also carries `dataset.id` (item/series identifier, e.g. `codigoMateria=137808`), `api_version` and per-field `field_sources`; those are validated on every build and inform `attribution` (below), but are not part of the `concise` projection.\n\nIn 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](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/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.\n\n**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`).\n\n**Field-level granularity.** Most tools are single-source, so one envelope suffices. The few that merge slices in one response fill `field_sources` in the canonical model — a list of `{ fields, source_url, data_vintage, retrieved_at, … }` attributing specific output fields to their real origin. Example: `senado_obter_materia` `secao=detalhe` fuses `/processo/{id}` (the top-level source) with the `ementa` from `/processo` and the `relator` from `/processo/relatoria`, each carrying its own `retrieved_at`. In the emitted `concise` block the per-field detail is summarized through `attribution`, which always lists every distinct underlying `source_url`.\n\n`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.\n\nCoverage 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.\n\n## Citable dataset (e-Cidadania participation)\n\nBeyond 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).\n\n- **How to cite** — [`CITATION.cff`](CITATION.cff) (dataset; cite the version-DOI of the snapshot you used, the concept-DOI for the dataset across versions).\n- **What's in each release** — [`CHANGELOG-dataset.md`](CHANGELOG-dataset.md) (cumulative, append-only; binds each release to its `schemaVersion`).\n- **Variable dictionary & field provenance** — [`docs/dataset-dictionary.md`](docs/dataset-dictionary.md) (generated from `src/dataset/schema.ts`, the single source of truth).\n- **Data license** — [`LICENSE-DATA.md`](LICENSE-DATA.md).\n- **Cutting a release** (freeze → checksums → GitHub Release → Zenodo DOI) — [`docs/release-runbook.md`](docs/release-runbook.md); machinery in `src/dataset/`, `scripts/build-dataset/`, and `.github/workflows/release-dataset.yml`.\n\n### Inventory — schema v2 (`schemaVersion` `2.0.0`)\n\nEach 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:\n\n| Resource (`*.ndjson`) | Grain | Key variables | Source(s) |\n|---|---|---|---|\n| `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 |\n| `ideias` | 1 legislative idea (~113.7k) | `titulo`, `apoios`, `status`, **`dataPublicacao`ⁿ**, **`autorUf`ⁿ**, **`descricao`ⁿ**, **`plConvertido`ⁿ**, `url`, `firstSeenAt` | `pesquisaideia` listing + **detail** (`visualizacaoideia`) via a resumable backfillⁿ |\n| `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**ᶜ |\n| **`eventos_comentarios`ⁿ** | 1 comment (comment-level) | `eventoId`, `comentarioId`, `uf`, `texto`, `data`, `hora`, `momentoVideoUrl`, `convidadoAssociado` | AJAX fragment `ajaxcolecaocomentarioaudiencia?audienciaId=` |\n| `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) |\n\n**ⁿ = 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)**:\n\n- **Eventos corrected & enriched.** `data`/`hora` now come from the detail page (canonical — the [estudo A3](docs/estudo-a3-reconciliacao-eventos.md) 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).\n- **New comment-level resource** `eventos_comentarios` — one row per audiência comment, the participation signal nobody else publishes versioned.\n- **Detail-only fields reopened** for `ideias` (`dataPublicacao`, `autorUf`, `descricao`, `plConvertido`) and `consultas` (`autoria`, `relator`) — previously always `null` by design.\n- **Privacy posture by data origin.** Citizen content (audiência comments, idea authors) keeps **UF only — never the name**, discarded at the parser; public agents (consulta authorship/rapporteur, event guests) keep the name (public by function). See `docs/schema-v2-inventario.md` for the field-by-field rationale (approved target).\n\nThe 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.\n\n## Tool Inventory\n\n### Group H — Reference/Metadata (1 tool)\n\n| Tool | Description |\n|------|-------------|\n| `senado_tabelas_referencia` | Tabelas de referência via `tabela` enum: tipos-materia, partidos, ufs, legislatura-atual, tipos-norma, tipos-uso-palavra |\n\n### Group A — Senators (5 tools)\n\n| Tool | Description |\n|------|-------------|\n| `senado_listar_senadores` | Lista senadores em exercício/por legislatura, com filtros `nome` (busca parcial sem acento), `uf` e `partido` |\n| `senado_obter_senador` | Detalhe biográfico de um senador: bio, mandatos, partido, contato |\n| `senado_votacoes_senador` ⊕ | Como um senador votou em cada matéria (via v3 `/votacao`) |\n| `senado_senador_historico` | Histórico funcional via `tipo` enum: licencas, comissoes, cargos, historico-academico, filiacoes, profissoes |\n| `senado_senadores_afastados` | Senadores atualmente afastados (fora de exercício) |\n\n### Group B — Bills/Matters (2 tools, v3 backend)\n\n| Tool | Description |\n|------|-------------|\n| `senado_buscar_materias` ⊕ | Busca matérias por tipo, número, ano, palavra-chave, autor ou tramitação (via v3 `/processo`) |\n| `senado_obter_materia` ⊕ | Dados de uma matéria via `secao` enum: detalhe (situação/relator), tramitacao (histórico) ou textos (documentos) |\n\n### Group C — Processes (5 tools)\n\n| Tool | Description |\n|------|-------------|\n| `senado_search_processos` ⊕ | Busca processos legislativos (complementar à busca de matérias) |\n| `senado_obter_processo` ⊕ | Detalhes completos de um processo legislativo específico |\n| `senado_processo_detalhe` | Aspecto de um processo via `secao` enum: emendas, relatorias ou prazos |\n| `senado_autores_atuais` | Parlamentares autores de processos em tramitação, ordenados por produção |\n| `senado_tabelas_processo` | 12 tabelas de referência (siglas, assuntos, classes, tipos-*) via `tabela` enum |\n\n### Group D — Votes (3 tools)\n\n| Tool | Description |\n|------|-------------|\n| `senado_obter_votacao` ⊕ | Detalhes de uma votação com votos nominais. Aceita `codigoVotacao` (codigoSessao da sessão plenária). |\n| `senado_votos_materia` ⊕ | Votações de uma matéria (via v3 `/votacao?codigoMateria`), com votos nominais opcionais |\n| `senado_search_votacoes` ⊕ | Busca/listagem flexível de votações do plenário por `dias`, período, processo, matéria ou senador |\n\n### Group E — Committees (7 tools)\n\n| Tool | Description |\n|------|-------------|\n| `senado_listar_comissoes` | Lista comissões (colegiados) ativas, filtráveis por tipo |\n| `senado_obter_comissao` | Dados de uma comissão via `secao` enum: resumo (mesa/totais) ou membros (composição). Resolve sigla para código internamente. |\n| `senado_reunioes_comissao` | Reuniões de uma comissão num período (lida com intervalos entre anos) |\n| `senado_agenda_comissoes` | Agenda de reuniões de todas as comissões numa data |\n| `senado_reuniao_comissao` | Detalhe completo de uma reunião: partes, itens, convidados, resultados, links pauta/ata |\n| `senado_requerimentos_cpi` | Requerimentos protocolados numa CPI em atividade, paginados (upstream costuma vir vazio mesmo para CPIs ativas; retorno vazio traz `aviso`) |\n| `senado_distribuicao_materias` | Estatísticas de carga por senador numa comissão: autoria ou relatoria |\n\n### Group F — Plenary (7 tools)\n\n| Tool | Description |\n|------|-------------|\n| `senado_agenda_plenario` | Plenary schedule — by day, month or Congress (escopo dia/mes/cn) |\n| `senado_resultado_plenario` | Session results: items deliberated, opinions, outcomes (SF/CN/month) |\n| `senado_orientacao_bancada` | Party leadership voting instructions per vote, with tallies |\n| `senado_vetos` | Presidential vetoes by year or tramitation status |\n| `senado_resultado_veto` | Nominal veto vote results (by veto, vetoed bill or device) |\n| `senado_encontro_plenario` | Legislative session detail, agenda items, results or summary |\n| `senado_tabelas_plenario` | Session types, attendance types, legislatures list |\n\n### Group G — e-Cidadania (9 tools)\n\n| Tool | Description |\n|------|-------------|\n| `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`) |\n| `senado_ecidadania_obter_consulta` | Detalhe de uma consulta: votos, autor, relator, comentários |\n| `senado_ecidadania_consultas_analise` | Analisa o conjunto completo de consultas **abertas** via `modo` (consenso/polarizada); `status` padrão `aberta` |\n| `senado_ecidadania_listar_ideias` | Ideias legislativas de cidadãos; ranking das mais apoiadas via `ordenarPor: apoios` |\n| `senado_ecidadania_obter_ideia` | Detalhe de uma ideia: texto, apoios, status de conversão em projeto |\n| `senado_ecidadania_listar_eventos` | Eventos interativos (audiências, sabatinas, lives); ranking dos mais comentados via `ordenarPor` |\n| `senado_ecidadania_obter_evento` | Detalhe de um evento: pauta, convidados, link de vídeo |\n| `senado_ecidadania_sugerir_tema_enquete` | Sugere temas para enquete mensal a partir de critérios configuráveis |\n| `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` |\n\n### Group I — Speeches (3 tools)\n\n| Tool | Description |\n|------|-------------|\n| `senado_discursos_senador` | Pronunciamentos de um senador via `tipo` enum: discursos (próprios) ou apartes (intervenções) |\n| `senado_discursos_plenario` | Todos os discursos em plenário num intervalo de datas |\n| `senado_discurso_texto` | Texto integral de um pronunciamento/discurso específico |\n\n### Group J — Blocs & Leadership (4 tools)\n\n| Tool | Description |\n|------|-------------|\n| `senado_listar_blocos` | Blocos parlamentares do Senado e seus partidos membros |\n| `senado_obter_bloco` | Detalhes de um bloco parlamentar específico |\n| `senado_liderancas` | Lideranças do Senado/Câmara/Congresso (líderes, vice-líderes) com o bloco/partido liderado, filtráveis |\n| `senado_mesa` | Membros da Mesa Diretora via `casa` enum: senado (Mesa do SF) ou congresso (Mesa do CN) |\n\n### Group K — Budget (1 tool)\n\n| Tool | Description |\n|------|-------------|\n| `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) |\n\n### Group L — Federal Law (2 tools)\n\n| Tool | Description |\n|------|-------------|\n| `senado_buscar_legislacao` | Busca normas jurídicas federais por tipo, número, ano ou data (ao menos um obrigatório) |\n| `senado_obter_legislacao` | Detalhes de uma norma jurídica federal específica |\n\n### Group M — Committee Voting (1 tool)\n\n| Tool | Description |\n|------|-------------|\n| `senado_votacao_comissao` | Votações em comissões via `por` enum: comissao, senador ou materia; período opcional |\n\n### Group N — Taquigrafia (2 tools)\n\n| Tool | Description |\n|------|-------------|\n| `senado_notas_taquigraficas` | Official transcripts of plenary sessions or committee meetings — summary mode with excerpts, full-text mode paginated in blocks, speaker filter |\n| `senado_videos_taquigrafia` | Video/audio units per session or meeting, with speaker and media links |\n\n### Group O — Senadores/Administrativo (2 tools)\n\n| Tool | Description |\n|------|-------------|\n| `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 |\n| `senado_senadores_admin` | Dados administrativos dos senadores via `tipo` enum: auxilio-moradia, escritorios-apoio ou aposentados |\n\n### Group P — Servidores / Gestão de Pessoas (4 tools)\n\n| Tool | Description |\n|------|-------------|\n| `senado_servidores` | Civil servants by status (active/effective/commissioned/inactive), filterable by name, unit, position |\n| `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` |\n| `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` |\n| `senado_pessoal_tabelas` | Tabelas de pessoal via `tabela` enum: quantitativos (pessoal, cargos-funcoes, previsao-aposentadoria, senadores) e listas (estagiarios, pensionistas, lotacoes, cargos) |\n\n### Group Q — Contratações (6 tools)\n\n| Tool | Description |\n|------|-------------|\n| `senado_contratos` | Contracts filtered in-Worker over the full base (accent-insensitive): supplier, CNPJ, year, number, object, labor |\n| `senado_contratacao_detalhe` | Items, payments, guarantees, amendments or activations of a contract/ata/empenho |\n| `senado_licitacoes` | Biddings by number or object text |\n| `senado_terceirizados` | Outsourced collaborators by name, company or unit |\n| `senado_empresas_contratadas` | Companies contracting with the Senate (requires name/CNPJ filter) |\n| `senado_contratacoes_lista` | Price-registration atas, commitment notes, young apprentices |\n\n### Group R — Suprimento de Fundos (1 tool)\n\n| Tool | Description |\n|------|-------------|\n| `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` |\n\n### Group S — Orçamento do Senado (1 tool)\n\n| Tool | Description |\n|------|-------------|\n| `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` |\n\n### Group T — Estrutura Organizacional (1 tool)\n\nReads 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`.\n\n| Tool | Description |\n|------|-------------|\n| `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). |\n\n### Group U — Deep Research (2 tools)\n\nThe [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.\n\n| Tool | Description |\n|------|-------------|\n| `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`. |\n| `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. |\n\n**Total: 69 tools**\n\n### Prompts (4)\n\nReusable pt-BR workflow templates (MCP `prompts` capability), defined in `src/prompts.ts`:\n\n| Prompt | Args | What it guides |\n| --- | --- | --- |\n| `senado_gastos_senador` | `senador`, `ano` | Resolve o senador e agrega/detalha despesas CEAPS. |\n| `senado_tramitacao_materia` | `sigla`, `numero`, `ano` | Obtém situação atual + histórico de tramitação da matéria. |\n| `senado_votos_senador` | `senador`, `periodo?` | Lista os votos nominais do senador no período. |\n| `senado_panorama_ecidadania` | — | Consolida consultas (consenso/polarização), ideias e eventos populares. |\n\n### Resources (5)\n\nStatic context documents/tables (MCP `resources` capability), defined in `src/resources.ts`:\n\n| URI | Type | Content |\n| --- | --- | --- |\n| `senado://guia` | markdown | Visão geral e qual ferramenta usar por objetivo. |\n| `senado://catalogo` | markdown | As 69 ferramentas agrupadas por domínio. |\n| `senado://glossario` | markdown | Siglas e termos do Senado (PEC, CEAPS, CCJ, RCN…). |\n| `senado://tabelas/tipos-materia` | json | Tipos de proposição (sigla/nome/descrição). |\n| `senado://tabelas/ufs` | json | As 27 unidades federativas. |\n\n## Project Structure\n\n```\nsrc/\n├── index.ts              # Worker entrypoint (fetch handler + scheduled/Cron handler)\n├── server.ts             # McpServer factory (creates per-request instance)\n├── auth.ts               # Optional Bearer token auth (constant-time compare)\n├── metrics.ts            # In-memory counters served at /metrics\n├── types.ts              # Env, cache categories, safeguard constants\n├── cache/\n│   ├── l0-memory.ts      # In-memory Map cache with TTL + LRU eviction\n│   ├── l1-cache-api.ts   # Cloudflare Cache API wrapper (synthetic GET keys)\n│   └── manager.ts        # Cache orchestrator (L0 → L1 → upstream)\n├── throttle/\n│   ├── token-bucket.ts   # Token bucket rate limiter (global + per-client)\n│   └── upstream.ts       # Upstream fetch with concurrency limit, retry, timeout\n├── scraper/\n│   ├── ecidadania.ts     # Isolated e-Cidadania scraper (REST lists + regex HTML detail; buildConsultaResumo)\n│   ├── pipeline.ts       # 2h Cron: targeted highlight metric splice (consultas/eventos/ideias); corpora owned by the off-Worker jobs\n│   ├── anomaly.ts        # Run classification (anomalous run never overwrites current)\n│   └── store.ts          # D1 reads (resolveList + per-entity staleness, lastGoodRunAt) + detail write-through\n├── instrument.ts         # Per-tool call telemetry (in-memory + Analytics Engine)\n├── utils/\n│   ├── logger.ts         # Structured JSON logging\n│   └── validation.ts     # toolResult, toolError, errorFrom, buildParams, ensureArray helpers\n└── tools/\n    ├── referencia.ts        # Group H — 1 reference/metadata tool\n    ├── senadores.ts         # Group A — 5 senator tools\n    ├── materias.ts          # Group B — 2 bill/matter tools (v3 backend)\n    ├── processos.ts         # Group C — 5 process tools\n    ├── votacoes.ts          # Group D — 3 vote tools\n    ├── comissoes.ts         # Group E — 7 committee tools\n    ├── plenario.ts          # Group F — 7 plenary tools\n    ├── ecidadania.ts        # Group G — 8 e-Cidadania tools (read from D1; see scraper/)\n    ├── discursos.ts         # Group I — 3 speech tools\n    ├── composicao.ts        # Group J — 4 bloc/leadership tools\n    ├── orcamento.ts         # Group K — 1 budget tool\n    ├── legislacao.ts        # Group L — 2 federal law tools\n    ├── votacao-comissao.ts  # Group M — 1 committee voting tool\n    ├── taquigrafia.ts       # Group N — 2 stenographic record tools\n    ├── senadores-admin.ts   # Group O — 2 admin senator tools (CEAPS, housing)\n    ├── servidores.ts        # Group P — 4 personnel tools\n    ├── contratacoes.ts      # Group Q — 6 procurement tools\n    ├── supridos.ts          # Group R — 1 petty-cas",
  "bytes": 60000,
  "sha": "ea8c833607c60f027184811f1d36ee339c0f0413de23bbc22f7510d9d0fb4130",
  "repo_slug": "sidneybissoli/senado-br-mcp-cloudflare",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_sidneybissoli_senado_br_mcp_cl_076fcb40/readme"
}