Back to the catalog

Oravan

Nonpartisan U.S. Congress data: bill decodes, rep lookup, bilingual EN/ES, read-only, no accounts.

Open source Repository Open in the app JSON README (API)

About

Nonpartisan U.S. Congress data: bill decodes, rep lookup, bilingual EN/ES, read-only, no accounts.

Details

Kind
MCP servers
Topic
Government & public data
Publisher
org.oravan
Origin
official
Category
ferramentas
Transport
http
Version
0.1.0
Stars
1
Open pull requests
1
Last push
2026-09-07T18:57:47Z
Repository state
ativo
Language
TypeScript
License
AGPL-3.0
Added
2026-08-29 04:01:53
Updated
2026-08-29 04:01:53
Origin id
org.oravan/mcp

README

# Oravan

**Your line to Congress · Tu línea con el Congreso**

Oravan is free, nonpartisan civic infrastructure: find your federal representatives, understand active bills in plain language (English and Spanish), get a 30-second call script, and make the call — in under 5 minutes, with no account.

The name is the **Oravan**: the platform in the Roman Forum where citizens stood to address the public and the powerful — the original place a voice met power. Latin roots are the shared ancestry of English and Spanish alike.

## MCP server

This repository also implements a **remote MCP (Model Context Protocol) server** — the same decoded corpus and lookups, exposed for AI assistants and agents:

