@legislation/mcp
v0.7.0
Published
Model Context Protocol server for the Delilah legislative data API — bills, legislators, votes, committees and coverage for 50 states, D.C. and Congress.
Maintainers
Readme
@legislation/mcp
An MCP server that gives AI assistants real U.S. legislative data from the Delilah API — all 50 states, D.C., and the federal Congress. Bills, sponsors, roll-call votes with individual member positions, action history, bill texts, committees, hearings, a change feed, and per-state coverage.
Read-only: it never writes anything. There are two ways to use it — a remote hosted server (recommended, nothing to install) and a local stdio package. Both expose the same 35 tools. The hosted server works without a key on a shared, rate-limited free tier; for your own cap use a Delilah API key (request one at https://delilahusa.com/developers/request).
Remote hosted server (recommended)
The server runs on Delilah's own infrastructure at https://mcp.delilahusa.com —
you just point your client at the URL and pass your key. Nothing to install, and
the implementation stays server-side.
Cursor — ~/.cursor/mcp.json:
{
"mcpServers": {
"legislation": {
"url": "https://mcp.delilahusa.com/",
"headers": { "Authorization": "Bearer dk_live_YOURKEY" }
}
}
}Claude — Settings → Connectors → Add custom connector, URL:
https://mcp.delilahusa.com/?key=dk_live_YOURKEYAny stdio-only client (bridge): npx mcp-remote https://mcp.delilahusa.com/ --header "Authorization: Bearer dk_live_YOURKEY"
See docs/ARCHITECTURE.md for how the hosted server works
(a Cloudflare Worker fronting api.delilahusa.com), and where it deploys from.
Local install (stdio)
One command, nothing to configure:
claude mcp add legislation -- npx @legislation/mcpWith no key it runs on the shared free tier: the hosted server supplies a key
of its own, so tools work immediately. That tier is read-only and rate-limited
per client. Set DELILAH_API_KEY for your own monthly cap and for writes:
claude mcp add legislation -e DELILAH_API_KEY=dk_live_… -- npx @legislation/mcpClaude Desktop
Add this to claude_desktop_config.json (macOS:
~/Library/Application Support/Claude/claude_desktop_config.json, Windows:
%APPDATA%\Claude\claude_desktop_config.json), then restart Claude Desktop:
{
"mcpServers": {
"legislation": {
"command": "npx",
"args": ["-y", "@legislation/mcp"],
"env": {
"DELILAH_API_KEY": "dk_live_..."
}
}
}
}Claude Code
Plugin (recommended) — adds the hosted MCP server and a /legislation
skill that tells Claude how to answer legislative questions well (cite bill
numbers, pick the right tool, never call a race). No API key needed to start:
the hosted server has a shared, rate-limited free tier.
/plugin marketplace add https://mcp.delilahusa.com/marketplace.json
/plugin install legislation@legislationThen, in any session: /legislation what passed in Florida this week
— or just ask; Claude reaches for the tools on its own. For your own monthly
cap, register the hosted server with your key:
claude mcp add --transport http legislation https://mcp.delilahusa.com/ -H "Authorization: Bearer dk_live_..."MCP server only:
claude mcp add legislation --env DELILAH_API_KEY=dk_live_... -- npx -y @legislation/mcpTools
36 tools, all read-only except manage_webhooks (whose writes require confirm:true).
Bills
| Tool | Endpoint | What it does |
| --- | --- | --- |
| search_bills | /v1/bills | Search or list legislation with structured filters. Use this when you know the jurisdiction, sponsor, topic, or status you want. |
| semantic_search_bills | /v1/search/semantic | Vector search over the full text of every bill: describe what you want in plain English — 'protecting kids from social media', 'limits on rent increases' — and get the closest bills with a match_score. Prefer this over search_bills for questions and topics; prefer search_bills for a known bill number, sponsor, or status filter, and keyword_search_bills for exact terms that must all appear. |
| keyword_search_bills | /v1/search/bills | Keyword search where EVERY term must appear. Use when the user gives specific words that all have to be present, e.g. ['short-term rental', 'tax']. |
| get_bill | /v1/bills/{bill_id} | Fetch a single bill by its numeric bill_id, optionally merging in sub-resources: votes (roll calls with tallies), sponsors, history (action timeline), texts (version metadata), amendments, subjects, analyses (staff analyses), calendar (scheduled events), citations (statutes the bill touches), referrals (committee referrals), sast (same-as / similar-to companion bills in the other chamber or other states), supplements (fiscal notes and summaries). analyses and citations are populated only for the deeper-coverage states. Each include is a separate API call, so ask only for what you need. |
| get_bill_text | /v1/texts/{text_id} | The cleaned plain text of one bill-text version, entities decoded, ready to quote or summarize. Get text_id from get_bill with include:['texts'] (each version has a date and type). |
| get_bill_text_history | /v1/bills/{bill_id}/changes | Timeline of all computed diffs between successive text versions of one bill: what changed, when, and how much. Use get_bill_diff for one specific pair of versions. |
| get_bill_diff | /v1/bills/{bill_id}/diff | Compare two versions of a bill's text: a unified diff plus similarity, added/removed line and character counts, and changed-section highlights. Get the two version ids from get_bill with include:['texts'] — each text row's text_id. |
People and votes
| Tool | Endpoint | What it does |
| --- | --- | --- |
| list_legislators | /v1/legislators | Search or list legislators by jurisdiction, chamber, party, district, or name. This is how you turn a name into the people_id that get_legislator needs. |
| get_legislator | /v1/legislators/{people_id} | Fetch a legislator's profile by people_id, optionally merging in votes (every roll call they are recorded on, newest first, with their individual position: 1=Yea, 2=Nay, 3=Not Voting, 4=Absent) and bills (legislation they sponsor, with sponsor_type_id 1=primary, 2=cosponsor). Find a people_id with list_legislators or from a bill's sponsors. |
| get_legislator_scorecard | /v1/legislators/{people_id}/scorecard | One-call voting and sponsorship profile for a legislator: participation, party-line alignment, bills sponsored and passed, notable votes. This is the tool for 'how does X vote' or 'is X effective'. |
| get_roll_call | /v1/votes/{roll_call_id} | Fetch a roll call by roll_call_id: the motion, chamber, date and yea/nay/absent tallies. Add include:['details'] to also pull how every individual member voted (vote_id 1=Yea, 2=Nay, 3=Not Voting, 4=Absent) — this is the tool for 'how did each legislator vote on this'. |
Committees and hearings
| Tool | Endpoint | What it does |
| --- | --- | --- |
| list_committees | /v1/committees | List legislative committees, filterable by jurisdiction, chamber, or name search. Returns the committee_id values that get_committee needs. |
| get_committee | /v1/committees/{committee_id} | Fetch a committee by committee_id, optionally merging in members (its roster, with roles) and hearings (its scheduled meetings). Find a committee_id with list_committees. |
| list_hearings | /v1/hearings | Scheduled committee hearings across committees, filterable by jurisdiction and start date. Use this to answer 'what is being heard this week' or to track when a committee next meets. |
Sessions and analytics
| Tool | Endpoint | What it does |
| --- | --- | --- |
| list_sessions | /v1/sessions | Legislative sessions: regular and special sessions with their years, start/sine-die dates and a session_id. Use this to resolve which session a bill belongs to, to find the current session for a state, or to pass session_id as a filter to search_bills. |
| get_session_dashboard | /v1/sessions/{id}/dashboard | Headline counts for one legislative session: bills filed, passed, signed, vetoed, roll calls, active legislators. Start here for 'how is the 2026 Florida session going'. |
| get_session_sponsor_stats | /v1/sessions/{id}/sponsor-stats | Legislators ranked by bills sponsored and passed in one session. Each row has bills_sponsored, bills_primary, bills_passed, bills_primary_passed and pass_rate (passed = status 4, 5 or 8). |
| get_session_vote_participation | /v1/sessions/{id}/vote-participation | How many roll calls each legislator voted in versus missed during one session. Use for attendance questions. |
| list_session_legislators | /v1/sessions/{id}/legislators | Every legislator who served in one session, with people_id. Use list_legislators for the current roster by state; use this for a specific past or special session. |
| get_session_breakdown | /v1/sessions/{id}/status-counts | Bill count per status (by:'status', on the status_id ladder) or per subject (by:'subject') for one session. Get session_id from list_sessions. status_id values: 1=Introduced, 2=In Committee, 3=Passed One Chamber, 4=Passed Both Chambers, 5=Signed into Law, 6=Vetoed, 7=Failed/Dead, 8=Chaptered. |
| get_trending_bills | /v1/trending | Bills with the most news coverage in the window, optionally for one state. Use for 'what's in the news' or 'hot bills right now'. |
| list_bills_by_stage | /v1/bills-by-stage | Bills whose LATEST action in the window falls in a stage: introduced, committee, passed, executive (with the governor, signed, or vetoed), amendments, other. Fleet-wide across all jurisdictions — there is no state filter, so filter the rows by state_id or state_name yourself. |
| get_weekly_stage_breakdown | /v1/bills-weekly-breakdown | One row per stage with the number of bills whose latest action landed there in the window. All jurisdictions combined. |
Elections and geography
| Tool | Endpoint | What it does |
| --- | --- | --- |
| list_candidates | /v1/candidates | Search the election-candidate directory: people running for office. Filters combine with AND. |
| get_candidate | /v1/candidates/{id} | Fetch one election candidate by id: their party, office, district and cycle, plus an FEC filer id where the campaign has filed and a Bioguide id for sitting members of Congress. When people_id is set, the candidate has a legislative voting record — pass it to get_legislator or get_legislator_scorecard to go from candidate to voting history in one hop. |
| get_midterms | /v1/midterms | The election slate for 2026-11-03: races, candidates and ballot measures per state, with official results where available. All data is official state election-office and FEC content collected by Delilah — no third-party attribution is required. live_results carries ONE ENTRY PER CONTEST, each with contest (e.g. primary:DEM, runoff:REP) and party: a closed primary returns each party separately and vote_pct is the share of THAT party's ballot, so never compare or rank candidates across two contests. |
| get_ballot_measure | /v1/measures | Ballot measures: the list for a state, or one measure by its number (e.g. 'Amendment 1') with full text, summary and official source URL. Use for 'what's on the ballot in X' and 'what does Amendment N do'. |
| get_candidate_photo | /v1/photos/{candidate_id} | The URL of a candidate's official portrait. Sources are official state-legislature and congressional portraits, public-domain and licensed images only — never a third party's photo library — and each carries a license and attribution string that MUST be shown when the license requires it (Wikimedia CC BY). |
| list_districts | /v1/districts | District metadata by state and type: congressional, state senate, state house. Pass district_id with boundary:true to get the polygon as GeoJSON — it is large, so set simplify (degrees, e.g. 0.01) to shrink it. |
Monitoring and reference
| Tool | Endpoint | What it does |
| --- | --- | --- |
| get_changes | /v1/changes | Near-real-time feed of what changed. Poll with since= to get everything newer than your cursor; with no since it defaults to the last hour. |
| list_topics | /v1/topics | The canonical topic taxonomy with per-topic bill counts. Call this first to find the right slug for the topic filter on search_bills, instead of guessing one. |
| list_subjects | /v1/subjects | The raw subject taxonomy with per-subject bill counts. Subjects are the fine-grained categories the legislatures themselves assign to bills — narrower and more numerous than the curated topic slugs from list_topics. |
| list_states | /v1/states | The jurisdiction directory: the 50 states, D.C. and the U.S. Congress with their state_id, 2-letter code and full name. Returns the full list (52 rows) so you can resolve a name to a code or enumerate everything the corpus covers. |
| get_coverage | /v1/coverage | Per-state depth snapshot: how COMPLETE each jurisdiction's data is — bill counts, the share of bills holding at least one vote, text or sponsor, and whether the legislature is currently in session. Use it before drawing conclusions from a thin result set, to tell 'no such bills' apart from 'this state is thinly covered'. |
Account
| Tool | Endpoint | What it does |
| --- | --- | --- |
| get_usage | /v1/_meta/usage | The current key's tier, monthly cap, requests used and remaining, and when the period resets. Call this when a request is rate-limited (HTTP 429) or the user asks about their plan. |
| manage_webhooks | /v1/webhooks | Manage the key's webhook subscriptions. action:'list' is read-only. 'create' REGISTERS a URL that Delilah will POST to on the chosen events — it changes the account, so it requires confirm:true and the user's explicit go-ahead. |
On semantic_search_bills
True vector search over the full text of every bill (GET /v1/search/semantic):
ask a question in plain English and each result carries a match_score. Use
search_bills for a known bill number, sponsor or status filter, and
keyword_search_bills when specific terms must all appear.
Status IDs
| id | Meaning | | --- | --- | | 1 | Introduced | | 2 | In Committee | | 3 | Passed One Chamber | | 4 | Passed Both Chambers | | 5 | Signed into Law | | 6 | Vetoed | | 7 | Failed / Dead | | 8 | Chaptered (law) |
Aggregate buckets: passed = {4, 5, 8}, failed = {6, 7}, alive = {1, 2, 3}. The
status_id filter on search_bills and the status field on every bill object
use this scheme.
Notes
- Coverage window: the corpus is 2026-forward only.
- Response size: long arrays are capped and long strings clipped, each with
an explicit truncation marker. Page with
limit/offsetfor the rest. - Errors: API error bodies are passed through verbatim, including
request_idand anyX-RateLimit-*/Retry-Afterheaders. - Includes: each entry in an
includearray is a separate API call against your rate limit, so request only what you need.
Configuration
| Variable | Required | Default |
| --- | --- | --- |
| DELILAH_API_KEY | yes, for tool calls | — |
| DELILAH_API_BASE | no | https://api.delilahusa.com |
Requests time out after 30 seconds.
Development
npm install
npm run build # tsc -> dist/
npm test # vitest: catalog, client, errors, worker-catalog parity
npm run docs # regenerate the README tool tables from the catalog
npm run emit-worker # bundle the catalog into ../delilah-infrastructure/cloudflare-workers/mcp/tools.generated.js
DELILAH_API_KEY=… npm run contract # call every tool once against the live APITools live in src/tools/*.ts — one defineTool({...}) per tool, written
against the tiny Api interface in src/tools/types.ts. The stdio server
(src/index.ts) registers that catalog directly; the hosted worker imports a
bundle generated from the same files, so the two transports cannot drift.
Add a tool by adding it to a module and to the ordered list in
src/tools/index.ts, then run npm test, npm run docs and npm run emit-worker.
Requires Node 18 or newer.
License
MIT
