Skip to main content
Glama

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.

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 (server.json at the repo root, schema-validated in CI by scripts/check-server-json.mjs)

  • Docs: oravan.org/mcp (tool reference, client config, privacy posture) · docs/mcp-server-readme.md

Related MCP server: CongressMCP-full

Design principles

  1. Zero accounts. ZIP code, interests, and call history live in localStorage on the visitor's device. Oravan keeps no accounts and no database of visitors. The host's request logs are the one exception, and the privacy page names them. 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 ~7,500 statically generated pages (7,566 in the 2026-09-24 build, including a page for every member of Congress and a daily brief). Fast, nearly free to host, resilient under load. The server endpoints are /api/script (AI call-script generation, cached per bill + stance + language + content version in a shared store, 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), /api/mcp (the MCP server below), /api/brand (an AI theme suggestion for a partner's site, used by the /embeds configurator, which is hidden until embeds come back), /api/stripe/webhook (partner-plan provisioning; refuses with 503 until its signing secret is set), /api/tenant/impressions (a partner reads its own embed impression counts), and /embed/portrait/[bioguide] (a same-origin portrait proxy for the embeds). Two page routes render per request — /reps (it reads the ZIP from the URL) and /nominations/[slug] — and tests/static-rendering.spec.ts names them with the reason.

  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 is a normal way to leave a message, so after-hours calling is encouraged; district offices listed alongside DC; outcomes — spoke / voicemail / couldn't reach — logged locally on the device), and every decoded answer on a decision still open keeps a completed call script within two interactions; once the decision is over — a law, a rejected vote — the page shows the outcome and how your members voted instead. Demoted, never buried. (Amended 2026-07-26; previously "The call moment is the product." Scoped to a decision still open on 2026-09-28. Enforced by the three named invariants in tests/funnel.spec.ts.)

  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 docs/constitution-log.md#cursor-age-2026-08-12.) 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 the 2026-07-25 amendment in docs/constitution-log.md, which this line should have followed and did not.)

    AI content is labeled at first contact, and a call script is read — and editable — by the caller before it drives a call. That last clause is scoped to call scripts on purpose: decodes and Moment update summaries publish behind automated gates with no human step, so the label is the whole disclosure there. The label sits with the content, above the fold — never in a footnote. (Moved here from DESIGN.md on 2026-09-27, when that file was retired.)

  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. Each record also stores which text version its decode was produced from, and a bill whose text Congress replaces — an amendment in committee changes the document without changing the title or the status — is re-read from the new text on a later nightly, at most 10 a night (REDECODE_MAX_PER_NIGHT, a cost ceiling; the backlog of older records drains a few a night, urgent bills first)

data/legislators.json

unitedstates/congress-legislators (public domain) + district offices

scripts/process-data.py

data/zip-districts.json

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/special-elections.json

Each vacant seat's special-election dates exactly as the Federal Election Commission's election-dates API lists them (date + the FEC's election-type code), with the day they were checked. No row means an empty list, never a guessed date; no member-elect is recorded, because no official machine-readable source names one before the oath. Shown on the vacant-seat card on /reps and the seat's own page

scripts/sync-special-elections.mjs, weekly via refresh-legislators.yml right after the vacancy diff (api.data.gov's public DEMO_KEY, no secret; a failed request keeps the last entry and its old checked date)

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'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, AI-relevance-filtered (Haiku)

scripts/sync-coverage.mjs (nightly, gated on NEWS_API_KEY)

data/media-bias.json

Outlet political-lean ratings by AllSides, used under CC BY-NC with attribution

Vendored snapshot

Portraits are served from the public-domain 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 showing the section makes no runtime third-party call. 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 CLAUDE.md, rule 3).

Develop

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.

  • The script cache and the rate limits live in shared Upstash stores (lib/scriptcache.ts, lib/ratelimit.ts). When those stores are not configured — local dev, CI, previews — or a request to one fails, both fall back to per-instance memory rather than fail the request.

  • 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. 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.

Available Tools

5 tools
get_billGet a bill decodeA
Read-only
Inspect

Get the full plain-language decode of a federal bill by slug (e.g. "hr-2701-119") or citation (e.g. "H.R. 2701" - resolves to the most recent Congress on a match). Returns the AI-generated summary (headline, tl;dr, what/who/why/cost - automatically checked before publish and clearly labeled when present), the official status in plain language, an urgency band, sponsor, key dates, the official Congress.gov page, and an act_url to Oravan's on-site call flow. This tool never drafts a phone script - script generation happens only on-site, where the caller reads and can edit the script before dialing; that is not available over this API.

Inputs: slug, locale, citation.

get_representativeGet a representativeA
Read-only
Inspect

Get full details for one member of Congress by bioguide ID (e.g. "W000797"), plus their 5 most recently active sponsored bills. Facts only: no scorecards, ratings, or vote grades.

Inputs: bioguide (required), locale.

lookup_representativesLook up representatives by ZIPA
Read-only
Inspect

Look up a person's U.S. House member and two Senators by 5-digit ZIP code. Returns each member's name, party, phone, official website, portrait URL, and district office phone numbers - the number a constituent should actually call. Some ZIP codes span more than one congressional district (needs_address: true, all candidate districts returned); this tool does not perform address-level refinement itself in this release - point the person to the response's reps_url, where a stateless, unlogged Census-geocoder proxy narrows it to a single district from a street address that Oravan never stores. When a House seat currently has no member, vacancies lists the empty seat(s) (state + district) explicitly - the departed member is never returned as if still serving, and no election timeline is implied.

Inputs: zip (required), locale.

search_billsSearch billsA
Read-only
Inspect

Search Oravan's bilingual federal bill corpus by free-text query, issue topic, status, or active-only. Returns short teasers (headline, status, urgency) for matching bills, most urgent first.

Inputs: limit, query, topic, locale, +2 more.

whats_movingWhat's moving in CongressA
Read-only
Inspect

What's moving in Congress recently: active, plain-language-decoded bills that cleared Oravan's 'act now' urgency bar within the last N days (default 7), optionally filtered by topic. Returns an honest empty list with quiet_week: true when nothing has cleared the bar - this tool never pads the list to look busier than Congress actually is this week. If the list is empty because Oravan's own data sync looks stale rather than Congress being quiet, data_stale is set instead so that distinction is never lost.

Inputs: days, limit, topic, locale.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.1/5.0

    Scored across 5 tools

    Disambiguation4/5

    The tools are mostly distinct: representative lookup is split by ID vs ZIP, and bill discovery is split into search teasers vs full-detail retrieval. whats_moving and search_bills can overlap when filtering by active status and topic, but their intent is clearly differentiated by recency/urgency vs free-text query.

    Naming Consistency4/5

    Four tools follow a clear verb_noun pattern: get_representative, lookup_representatives, get_bill, search_bills. whats_moving breaks the pattern stylistically but is still readable and memorable, so the set is mostly consistent with one minor deviation.

    Tool Count5/5

    Five tools is well-scoped for a focused congressional-information server. Each tool covers a distinct core action: look up a representative, get representative details, get a bill, search bills, and see recent urgent activity.

    Completeness3/5

    Core read-only workflows are covered: search bills, retrieve bill details, find representatives by ZIP, and see recent activity. However, get_representative requires a bioguide ID that lookup_representatives does not appear to return, and address-level district refinement is explicitly left to an external URL, creating possible dead-ends for agents.

    Maintenance

    ActivityActive
    ResponsivenessMixed handling

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      F
      maintenance
      An MCP server for the Congress.gov API that consolidates 91 operations into 6 comprehensive legislative tools that can be used by any MCP client (i.e. Claude Desktop), or MCP-compatible AI agent, to query and reason about congressional data.
      7 npm
      1
      JavaScript
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      The open Congress.gov MCP server. Provides comprehensive access to the Congress.gov API through 6 organized toolsets, enabling AI systems to retrieve and interact with legislative data from the United States Congress with a clean, unified interface.
      24
      52 PyPI
      36
      Sustainable Use v1.0
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables querying legislative data from the LegiScan API, including bills and votes, via natural language through an MCP gateway.
      137 npm
      MIT
    • F
      license
      A
      quality
      C
      maintenance
      Exposes a Congress.gov-style legislative GraphQL API to MCP clients, enabling querying bills, actions, committee activities, and cosponsors via natural language.
      7
      -