- **Endpoint (Streamable HTTP):** `https://oravan.org/api/mcp/mcp` — keyless, read-only, rate-limited; no account or credentials required
- **Local/stdio:** `npm ci` then `npx tsx scripts/mcp-stdio.mjs` — the same 5 tools over stdio, zero env vars/secrets required
- **Implementation:** `app/api/mcp/[transport]/route.ts` (built on `mcp-handler` + `@modelcontextprotocol/sdk`), tool definitions shared with the stdio entry via `lib/core/mcp-tools.ts`, pure data layer in `lib/core/`
- **Five tools:** `lookup_representatives`, `get_bill`, `search_bills`, `whats_moving`, `get_representative` — every response carries a citation envelope (source, as-of freshness, AI-content label, license) in English or Spanish
- **Official MCP Registry:** published as [`org.oravan/mcp`](https://registry.modelcontextprotocol.io/v0.1/servers?search=org.oravan/mcp) (`server.json` at the repo root, schema-validated in CI by `scripts/check-server-json.mjs`)
- **Docs:** [oravan.org/mcp](https://oravan.org/mcp) (tool reference, client config, privacy posture) · [docs/mcp-server-readme.md](docs/mcp-server-readme.md)

## Design principles

1. **Zero accounts.** ZIP code, interests, and call history live in `localStorage` on the visitor's device. No server-side user data exists — nothing to breach, leak, or subpoena. This is the core privacy posture for at-risk users, not a missing feature.
2. **Static-first.** Bills, legislators, district offices, and ZIP→district mappings are static JSON in `data/`, baked into ~1,000 statically generated pages. Fast, nearly free to host, resilient under load. The only dynamic endpoints are `/api/script` (AI script generation, cached per bill+stance+language, IP rate-limited), `/api/reps` (pure lookup), `/api/district` (stateless split-ZIP address refinement: proxies the Census geocoder so the visitor's IP never reaches census.gov; the address is never stored or logged), and `/api/feedback` (beta feedback relayed to a private GitHub issue — only what the visitor volunteered, no identifiers).
3. **Bilingual as a first-class feature.** Full EN/ES UI via `next-intl`; scripts are generated in the user's language.
4. **Truth first; the call is the natural next step.** Oravan leads as an unbiased, plain-words account of what Congress is actually doing — understanding is the front door, never an assignment. The call apparatus stays the differentiator (voicemail legitimized, offices tally it identically; after-hours calling encouraged; district offices listed alongside DC; outcomes — spoke / voicemail / couldn't reach — logged locally on the device), and every decoded answer keeps a completed call script within two interactions. Demoted, never buried. *(Amended 2026-07-26; previously "The call moment is the product." Enforced by the three named invariants in `tests/funnel.spec.ts` — see DESIGN.md structural constraint 2.)*
5. **Honest about AI.** Every generated summary and script is labeled at first contact, and nothing publishes unless the automated gates pass: both languages present, the official record attached, and a schema check on every decode — a decode that comes back missing a required field is discarded rather than stored half-written, and `scripts/verify-sync.mjs` re-checks the whole corpus and fails the nightly run before it is allowed to commit anything. *(Amended 2026-08-12: one check left that file and now runs AFTER the commit — the cursor-age ceiling in `scripts/check-cursor-age.mjs`. It is a progress signal, not a corpus one: a stalled cursor means we are behind, and failing it before the commit made a stalled night throw away a night of already-paid decodes. Every corpus check named here is unchanged and still runs before anything is committed. See CLAUDE.md's amendment of the same date.)* Nonpartisan wording is a *drafting instruction* to the model on bill decodes and an *enforced vocabulary lint* on Big Questions (`lib/moments-gate.mjs`) — the two are not the same guarantee, and the copy never blurs them. The nightly decode path has no human step and the product never claims one; the one review it does claim is real: a caller reads, and can edit, the call script before dialing. *(Amended 2026-08-06; previously "labeled, editable, and reviewed by the human before any call." See CLAUDE.md's 2026-07-25 amendment, which this line should have followed and did not.)*
6. **Accessible by default.** Semantic landmarks, skip link, visible focus, `prefers-reduced-motion`, 44px+ touch targets, AA contrast.

## Data sources

| File | Source | Refresh |
|---|---|---|
| `data/bills.json` + `data/bills-es.json` | Decoded bill corpus (Congress.gov bills + AI plain-language summaries, English and Spanish) | Nightly sync (`scripts/sync-bills.mjs` via `sync-bills.yml`): statuses refresh freely; new bills are decode-before-publish, entering the corpus only once their EN **and** ES summaries exist |
| `data/legislators.json` | [unitedstates/congress-legislators](https://github.com/unitedstates/congress-legislators) (public domain) + district offices | `scripts/process-data.py` |
| `data/zip-districts.json` | [OpenSourceActivismTech/us_zipcodes_congress](https://github.com/OpenSourceActivismTech/us_zipcodes_congress) | same |
| `data/vacancies.json` | Derived, not fetched: `scripts/vacancy_diff.py` diffs seat sets against the currently-committed data every run, so a departed member with no successor surfaces as an explicit vacancy (reps page, `/api/reps`, MCP `lookup_representatives`) instead of silently disappearing or being backfilled from a stale term record | `scripts/process-data.py` (same run as legislators.json) |
| `data/redistricting-watch.json` | Human-authored (`status`/`note`) for the 10 states with contested-or-recent 2025–26 mid-decade map changes; `rdh_lastmod` is a tripwire baseline against the [Redistricting Data Hub](https://redistrictingdatahub.org/)'s own state-page sitemap — see `docs/solutions/two-clock-district-boundaries.md` | `scripts/check-redistricting-watch.mjs`, weekly via `refresh-legislators.yml`; on change it comments on ONE standing, pinned `redistricting-watch` issue whose body is a rewritten 10-state status board (it used to open one issue per changed state — ten accumulated in six weeks, eight of them from a single RDH site-wide republish), never auto-updates `status`/`note` |
| `data/nominations.json` | Civilian Senate nominations (PNs) of the 119th Congress — Congress.gov's own citation, description sentence, and latest action, plus a status derived from that action text by `lib/nomination-status.mjs`'s rule table. **No AI touches this file** — Oravan does not decode or rewrite a nomination, because Congress.gov's description is already one plain English sentence, so `/nominations/[slug]` renders the Senate's own record verbatim and says so on the page. The one piece of AI is the call script, labeled where it is generated. A nomination can be a Big Question's vehicle; the card, the page, and the call are live. It stays English on `/es` like the coverage titles below (see **Known v1 caveats**). Military promotion lists are excluded (no description, no nameable nominee). No MCP tool exposes nominations yet. | `scripts/sync-nominations.mjs` (nightly, one free request; gated by `scripts/check-nominations.mjs`) |
| `data/coverage.json` | Real news articles about top-band bills via [TheNewsAPI](https://www.thenewsapi.com/), AI-relevance-filtered (Haiku) | `scripts/sync-coverage.mjs` (nightly, gated on `NEWS_API_KEY`) |
| `data/media-bias.json` | Outlet political-lean ratings by [AllSides](https://www.allsides.com/media-bias/ratings), used under CC BY-NC with attribution | Vendored snapshot |

Portraits are served from the public-domain [unitedstates/images](https://github.com/unitedstates/images) project.

Solved pipeline incidents (root cause + the CI gates that prevent recurrence) are documented in `docs/solutions/`.

### The "Read" section (outlet-bias coverage)

Each top bill's page shows real third-party articles about it, labeled by the **outlet's** political lean (Left / Center / Right) — reusing AllSides' publication-level ratings, never a Oravan-invented one. Oravan takes no stance and authors no partisan text: AI is used only behind the scenes — generating each bill's news-search terms (press-style names and a subject query) and a cheap relevance gate (is this article about this bill?) — and authors nothing displayed. The ingestion runs nightly in CI and bakes results to JSON, so the site still makes **zero runtime third-party calls**. Without `NEWS_API_KEY` the sync is a no-op and the section renders nothing; a small hand-built real sample (`data/coverage.json`) keeps it demoable. Lean is shown by **text label + position only — never party colors** (a hard rule; see `DESIGN.md`).

## Develop

```bash
npm install
echo "ANTHROPIC_API_KEY=sk-ant-..." > .env.local   # script generation + decode/relevance
echo "NEWS_API_KEY=..." >> .env.local               # optional; enables the "Read" coverage sync
npm run dev
```

`npm run build` statically generates every bill page in both locales.

## Known v1 caveats

- ZIP→district mapping is ZCTA-based; a split ZIP shows all candidate districts by default (senators are unaffected). Entering a street address — optional, sent once by POST, never stored or logged — narrows it to the actual district via a server-proxied Census-geocoder lookup; the all-candidates view remains the graceful fallback whenever the geocoder can't help. The geocoder request pins the "119th Congressional Districts" layer, which needs a bump when the Census rolls the vintage to the 120th.
- Script cache and rate limits are in-memory per serverless instance — fine at demo scale, should move to a shared store before heavy traffic.
- New bills can lag behind Congress.gov: the nightly sync decodes at most `MAX_NEW_DECODES` new bills per run (cost ceiling), so after a missed window the corpus catches up over several nights (decode-before-publish; the backlog drains oldest-first).
- "Read" coverage exists only for top-band bills (the long tail shows nothing); the ES locale shows the same English articles with localized chrome; outlets absent from `data/media-bias.json` appear without a lean chip.

## License

- **Code:** [GNU AGPL-3.0](LICENSE). You may use, modify, and run this code — including as a network service — provided modified versions you operate or distribute remain open under the same license. Embedding Oravan's hosted widgets on your site via the loader/script tag does **not** subject your site to the AGPL; that's use of our service, not distribution of this code.
- **Not licensed:** the Oravan name, logo, and brand assets (`assets/brand/`, app icons). All rights reserved — forks must use their own identity.
- **Content:** underlying legislative data is U.S. government work (public domain). AI-generated decodes and summaries are licensed CC BY 4.0, exactly as declared in the MCP citation envelope and on the [citations page](https://oravan.org/citations).

More