@cyanheads/congressgov-mcp-server
Access U.S. congressional data (bills, votes, members, committees) via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://congressgov.caseyjhand.com/mcp
Overview
US congressional data from Congress.gov and the Senate's official vote feed: bills, amendments, enacted laws, members, committees, roll call votes, nominations, CRS and committee reports, and the Congressional Record. Browse legislative activity, read bill and report text, and follow nominations through Senate confirmation.
Tools
| Tool | Description |
|---|---|
congressgov_bill_lookup | Browse and retrieve bills: actions, sponsors, summaries, text, related bills |
congressgov_amendment_lookup | Browse and retrieve House and Senate amendments: sponsor, amended bill, actions, cosponsors, text |
congressgov_enacted_laws | Browse enacted public and private laws by congress |
congressgov_member_lookup | Find members by state, district, or congress, and retrieve their legislative portfolios |
congressgov_committee_lookup | Browse committees and their legislation, reports, and nominations |
congressgov_roll_votes | Retrieve House and Senate roll call votes and each member's position |
congressgov_senate_nominations | Browse presidential nominations and track Senate confirmation |
congressgov_bill_summaries | Browse recently updated CRS bill summaries |
congressgov_crs_reports | Browse and retrieve nonpartisan CRS policy reports |
congressgov_committee_reports | Browse and read committee reports accompanying legislation |
congressgov_daily_record | Browse and read the daily Congressional Record |
congressgov_search_bills | Keyword-search bill titles and CRS summaries via a local full-text mirror (opt-in) |
Resources
| Resource | Description |
|---|---|
congress://current | Current congress number, session dates, chamber info |
congress://bill-types | Reference table of valid bill type codes |
congress://member/{bioguideId} | Member profile by bioguide ID |
congress://bill/{congress}/{billType}/{billNumber} | Bill detail by congress, type, and number |
congress://committee/{committeeCode} | Committee detail by committee code |
skill://congressgov-guide/SKILL.md | Legislative research guide: identifier sources, discovery boundaries, votes, and document evidence |
Bill, member, and committee data is also reachable through congressgov_bill_lookup, congressgov_member_lookup, and congressgov_committee_lookup (operation get), since many MCP clients are tool-only and never surface resources.
Prompts
| Prompt | Description |
|---|---|
congressgov_bill_analysis | Structured framework for analyzing a bill |
congressgov_legislative_research | Research framework for a policy area across Congress |
Agent skill
congressgov-guide guides research across bills, laws, members, committees, votes, and floor debate. It covers identifier provenance, discovery limits, and version-specific document evidence. The guide ships with plugin installs, npm, MCPB, and Docker, and is served over MCP through the Skills extension and its skill:// resource.
Capability reference
congressgov_bill_lookup tool
listbrowses onecongress, narrowed bybillTypeand afromDateTime/toDateTimerange on the record's update date, newest first unlessorder: 'oldest';get,actions,amendments,cosponsors,committees,subjects,summaries,text,titles,related, andcontentneedcongress+billType+billNumbertypeandnumberfrom a bill row are accepted asbillTypeandbillNumber; bill type codes are case-insensitive. Send one name per field, since canonical and alias keys together are rejectedcontentreads the text version attextVersionIndex(0 is the most recent) and returns one character window incontent.textwithtotalCharacters,truncated, andnextOffsetsummariestakes an optionalversionCode(00introduced,49public law) and pages likecongressgov_bill_summaries, usingcharacterOffset/characterLimitfromDateTime/toDateTimeapply tolistonly; other operations are served with a notice naming them as ignored- An
actionsrow that records exactly one roll call vote carriesrollVote, thecongressgov_roll_votesarguments for it (House votes from the 115th Congress, Senate from the 101st)
congressgov_amendment_lookup tool
- An amendment is
congress+amendmentType(hamdt,samdt, orsuamdt, in any letter case;suamdt, Senate unprinted amendments, exists only in the 97th and 98th Congresses) +amendmentNumber(a number or a digit string). Those identifiers come fromcongressgov_bill_lookupamendmentsrows, Housecongressgov_roll_votesrows (amendmentType/amendmentNumber), andcongressgov_member_lookupsponsored/cosponsoredrows, which carry the number and the type only in the row'surl listbrowses onecongress, optionally oneamendmentType, newest update first unlessorder: 'oldest', withfromDateTime/toDateTimeon the record's update date; it refusesamendmentNumber(incompatible_identity). Other operations are served with a notice naming a date bound as ignoredgetreturns the sponsor, purpose, description, the bill or amendment amended, and the latest action;actions,cosponsors,amendments(amendments to this amendment), andtextpage the sub-resources- An
actionsrow that records exactly one roll call vote carriesrollVote, thecongressgov_roll_votesarguments for it textlists the published text versions, each a Congressional Record page;contentreads the HTML of the version attextVersionIndexas onecharacterOffset/characterLimitwindow. The page prints the amendment amid the surrounding floor proceedings, so it is often far longer than the amendment
congressgov_enacted_laws tool
listbrowses onecongress, optionally bylawType(puborpriv), the enactment filtercongressgov_bill_lookuplacks;getneedslawType+lawNumber, taken from a list row'slaws[].number(118-90) or its bare number (90)getreturns the origin bill record, with the law citation on itslaws[]array
congressgov_member_lookup tool
- No name search:
listfilters bystateCode(plusdistrict,0for at-large, which requiresstateCode),congress, andcurrentMember getreturns the profile for abioguideId(e.g.P000197);sponsoredandcosponsoredreturn that member's legislation
congressgov_committee_lookup tool
committeeCodeis a chamber prefix (h/s/j), letters, and a 2-digit suffix (e.g.hsju00);get,bills,reports, and the Senate-onlynominationsinferchamberfrom the prefix, and read it from the committee roster when the prefix's chamber doesn't hold the code (jsat00is a Senate committee);chamberis accepted in any letter case, as committee rows carry it (Senate)- A committee name in
committeeCoderesolves when exactly one committee matches, and otherwise returns candidate rows with a notice. Withchamberset, only that chamber's committees are searched, so a name unique within one chamber (judiciarywithchamber: 'senate') resolves in one step, and a lone fuzzy match there comes back as a candidate row; withoutchamber, a lone fuzzy match resolves and the notice says it is approximate. The resolved committee is read in the chamber its roster row names.listwithfiltername-matches every committee in scope, and fuzzy hits carryapproximate: true billsis newest first by default (order), and its rows carry no titles, so chaincongressgov_bill_lookupgetper row
congressgov_roll_votes tool
chamberishouse(default, Congress.gov API) orsenate(the Senate's LIS feed), in any letter case; every call needscongress+session(1 or 2), andget/membersaddvoteNumber, which resets each session and is specific to one chambermembersfilters bybioguideId(House only),stateCode, andparty, all applied before paging:pagination.countcounts the matches andmemberFilter.rosterCountis the roster size. A Senate call withbioguideIdfailsunsupported_filter, whichever source answers, since the Senate feed identifies senators by its own IDs; filter senators bystateCodelistandgetserve the call and name any of those filters as ignored- A congress's first year is session 1 and its second year session 2, but a second session can run into January 1–3 of the following year (the 116th Congress's second session voted on 2021-01-01)
listis newest first unlessorder: 'oldest';getreturns the question, result, tallies, and party breakdown, andmembersreturns each member's position- House votes are covered from the 115th Congress (2017) and Senate votes from the 101st (1989), and Senate rows pair the feed's year-less
voteDatewith a derivedvoteDateIso(rows Voteview serves carryvoteDateIsoalone) - A House
getormemberscall below the 115th Congress failsnot_foundbefore any request, naming that coverage and pointing at the Clerk's vote XML in a bill action'srecordedVotes - When the Senate LIS feed can't answer a
list,get, ormemberscall (senate.gov refusing automated clients with HTTP 403 or its firewall page, another 4xx, a 5xx or timeout after retries, an empty body), Voteview's per-congress files answer instead, and the response carriessource(name, url,latestVote, and Voteview's requested citation). Voteview's President's positions are dropped, and a vote aftersource.latestVoteisnot_foundnaming that vote. The feed's own not-found page, a cancelled call, a spent call budget, and congresses before the 101st never fall back. When the feed answers, the output is unchanged and Voteview is not contacted. Voteview's files are cached for 1 hour. House votes are unaffected
congressgov_senate_nominations tool
listbrowses onecongress, newest update first;fromDateTime/toDateTimefilter on the record's update date, not its received or action date.get,actions,committees,hearings, andnomineesneed anominationNumber(1000for PN1000;PN730-2,pn730-2, and730-2are equivalent), andnomineesalso needs anordinalfrom thenomineesarraygetreturns- Multi-part nominations (e.g. PN851) keep their activity on partitioned children (
851-1,851-2, …); an empty sub-resource call on the bare number returns a notice pointing there - An action whose text cites its own Senate record vote (
Record Vote Number: 225.) carriesrollVote—{ chamber: 'senate', congress, session, voteNumber }, thecongressgov_roll_votesarguments for that vote. Every citingactionsrow gets one, cloture votes included, and so doeslatestActiononlistrows andget; votes before the 101st Congress get none
congressgov_bill_summaries tool
- Optional
congressandbillType(which requirescongress);fromDateTime/toDateTimefilter on the summary's update time, not the bill's action date, and cover the last 7 days when both are omitted - A page returns
limitrows, andpagination.nextOffsetresumes; a summary over 25,000 characters arrives windowed withtextTotalCharacters,textTruncated, andtextNextOffset - For one bill's summaries, or to read a windowed summary to the end, use
congressgov_bill_lookupsummaries
congressgov_crs_reports tool
listbrowses the catalog, newest update first;fromDateTime/toDateTimefilter on the date Congress.gov last updated a report, not its publication date.getandcontenttake areportNumber: letters then digits (R40097,RL33612,IF12345) or, for older reports, two digit groups joined by a hyphen (96-548), in any caseid,reportId, andreport_idare accepted asreportNumber; send only one identifier keygetreturns authors, topics, summary, and download formatscontentreads the report's HTML version as plain text in acharacterOffset/characterLimitwindow (script, style, and comments dropped; tables as tab-separated rows). A PDF-only report failsformat_unavailable, naming its PDF URL
congressgov_committee_reports tool
listbrowses onecongress, optionally byreportType(hrptHouse,srptSenate,erptExecutive).fromDateTime/toDateTimefilter on the date Congress.gov last updated a report record, andconferenceselects conference reports (true) or the others (false);get,text, andcontentneedreportType+reportNumbergetreturns the citation, title, committees, and associated bill;textlists{type, url}format links, andcontentreads the report as one character window- A multi-part report is filed as one record per part:
part(default 1) selects one onget,text, andcontent, andpartslists every part number when there is more than one. A part the report lacks failsnot_found, naming the parts it has
congressgov_daily_record tool
- Navigation runs
list(volumes) →issues(byvolumeNumber) →articles(byvolumeNumber+issueNumber) contentreads the article atarticleIndex(0-based across the issue) as one character window; Record articles publish Formatted Text and PDF only, soformat: 'xml'fails asformat_unavailable
congressgov_search_bills tool
querykeywords (AND-combined) over bill titles and CRS summaries, narrowed bycongress,billType, andoriginChamber;limit1–100,offsetpagination- BM25-ranked rows carry
billIdpluscongress/billType/billNumberforcongressgov_bill_lookup, and asummaryPreview; policy area and full bill text are not indexed - Every response names the searchable congresses in
mirroredCongresses; acongressoutside them returns an empty result with a notice pointing atcongressgov_bill_lookup - Listed only when
CONGRESS_MIRROR_ENABLED=true; untilbun run mirror:initbuilds the index, it returns an empty result with a notice
congress://current resource
- Current congress number, session dates, and chamber info, the baseline for other queries
- Cached publicly for 1 hour
congress://bill-types resource
- The 8 bill type codes (
hr,s,hjres,sjres,hconres,sconres,hres,sres) with description, chamber, and an example citation - Static, with no upstream call; cached publicly for 24 hours
congress://member/{bioguideId} resource
bioguideIdis one uppercase letter and 6 digits (e.g.P000197)- Returns the member profile: name, state, party, terms, leadership, office, legislation counts
congress://bill/{congress}/{billType}/{billNumber} resource
congressandbillNumberare positive integers;billTypeis one of the 8 bill type codes- Returns bill detail: sponsor, status, policy area, committees, latest action
congress://committee/{committeeCode} resource
committeeCodeish/s/jfollowed by 3–8 lowercase alphanumeric characters (e.g.hsju00); chamber comes from the first letter- Returns committee detail: name, chamber, subcommittees, history, legislation counts
congressgov_bill_analysis prompt
- Arguments:
congress,billType, andbillNumber, all required - Returns one user message framing a seven-part analysis, from summary to outlook, with the tools to call for each part
congressgov_legislative_research prompt
- Arguments:
topicrequired;congressoptional, defaulting to the current congress - With
CONGRESS_MIRROR_ENABLED, the plan opens withcongressgov_search_bills; without it, the prompt asks for a seed bill, member, committee, or CRS report ID, since no other tool takes a topic
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Congress.gov-specific:
- Type-safe client for the Congress.gov REST API v3, plus a client for the Senate's LIS XML feed that backs Senate roll votes, with Voteview's published vote files as the fallback when that feed can't be read
- The Congress.gov API has no keyword search, so tools browse by congress, type, date range, chamber, state, and district; list operations page with
limit1–250 (default 20) andoffset contenton bill text, amendment text, committee reports, Record articles, and CRS reports reads a document in acharacterOffset/characterLimitwindow (1–100,000 characters, default 25,000), fetched only fromwww.congress.govunder a 25 MB ceiling; bill text, committee reports, and Record articles takeformattext(default) orxml, a CRS report is always read as its HTML version, and an amendment is read as the HTML of its Congressional Record page. A document longer than one window (up to 16 MB) is downloaded once and its later windows are served from a 10-minute in-memory cache- Each tool call or resource read gets 50 seconds for all of its upstream requests, retries and backoff included, whether it makes one request or chains several (a
contentcall's metadata lookup and document fetch, a committee name resolved against the committee roster, one chamber's whenchamberis set). A stalled upstream ends the call before the 60-second default timeout of MCP clients:upstream_timeoutfrom Congress.gov, the retryableupstream_errorfrom the Senate LIS feed and the Voteview fallback - Optional API key from api.data.gov:
DEMO_KEYallows 30 requests/hour and 50/day per IP, registered keys 5,000/hour - Opt-in local SQLite FTS5 mirror (
CONGRESS_MIRROR_ENABLED) adds keyword search over bill titles and CRS summaries
Agent-friendly output:
- Every list response carries
effectiveQueryandtotalCount, with anoticewhen nothing matched listrefuses an identity field (memberbioguideId, lawlawNumber, committeecommitteeCodewith a committee name pointed atfilter, nominationnominationNumber, committee reportpart, andbillNumber,voteNumber,reportNumber,volumeNumber) with anincompatible_identityerror that names the operation to use instead- Inputs an operation doesn't use are served with a notice naming them as ignored; a member's sponsored and cosponsored legislation and a committee's bills, reports, and nominations span every congress
contentreturnsoffset,truncated, andnextOffset, so walking a multi-megabyte bill never skips or repeats a character- Typed failures (
document_unavailable,format_unavailable,document_fetch_failed,document_too_large,offset_past_end,unsupported_filter,not_found,rate_limited,invalid_request,api_key_rejected,upstream_error,upstream_timeout), each with a recovery hint
Getting started
Connect to the hosted instance:
claude mcp add --transport http congressgov-mcp-server https://congressgov.caseyjhand.com/mcp
Or run it locally:
claude mcp add congressgov-mcp-server -- npx -y @cyanheads/congressgov-mcp-server
For other clients, add this to the MCP config file:
{
"mcpServers": {
"congressgov-mcp-server": {
"command": "npx",
"args": ["-y", "@cyanheads/congressgov-mcp-server"]
}
}
}
Optional: set CONGRESS_API_KEY to a free api.data.gov key for 5,000 requests/hour instead of the demo key's 30/hour and 50/day per IP. For Docker or a source build, see Running the server.
Configuration
| Variable | Description | Default |
|---|---|---|
CONGRESS_API_KEY | API key from api.data.gov. | DEMO_KEY |
CONGRESS_API_BASE_URL | Congress.gov API base URL. | https://api.congress.gov/v3 |
CONGRESS_MIRROR_ENABLED | Enable the local bill search mirror and the congressgov_search_bills tool. | false |
CONGRESS_MIRROR_PATH | Filesystem path to the SQLite mirror index. | .mirror/bills.sqlite3 |
CONGRESS_MIRROR_REFRESH_CRON | Cron schedule for the in-process mirror refresh (HTTP transport only). Unset means running mirror:refresh yourself. | — |
CONGRESS_MIRROR_CONGRESSES | Comma-separated congress numbers to mirror (e.g. 118,119). | current congress + 1 prior |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto; auto resolves to stateful. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, notice, warning, error, etc.). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
OTEL_ENABLED | Enable OpenTelemetry. | false |
OTEL_EXPORTER_OTLP_ENDPOINT | Base OTLP endpoint for traces and metrics. | — |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT | Explicit OTLP logs endpoint; the base endpoint does not enable log export. | — |
LOG_TOOL_FAILURE_PAYLOADS | Log failed-call input and output with key-based redaction; secrets inside free-form values are not redacted. | false |
LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES | Maximum bytes per failed-call input or output record. | 16384 |
See .env.example for the full list of optional overrides.
Running the server
Requires Bun v1.4.0 or higher (or Node.js v24+).
git clone https://github.com/cyanheads/congressgov-mcp-server.git
cd congressgov-mcp-server && bun install
cp .env.example .env
# Optionally set CONGRESS_API_KEY and the mirror variables in .env.
Local development
-
Build and run the production version:
bun run rebuild bun run start:http # or start:stdio -
Build the bill search mirror from this checkout (with
CONGRESS_MIRROR_ENABLED=true):bun run mirror:init # full build bun run mirror:refresh # incremental refresh bun run mirror:verify # readiness and integrity checkThe mirror CLI requires this checkout or the Docker image's path mapping; an npm installation cannot run these scripts.
-
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
docker run --rm -e CONGRESS_API_KEY=your-api-key -p 3010:3010 ghcr.io/cyanheads/congressgov-mcp-server:latest
The image defaults to HTTP transport, stateless session mode, and logs to /var/log/congressgov-mcp-server. For stdio, use docker run -i --rm -e MCP_TRANSPORT_TYPE=stdio ghcr.io/cyanheads/congressgov-mcp-server:latest. To build locally, run docker build -t congressgov-mcp-server .; OpenTelemetry peer dependencies are installed by default, with --build-arg OTEL_ENABLED=false to omit them. The image carries the mirror scripts, so docker exec <container> bun run mirror:init builds the index in place.
Project structure
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point: registers tools, resources, and prompts, inits services, and schedules the mirror refresh. |
skills/ | Published legislative research guide for agents calling the server. |
src/config/ | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools/ | Tool definitions (definitions/*.tool.ts) plus shared input, formatting, and summary-window helpers. |
src/mcp-server/resources/definitions/ | Resource definitions (*.resource.ts). |
src/mcp-server/prompts/definitions/ | Prompt definitions (*.prompt.ts). |
src/services/call-budget.ts | The 50-second budget every tool call's upstream requests share. |
src/services/congress-api/ | Congress.gov API client: auth, pagination, rate limiting. |
src/services/congress-documents/ | Bounded document-text fetch: host allowlist, byte ceiling, character window, and a 10-minute cache of document bodies. |
src/services/congress-mirror/ | Local SQLite FTS5 bill-search mirror: ingest, normalize, schema. |
src/services/senate-lis/ | Senate LIS XML client for Senate roll call votes, with a Voteview CSV fallback. |
src/utils/ | HTML/XML character-reference decoding and congress-number arithmetic. |
scripts/ | Build, devcheck, and release tooling, plus the mirror:init / mirror:refresh / mirror:verify CLIs. |
tests/ | Unit and integration tests, mirroring the src/ structure. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storage - All tools are read-only, with
readOnlyHint: trueandidempotentHint: trueannotations - Wrap external API calls: validate raw → normalize to a domain type → return the output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.