@cyanheads/openfec-mcp-server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@cyanheads/openfec-mcp-serverfind candidates for president in 2024"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Public Hosted Server: https://openfec.caseyjhand.com/mcp
Overview
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.
Tools
Tool | Description |
| Find federal candidates by name, state, office, party, or cycle; fetch one by FEC ID with financial totals |
| Find political committees by name, type, candidate affiliation, or state; fetch one by FEC ID |
| Pre-aggregated financial totals for one committee by cycle, or ranked across committees of one entity type |
| Itemized contributions (Schedule A), or aggregates by size, state, employer, or occupation |
| Itemized committee spending (Schedule B), or aggregates by purpose or recipient |
| Independent expenditures (Schedule E) supporting or opposing candidates, itemized or totaled per candidate |
| Coordinated party expenditures (Schedule F) made on behalf of a candidate |
| FEC filings and reports by committee, candidate, form type, or date range |
| Candidates in a federal race with their fundraising totals, or an aggregate race summary |
| Advisory opinions, enforcement cases, alternative dispute resolutions, administrative fines, and statutes |
| One legal document in full, including the arrays search trims away, paged by entry when too large for one response |
| FEC calendar events, filing deadlines, and election dates |
Resources
Resource | Description |
| Candidate profile with financial totals and principal committees |
| Committee profile with type, designation, and financial summary |
| Presidential race with candidate financial totals |
| Senate or at-large House race with candidate financial totals |
| 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.
Prompts
Prompt | Description |
| Trace the money around a candidate: direct fundraising, PAC support, independent expenditures, and party spending |
| Structured analysis of a candidate's financial position |
Related MCP server: fec-mcp
Capability reference
openfec_search_candidates tool
queryname search filtered by state, district, office, party, cycle, election_year, incumbent_challenge, candidate_status, and has_raised_funds, up to 100 per page; or acandidate_idlookup (H/S/P + eight letters or digits) returning one full recordinclude_totals(default on for an ID lookup) adds per-cycletotalsrows 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 inmissing_totalsTyped errors:
candidate_not_found,inputs_not_applicable_to_id_lookup
openfec_search_committees tool
queryname search filtered by candidate_id, state, party, committee_type, designation, cycle, and treasurer_name, up to 100 per page; or acommittee_idlookup (C+ eight digits)Typed errors:
committee_not_found,inputs_not_applicable_to_id_lookup
openfec_get_committee_totals tool
mode: "single"(default) takes acommittee_idand returns one row per two-year cycle;mode: "by_entity_type"ranks every committee of oneentity_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 pageRows carry receipts, disbursements, cash on hand, debts, and the itemized/unitemized split; the output
modesays whether rows are cycles or committeesTyped errors:
committee_id_required_for_single_mode,entity_type_required_for_group_mode,committee_totals_not_found
openfec_search_contributions tool
itemized(default) needs acommittee_idand filters by contributor name, employer, occupation, city, state, ZIP, date and amount ranges, andis_individual, 30 rows per page;by_size/by_statetake acommittee_idorcandidate_id,by_employer/by_occupationacommittee_idThe output
modebecomesby_size_candidateorby_state_candidatewhen scoped by candidate, a different row shape; itemized responses carrynext_cursor,count, andcount_is_approximateTyped errors:
itemized_requires_committee_id,aggregate_requires_committee_id
openfec_search_disbursements tool
committee_idrequired 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_idItemized responses carry
next_cursor,count, andcount_is_approximate; aggregate modes page by number
openfec_search_expenditures tool
itemized(default) filters by committee, candidate,support_oppose, payee, targeted office/state/district, party,is_notice(24/48-hour notices), dates, and amounts;most_recentdefaults to true; 60 rows per page when scoped bycommittee_id, 30 otherwiseby_candidatetotals need acandidate_idor a full race scope (candidate_office=Palone,Spluscandidate_office_state,Hplus state andcandidate_office_district), or fail asby_candidate_requires_scope
openfec_search_coordinated_expenditures tool
Scope by spending party
committee_id, benefitingcandidate_id,cycle, payee, dates, and amounts; unscoped queries span all yearsPage-based, 80 rows per page when scoped by
committee_idand 25 otherwise; rows carrysubordinate_committee_id, the committee the spending is attributed to
openfec_search_filings tool
Filters: committee_id, candidate_id, filer_name, form_type (F3, F3P, F3X, F24, F1, F2, F5), report_type, report_year, cycle, is_amended, and receipt date range; 65 rows per page
most_recentdefaults to true, hiding superseded amendments; rows carry form and report type, financial totals, andpdf_url
openfec_lookup_elections tool
officeand an evencycleare required; Senate also needsstateand Housestateplusdistrict, unless azipscopes a searchmode: "search"(default) returns the race's candidates with totals andcandidate_pcc_id/candidate_pcc_name, that cycle's principal campaign committee;mode: "summary"returns one aggregate race rowTyped errors:
cycle_must_be_even,missing_state_for_office,missing_district_for_house,summary_does_not_support_zip
openfec_search_legal tool
type(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 needstypeplus adate_kindthat type recordsCitations, penalty bounds, respondent, ao_number, and case_number each filter only some types; one sent with a
typeit doesn't filter fails asfilter_not_valid_for_type, and withtypeomitted 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 oneCitations take the form upstream parses —
52 U.S.C. 30104or11 CFR 110.1(USC,C.F.R.,§, and suffixes like30104(g)are fine); a bare30106or110.1fails asinvalid_citationinstead of silently returning unfiltered results. One citation per field: upstream applies only the first citation in a value, so52 U.S.C. 30104, 52 U.S.C. 30118fails the same wayOffset paging via
from_hit/hits_returned, up to 200 per type within a 10,000-result windowResults are trimmed:
documentsbecomesdocument_countanddocument_categories,dispositionsbecomesdisposition_countanddisposition_categories, highlights stop at 3 with their<em>match markup removed and render one per line, andcommission_votesshrink to a date and a 200-character action;openfec_get_legal_documentreturns the full recordEach response stays under 100,000 bytes: whole results are admitted one type at a time until the next would not fit, and a page that held some back reports
truncated,shown, andnextFromHit— thefrom_hitthat continues each type, passed back with thattype
openfec_get_legal_document tool
doc_typeis the plural of a search result'sdocument_type(mur→murs);nois that result'snofieldReturns the untrimmed record, including
documents,commission_votes, anddispositions; fails aslegal_document_not_foundA record over 100,000 bytes returns its scalar fields, the arrays that fit (smallest first), and
withheld— each held-back array with its entry count and size; re-call witharrayandoffsetfor that array's entries while they fit, plusnext_offset. An array the record lacks fails asarray_not_in_record
openfec_lookup_calendar tool
events(default;category, one of 18 codes, anddescription),filing_deadlines(report_type,report_year), orelection_dates(state,office,district,election_year);min_date/max_dateapply in every mode, up to 100 per pageA
districtfilter never matches an at-large race; pair it withstate
openfec://candidate/{candidate_id} resource
Candidate record merged with its financial totals and
principal_committees(current designationP, not scoped to a cycle)candidate_idcomes fromopenfec_search_candidates; fails ascandidate_not_found
openfec://committee/{committee_id} resource
Committee record merged with its financial totals, which are absent for a committee that files no Form 3/3X/3P
committee_idcomes fromopenfec_search_committees; fails ascommittee_not_found
openfec://election/{cycle}/{office} resource
Presidential races (
officeP), with totals for the full election periodFirst page only: a longer race carries
truncation_noticepointing toopenfec_lookup_elections, and an empty oneempty_result_notice
openfec://election/{cycle}/{office}/{state} resource
Senate races, or an at-large House race in a single-district state (
officeSorH)Same first-page limit and notices as the presidential template
openfec://election/{cycle}/{office}/{state}/{district} resource
House district races (
officeH)Same first-page limit and notices as the presidential template
openfec_money_trail prompt
Arguments:
candidate_nameorcandidate_id(one required), optionalcycleSeven steps: identify the candidate, resolve the cycle's principal committee via
candidate_pcc_id, then direct fundraising, independent expenditures, coordinated party spending, disbursements, and a synthesis
openfec_campaign_analysis prompt
Arguments:
candidate_nameorcandidate_id(one required), optionalcycleSeven steps: candidate overview, principal committee and per-cycle trajectory, fundraising mix, burn rate, competitive position, outside money, and an assessment
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.
OpenFEC-specific:
Type-safe client for the OpenFEC REST API with retry and a configurable timeout; error messages have the API key stripped and HTTP failures carry recovery hints
Cycles are even years covering two calendar years (2024 = Jan 2023 – Dec 2024); itemized Schedule A/B/E default to the current cycle when
cycleis omitted, andelection_full(default true) widens a race total to the full election period (4yr president, 6yr senate, 2yr house)Itemized contributions and disbursements scope to a
committee_id, not acandidate_id. Committeedesignationandprincipal_committeesreflect current designation only, so for a cycle's principal committee readcandidate_pcc_idfromopenfec_lookup_electionsItemized Schedule A/B/E page with an opaque
next_cursor, valid only for an otherwise-identical call; other tools page by number, and legal search by offset
Agent-friendly output:
Effective-query echo: every tool response carries
search_criteriawith the post-default filters, and an empty result carries anoticeon how to broaden itResponse budget of 100,000 bytes per surface: high-volume tools cap rows per page and report
truncated,shown, andcapwhen a page falls below the requestedper_page(page-based tools also echo the applied size inpagination.per_page); legal search holds back whole results and reportsnextFromHit, and a large legal document pages its arrays byoffset; a single-committee query lifts that committee into one top-levelcommitteefield instead of repeating it per rowTyped contracts: multi-mode tools echo the resolved
modeand 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 typedreasonandrecoverytext, and estimated totals are flaggedcount_is_approximate
Getting started
Public Hosted Instance
A 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"
}
}
}Self-Hosted / Local
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/mcpPrerequisites
Bun v1.4.0 or higher (or Node.js v24+).
Optional: a free OpenFEC API key raises the rate limit from 30 to 1,000 requests per hour over the default
DEMO_KEY.
Installation
Clone the repository:
git clone https://github.com/cyanheads/openfec-mcp-server.gitNavigate into the directory:
cd openfec-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# edit .env and set FEC_API_KEY (optional)Configuration
Variable | Description | Default |
| OpenFEC API key from api.data.gov. |
|
| OpenFEC API base URL. |
|
| Retry attempts for failed API requests. |
|
| Per-request timeout, in ms. |
|
| Transport: |
|
| HTTP server port. |
|
| HTTP session mode: |
|
| Authentication: |
|
| Log level ( |
|
| Directory for log files (Node.js only). |
|
| Storage backend: |
|
| Enable OpenTelemetry. |
|
See .env.example for the full list of optional overrides.
Running the server
Local development
Build and run:
bun run rebuild bun run start:stdio # or start:httpRun 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
docker build -t openfec-mcp-server .
docker run --rm -e FEC_API_KEY=your-key -p 3010:3010 openfec-mcp-serverThe 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.
Project structure
Directory | Purpose |
|
|
| Server-specific environment variable parsing and validation with Zod. |
| Tool definitions ( |
| Resource definitions ( |
| Prompt definitions ( |
| OpenFEC API client and domain types. |
| Unit and integration tests, mirroring the |
| Build, clean, devcheck, tree, changelog, and lint scripts. |
| Design notes and the OpenFEC OpenAPI spec. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor logging,ctx.statefor storageRegister new tools and resources in the barrels at
src/mcp-server/*/definitions/index.tsWrap the OpenFEC API: validate raw → normalize to domain type → return the output schema; never fabricate fields the upstream response omitted
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
Apache-2.0 — see LICENSE for details.
This server cannot be deployed
Maintenance
Related MCP Connectors
Query SEC EDGAR filings, XBRL financials, and company data through MCP. STDIO & Streamable HTTP.
OpenFEC MCP — Federal Election Commission campaign finance data
Access U.S. congressional data (bills, votes, members, committees) via MCP.
FEC campaign finance data: candidate fundraising, donors, and spending
Related MCP Servers
- AlicenseAqualityBmaintenanceQuery FEC campaign finance data — search candidates, track donations, analyze spending, and monitor Super PAC activity via the OpenFEC API.89 npm4MIT
- FlicenseNot gradedqualityDmaintenanceAn MCP server that searches official FEC PDF rulebooks for compliance and contribution limits, and provides real-time lookups against the OpenFEC API for candidates, committees, filings, and financial data.-
- AlicenseNot gradedqualityAmaintenanceQuery US Treasury national debt, interest rates, exchange rates, and fiscal datasets via MCP with STDIO or Streamable HTTP.324 npm2Apache 2.0
- FlicenseNot gradedqualityAmaintenanceQuery normalized U.S. House and Senate STOCK Act disclosures, member trading histories, and aggregate trading statistics through MCP.-