
Wraps the FEC's OpenFEC API with nine tools covering candidates, committees, contributions, disbursements, independent expenditures, filings, elections, legal documents, and the FEC calendar. You can search itemized Schedule A contributions with filters for employer, occupation, state, and amount range, or pull aggregated breakdowns by size bracket. The disbursements tool works the same way for Schedule B spending. Independent expenditures show support or oppose positions per candidate. Five resources expose candidate profiles, committee details, and election races at the presidential, senate, and house district level. Two prompts frame campaign finance analysis and money trail investigations. Requires an FEC API key. Available via stdio, streamable HTTP, or a hosted instance at openfec.caseyjhand.com/mcp.
Public tool metadata for what this MCP can expose to an agent.
openfec_search_candidatesFind federal candidates by name, state, office, party, or cycle. Retrieve a specific candidate by FEC ID with financial totals. Candidate IDs start with H (House), S (Senate), or P (President) followed by digits.14 paramsFind federal candidates by name, state, office, party, or cycle. Retrieve a specific candidate by FEC ID with financial totals. Candidate IDs start with H (House), S (Senate), or P (President) followed by digits.
candidate_idstringcandidate_statusstringC · F · N · Pcyclenumberdistrictstringelection_yearnumberhas_raised_fundsbooleaninclude_totalsbooleanincumbent_challengestringI · C · OofficestringH · S · Ppagenumberpartystringper_pagenumberquerystringstatestringopenfec_search_committeesFind political committees (campaign, PAC, Super PAC, party) by name, type, candidate affiliation, or state. Retrieve a specific committee by FEC ID. Committee IDs start with C followed by digits (e.g., C00358796).11 paramsFind political committees (campaign, PAC, Super PAC, party) by name, type, candidate affiliation, or state. Retrieve a specific committee by FEC ID. Committee IDs start with C followed by digits (e.g., C00358796).
candidate_idstringcommittee_idstringcommittee_typestringcyclenumberdesignationstringpagenumberpartystringper_pagenumberquerystringstatestringtreasurer_namestringopenfec_search_contributionsSearch itemized individual contributions (Schedule A) or get aggregate breakdowns by size, state, employer, or occupation. Use to answer "who is funding this committee?" Itemized mode requires a committee_id. Aggregate by_size/by_state can use candidate_id instead.18 paramsSearch itemized individual contributions (Schedule A) or get aggregate breakdowns by size, state, employer, or occupation. Use to answer "who is funding this committee?" Itemized mode requires a committee_id. Aggregate by_size/by_state can use candidate_id instead.
candidate_idstringcommittee_idstringcontributor_citystringcontributor_employerstringcontributor_namestringcontributor_occupationstringcontributor_statestringcontributor_zipstringcursorstringcyclenumberis_individualbooleanmax_amountnumbermax_datestringmin_amountnumbermin_datestringmodestringitemized · by_size · by_state · by_employer · by_occupationdefault: itemizedper_pagenumbersortstringcontribution_receipt_date · contribution_receipt_amountopenfec_search_disbursementsSearch itemized committee spending (Schedule B) or get aggregate breakdowns by purpose or recipient. All modes require a committee_id. Use to answer "what is this committee spending money on?" or "who is receiving payments from this committee?"16 paramsSearch itemized committee spending (Schedule B) or get aggregate breakdowns by purpose or recipient. All modes require a committee_id. Use to answer "what is this committee spending money on?" or "who is receiving payments from this committee?"
committee_idstringcursorstringcyclenumberdisbursement_descriptionstringdisbursement_purpose_categorystringmax_amountnumbermax_datestringmin_amountnumbermin_datestringmodestringitemized · by_purpose · by_recipient · by_recipient_iddefault: itemizedper_pagenumberrecipient_citystringrecipient_committee_idstringrecipient_namestringrecipient_statestringsortstringdisbursement_date · disbursement_amountopenfec_search_expendituresSearch independent expenditures (Schedule E) — outside spending supporting or opposing federal candidates. Covers Super PACs, party committees, and other groups. Use itemized mode for individual expenditure records, or by_candidate for aggregated totals per candidate.18 paramsSearch independent expenditures (Schedule E) — outside spending supporting or opposing federal candidates. Covers Super PACs, party committees, and other groups. Use itemized mode for individual expenditure records, or by_candidate for aggregated totals per candidate.
candidate_idstringcandidate_officestringH · S · Pcandidate_office_statestringcandidate_partystringcommittee_idstringcursorstringcyclenumberis_noticebooleanmax_amountnumbermax_datestringmin_amountnumbermin_datestringmodestringitemized · by_candidatedefault: itemizedmost_recentbooleanpayee_namestringper_pagenumbersortstringexpenditure_date · expenditure_amount · office_total_ytdsupport_opposestringS · Oopenfec_search_filingsSearch FEC filings and reports by committee, candidate, form type, or date range. Covers financial reports (F3/F3P/F3X), statements of candidacy (F2), organizational filings (F1), 24-hour IE notices (F24), and amendments.13 paramsSearch FEC filings and reports by committee, candidate, form type, or date range. Covers financial reports (F3/F3P/F3X), statements of candidacy (F2), organizational filings (F1), 24-hour IE notices (F24), and amendments.
candidate_idstringcommittee_idstringcyclenumberfiler_namestringform_typestringis_amendedbooleanmax_receipt_datestringmin_receipt_datestringmost_recentbooleanpagenumberper_pagenumberreport_typestringreport_yearnumberopenfec_lookup_electionsLook up federal election races and candidate financial summaries. Find who's running in a race with fundraising totals, or get an aggregate race summary.7 paramsLook up federal election races and candidate financial summaries. Find who's running in a race with fundraising totals, or get an aggregate race summary.
cycleintegerdistrictstringelection_fullbooleanmodestringsearch · summarydefault: searchofficestringpresident · senate · housestatestringzipstringopenfec_search_legalSearch FEC legal documents: advisory opinions, enforcement cases (MURs), alternative dispute resolutions, and administrative fines.13 paramsSearch FEC legal documents: advisory opinions, enforcement cases (MURs), alternative dispute resolutions, and administrative fines.
ao_numberstringcase_numberstringfrom_hitintegerhits_returnedintegermax_datestringmax_penalty_amountnumbermin_datestringmin_penalty_amountnumberquerystringregulatory_citationstringrespondentstringstatutory_citationstringtypestringadvisory_opinions · murs · adrs · admin_fines · statutesopenfec_lookup_calendarLook up FEC calendar events, filing deadlines, and election dates.12 paramsLook up FEC calendar events, filing deadlines, and election dates.
categorystringdescriptionstringelection_yearintegermax_datestringmin_datestringmodestringevents · filing_deadlines · election_datesdefault: eventsofficestringH · S · Ppageintegerper_pageintegerreport_typestringreport_yearintegerstatestringAccess FEC campaign finance data on candidates, money trails, and election filings via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://openfec.caseyjhand.com/mcp
US federal campaign finance data from the FEC's OpenFEC API. Search candidates, committees, and filings, trace contributions (Schedule A), disbursements (Schedule B), and independent and coordinated party expenditures (Schedules E/F), and look up election races, legal documents, and filing deadlines. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
openfec_search_candidates | Find federal candidates by name, state, office, party, or cycle; fetch one by FEC ID with financial totals |
openfec_search_committees | Find political committees by name, type, candidate affiliation, or state; fetch one by FEC ID |
openfec_get_committee_totals | Pre-aggregated financial totals for one committee by cycle, or ranked across committees of one entity type |
openfec_search_contributions | Itemized contributions (Schedule A), or aggregates by size, state, employer, or occupation |
openfec_search_disbursements | Itemized committee spending (Schedule B), or aggregates by purpose or recipient |
openfec_search_expenditures | Independent expenditures (Schedule E) supporting or opposing candidates, itemized or totaled per candidate |
openfec_search_coordinated_expenditures | Coordinated party expenditures (Schedule F) made on behalf of a candidate |
openfec_search_filings | FEC filings and reports by committee, candidate, form type, or date range |
openfec_lookup_elections | Candidates in a federal race with their fundraising totals, or an aggregate race summary |
openfec_search_legal | Advisory opinions, enforcement cases, alternative dispute resolutions, administrative fines, and statutes |
openfec_get_legal_document | One legal document in full, including the arrays search trims away, paged by entry when too large for one response |
openfec_lookup_calendar | FEC calendar events, filing deadlines, and election dates |
| Resource | Description |
|---|---|
openfec://candidate/{candidate_id} | Candidate profile with financial totals and principal committees |
openfec://committee/{committee_id} | Committee profile with type, designation, and financial summary |
openfec://election/{cycle}/{office} | Presidential race with candidate financial totals |
openfec://election/{cycle}/{office}/{state} | Senate or at-large House race with candidate financial totals |
openfec://election/{cycle}/{office}/{state}/{district} | House district race with candidate financial totals |
The same data is reachable through openfec_search_candidates, openfec_search_committees, and openfec_lookup_elections for tool-only clients.
| Prompt | Description |
|---|---|
openfec_money_trail | Trace the money around a candidate: direct fundraising, PAC support, independent expenditures, and party spending |
openfec_campaign_analysis | Structured analysis of a candidate's financial position |
openfec_search_candidates toolquery name search filtered by state, district, office, party, cycle, election_year, incumbent_challenge, candidate_status, and has_raised_funds, up to 100 per page; or a candidate_id lookup (H/S/P + eight letters or digits) returning one full recordinclude_totals (default on for an ID lookup) adds per-cycle totals rows and caps a search page at 35 candidates when cycle or election_year scopes the totals, 5 when they span every cycle; candidates the totals fetch didn't reach are listed in missing_totalscandidate_not_found, inputs_not_applicable_to_id_lookupopenfec_search_committees toolquery name search filtered by candidate_id, state, party, committee_type, designation, cycle, and treasurer_name, up to 100 per page; or a committee_id lookup (C + eight digits)committee_not_found, inputs_not_applicable_to_id_lookupopenfec_get_committee_totals toolmode: "single" (default) takes a committee_id and returns one row per two-year cycle; mode: "by_entity_type" ranks every committee of one entity_type (presidential, pac, party, pac-party, house-senate, ie-only), filterable by state, type, designation, organization type, and receipts/disbursements bounds; up to 100 per pagemode says whether rows are cycles or committeescommittee_id_required_for_single_mode, entity_type_required_for_group_mode, committee_totals_not_foundopenfec_search_contributions toolitemized (default) needs a committee_id and filters by contributor name, employer, occupation, city, state, ZIP, date and amount ranges, and is_individual, 30 rows per page; by_size / by_state take a committee_id or candidate_id, by_employer / by_occupation a committee_idmode becomes by_size_candidate or by_state_candidate when scoped by candidate, a different row shape; itemized responses carry next_cursor, count, and count_is_approximateitemized_requires_committee_id, aggregate_requires_committee_idopenfec_search_disbursements toolcommittee_id required in every mode: itemized (default; recipient name, state, city, and committee ID, description, purpose category, date and amount ranges, 30 rows per page), by_purpose, by_recipient, by_recipient_idnext_cursor, count, and count_is_approximate; aggregate modes page by numberopenfec_search_expenditures toolitemized (default) filters by committee, candidate, support_oppose, payee, targeted office/state/district, party, is_notice (24/48-hour notices), dates, and amounts; most_recent defaults to true; 60 rows per page when scoped by committee_id, 30 otherwiseby_candidate totals need a candidate_id or a full race scope (candidate_office=P alone, S plus candidate_office_state, H plus state and candidate_office_district), or fail as by_candidate_requires_scopeopenfec_search_coordinated_expenditures toolcommittee_id, benefiting candidate_id, cycle, payee, dates, and amounts; unscoped queries span all yearscommittee_id and 25 otherwise; rows carry subordinate_committee_id, the committee the spending is attributed toopenfec_search_filings toolmost_recent defaults to true, hiding superseded amendments; rows carry form and report type, financial totals, and pdf_urlopenfec_lookup_elections tooloffice and an even cycle are required; Senate also needs state and House state plus district, unless a zip scopes a searchmode: "search" (default) returns the race's candidates with totals and candidate_pcc_id / candidate_pcc_name, that cycle's principal campaign committee; mode: "summary" returns one aggregate race rowcycle_must_be_even, missing_state_for_office, missing_district_for_house, summary_does_not_support_zipopenfec_search_legal tooltype (advisory_opinions, murs, adrs, admin_fines, statutes), query, ao_number, case_number, respondent, citations, penalty bounds, or dates; at least one is required (missing_filter), and a date bound needs type plus a date_kind that type recordstype it doesn't filter fails as filter_not_valid_for_type, and with type omitted the search returns only the types they all apply to, with a total over those types. On MURs and ADRs a citation can't be combined with case_number, respondent, a penalty bound, or an open/close date bound, which upstream would ignore; the two citation fields together match either one52 U.S.C. 30104 or 11 CFR 110.1 (USC, C.F.R., §, and suffixes like 30104(g) are fine); a bare 30106 or 110.1 fails as invalid_citation instead of silently returning unfiltered results. One citation per field: upstream applies only the first citation in a value, so 52 U.S.C. 30104, 52 U.S.C. 30118 fails the same wayfrom_hit / hits_returned, up to 200 per type within a 10,000-result windowdocuments becomes document_count and document_categories, dispositions becomes disposition_count and disposition_categories, highlights stop at 3 with their <em> match markup removed and render one per line, and commission_votes shrink to a date and a 200-character action; openfec_get_legal_document returns the full recordtruncated, shown, and nextFromHit — the from_hit that continues each type, passed back with that typeopenfec_get_legal_document tooldoc_type is the plural of a search result's document_type (mur → murs); no is that result's no fielddocuments, commission_votes, and dispositions; fails as legal_document_not_foundwithheld — each held-back array with its entry count and size; re-call with array and offset for that array's entries while they fit, plus next_offset. An array the record lacks fails as array_not_in_recordopenfec_lookup_calendar toolevents (default; category, one of 18 codes, and description), filing_deadlines (report_type, report_year), or election_dates (state, office, district, election_year); min_date / max_date apply in every mode, up to 100 per pagedistrict filter never matches an at-large race; pair it with stateopenfec://candidate/{candidate_id} resourceprincipal_committees (current designation P, not scoped to a cycle)candidate_id comes from openfec_search_candidates; fails as candidate_not_foundopenfec://committee/{committee_id} resourcecommittee_id comes from openfec_search_committees; fails as committee_not_foundopenfec://election/{cycle}/{office} resourceoffice P), with totals for the full election periodtruncation_notice pointing to openfec_lookup_elections, and an empty one empty_result_noticeopenfec://election/{cycle}/{office}/{state} resourceoffice S or H)openfec://election/{cycle}/{office}/{state}/{district} resourceoffice H)openfec_money_trail promptcandidate_name or candidate_id (one required), optional cyclecandidate_pcc_id, then direct fundraising, independent expenditures, coordinated party spending, disbursements, and a synthesisopenfec_campaign_analysis promptcandidate_name or candidate_id (one required), optional cycleBuilt 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.
OpenFEC-specific:
cycle is omitted, and election_full (default true) widens a race total to the full election period (4yr president, 6yr senate, 2yr house)committee_id, not a candidate_id. Committee designation and principal_committees reflect current designation only, so for a cycle's principal committee read candidate_pcc_id from openfec_lookup_electionsnext_cursor, valid only for an otherwise-identical call; other tools page by number, and legal search by offsetAgent-friendly output:
search_criteria with the post-default filters, and an empty result carries a notice on how to broaden ittruncated, shown, and cap when a page falls below the requested per_page (page-based tools also echo the applied size in pagination.per_page); legal search holds back whole results and reports nextFromHit, and a large legal document pages its arrays by offset; a single-committee query lifts that committee into one top-level committee field instead of repeating it per rowmode and reject a filter the mode can't apply (itemized_only_filters_in_aggregate_mode, inputs_not_applicable_to_mode) instead of dropping it; every failure carries a typed reason and recovery text, and estimated totals are flagged count_is_approximateA public instance is available at https://openfec.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"openfec-mcp-server": {
"type": "streamable-http",
"url": "https://openfec.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file.
{
"mcpServers": {
"openfec-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/openfec-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"FEC_API_KEY": "your-api-key"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"openfec-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/openfec-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"FEC_API_KEY": "your-api-key"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"openfec-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "-e", "FEC_API_KEY=your-api-key", "ghcr.io/cyanheads/openfec-mcp-server:latest"]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 FEC_API_KEY=your-key bun run start:http
# Server listens at http://localhost:3010/mcp
DEMO_KEY.git clone https://github.com/cyanheads/openfec-mcp-server.git
cd openfec-mcp-server
bun install
cp .env.example .env
# edit .env and set FEC_API_KEY (optional)
| Variable | Description | Default |
|---|---|---|
FEC_API_KEY | OpenFEC API key from api.data.gov. DEMO_KEY allows 30 req/hr; your own key allows 1,000. | DEMO_KEY |
FEC_BASE_URL | OpenFEC API base URL. | https://api.open.fec.gov/v1 |
FEC_MAX_RETRIES | Retry attempts for failed API requests. | 3 |
FEC_REQUEST_TIMEOUT | Per-request timeout, in ms. | 30000 |
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. Overrides the stateless mode the server declares. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry. | false |
See .env.example for the full list of optional overrides.
Build and run:
bun run rebuild
bun run start:stdio # or start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security audit
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
docker build -t openfec-mcp-server .
docker run --rm -e FEC_API_KEY=your-key -p 3010:3010 openfec-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/openfec-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools/resources/prompts and inits the OpenFEC service. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts), with shared validators and formatters in definitions/utils/. |
src/mcp-server/resources | Resource definitions (*.resource.ts). Candidate, committee, and three election-race templates. |
src/mcp-server/prompts | Prompt definitions (*.prompt.ts). Money trail and campaign analysis. |
src/services/openfec | OpenFEC API client and domain types. |
tests/ | Unit and integration tests, mirroring the src/ structure. |
scripts/ | Build, clean, devcheck, tree, changelog, and lint scripts. |
docs/ | Design notes and the OpenFEC OpenAPI spec. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for logging, ctx.state for storagesrc/mcp-server/*/definitions/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
FEC_API_KEYOpenFEC API key — optional (falls back to DEMO_KEY: 30 req/hr). Own key: 1000 req/hr. Free signup at api.data.gov.
MCP_LOG_LEVELdefault: infoSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
MCP_HTTP_HOSTdefault: 127.0.0.1The hostname for the HTTP server.
MCP_HTTP_PORTdefault: 3010The port to run the HTTP server on.
MCP_HTTP_ENDPOINT_PATHdefault: /mcpThe endpoint path for the MCP server.
MCP_AUTH_MODEdefault: noneAuthentication mode to use: 'none', 'jwt', or 'oauth'.