Back to Directory/Developer Tools

io.github.cyanheads/openfec-mcp-server

Access FEC campaign finance data. Query data about candidates, money trails, and election filings.

Developer ToolsTypeScriptv0.9.0

@cyanheads/openfec-mcp-server

Access FEC campaign finance data through MCP. Query data about candidates, money trails, and election filings. STDIO & Streamable HTTP.

12 Tools • 5 Resources • 2 Prompts

npm Version Docker MCP SDK License TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework

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

ToolDescription
openfec_search_candidatesFind federal candidates by name, state, office, party, or cycle; fetch one by FEC ID with financial totals.
openfec_search_committeesFind political committees by name, type, candidate affiliation, or state; fetch one by FEC ID.
openfec_get_committee_totalsPre-aggregated committee financial totals — one committee's per-cycle summary, or a ranked search across committees of one entity type.
openfec_search_contributionsSearch itemized individual contributions (Schedule A) or aggregate breakdowns by size, state, employer, or occupation.
openfec_search_disbursementsSearch itemized committee spending (Schedule B) or aggregate breakdowns by purpose or recipient.
openfec_search_expendituresSearch independent expenditures (Schedule E) supporting or opposing federal candidates, itemized or aggregated by candidate.
openfec_search_coordinated_expendituresSearch coordinated party expenditures (Schedule F) made on behalf of a candidate.
openfec_search_filingsSearch FEC filings and reports by committee, candidate, form type, or date range.
openfec_lookup_electionsLook up federal election races and candidate financial summaries.
openfec_search_legalSearch FEC legal documents: advisory opinions, enforcement cases, administrative fines, and statutes.
openfec_get_legal_documentFetch one legal document in full, including the arrays search trims away.
openfec_lookup_calendarLook up FEC calendar events, filing deadlines, and election dates.

Resources

ResourceDescription
openfec://candidate/{candidate_id}Federal candidate profile with current financial totals and principal committees.
openfec://committee/{committee_id}Political committee profile with type, designation, and financial summary.
openfec://election/{cycle}/{office}Presidential election race with candidate financial totals.
openfec://election/{cycle}/{office}/{state}Senate or at-large House election race with candidate financial totals.
openfec://election/{cycle}/{office}/{state}/{district}House district election race with candidate financial totals.

Prompts

PromptDescription
openfec_money_trailFramework for tracing the flow of money around a candidate or race.
openfec_campaign_analysisStructured analysis of a candidate's financial position.

Capability reference

openfec_search_candidates tool

  • Full-text name search, or a direct lookup by FEC candidate ID (H/S/P + eight letters or digits) that returns full detail
  • Filters: state, district, office, party, cycle, election_year, incumbent_challenge, candidate_status, has_raised_funds
  • include_totals merges receipts/disbursements/cash-on-hand per cycle — defaults to true on an ID lookup, false on search; capped at 5 pages of 100 rows, with uncovered IDs listed in missing_totals for re-query
  • Pagination up to 100 results per page
  • Typed errors: candidate_not_found; inputs_not_applicable_to_id_lookup when search-only filters accompany a direct ID lookup

openfec_search_committees tool

  • Full-text name search, or a direct lookup by FEC committee ID (C + eight digits)
  • Filters: candidate_id, state, party, committee_type, designation, cycle, treasurer_name
  • Pagination up to 100 results per page
  • Typed errors: committee_not_found; inputs_not_applicable_to_id_lookup when search-only filters accompany a direct ID lookup

openfec_get_committee_totals tool

  • mode: "single" (default): one committee's totals, one row per two-year cycle filed. mode: "by_entity_type": ranks or screens every committee of one entity type (presidential, pac, party, pac-party, house-senate, ie-only)
  • by_entity_type-only filters: committee_state, committee_type, committee_designation, organization_type, and receipts/disbursements min/max bounds
  • Returns receipts, disbursements, cash on hand, debts, and the itemized/unitemized contribution split
  • Typed errors: committee_id_required_for_single_mode, entity_type_required_for_group_mode, inputs_not_applicable_to_mode, committee_totals_not_found

openfec_search_contributions tool

  • Modes: itemized (Schedule A records, requires committee_id, keyset cursor pagination), by_size/by_state (committee_id or candidate_id), by_employer/by_occupation (committee_id only)
  • Itemized filters: contributor name, employer, occupation, city, state, ZIP, date range, amount range, is_individual; defaults to the current cycle when omitted
  • Sort defaults to -contribution_receipt_date; a cursor is valid only for an otherwise-identical call
  • Typed errors: itemized_requires_committee_id, aggregate_requires_committee_id, itemized_only_filters_in_aggregate_mode, inputs_not_applicable_to_mode

openfec_search_disbursements tool

  • Modes: itemized (Schedule B records, keyset cursor pagination), by_purpose, by_recipient, by_recipient_id — committee_id required for every mode
  • Itemized filters: recipient name/state/city/committee ID, description, purpose category, date range, amount range; defaults to the current cycle when omitted
  • Sort defaults to -disbursement_date
  • Typed errors: itemized_only_filters_in_aggregate_mode; inputs_not_applicable_to_mode for an explicit page in itemized mode

openfec_search_expenditures tool

  • Modes: itemized (Schedule E, keyset cursor pagination, defaults to the current cycle and most_recent: true) and by_candidate (aggregated per targeted candidate — needs candidate_id or a full race scope: office alone for President, plus state for Senate, plus district for House)
  • Itemized filters: payee_name, candidate_party, is_notice (24/48-hour notices), date range, amount range, support_oppose (S/O)
  • Typed errors: by_candidate_requires_scope, itemized_only_filters_in_aggregate_mode, inputs_not_applicable_to_mode

openfec_search_coordinated_expenditures tool

  • Schedule F — party committee spending coordinated with a candidate's campaign, a separate legal category from independent expenditures and direct contributions
  • Filters: committee_id (spending party committee), candidate_id (benefiting candidate), cycle, payee_name, date range, amount range; unscoped queries span all years
  • Page-based pagination; the spending committee is hoisted out of rows when committee_id is supplied

openfec_search_filings tool

  • Form types: F3 (House/Senate quarterly), F3P (Presidential), F3X (PAC/party), F24 (24-hour IE notice), F1 (statement of organization), F2 (statement of candidacy), F5 (IE by persons)
  • Filters: committee_id, candidate_id, filer_name, report_type, report_year, cycle, is_amended, receipt date range
  • most_recent defaults to true, filtering out superseded amendments
  • Page-based pagination, up to 100 results per page

openfec_lookup_elections tool

  • mode: "search" (default): candidates in a race with financial totals. mode: "summary": aggregate race financial totals
  • Requires office and cycle; Senate/House also need state (House also needs district) unless a ZIP is given — ZIP resolves geography for search mode only
  • election_full defaults to true (expands to the full election period: 4yr president, 6yr senate, 2yr house); rejected on ZIP-scoped searches
  • Typed errors: cycle_must_be_even, missing_state_for_office, missing_district_for_house, summary_does_not_support_zip, inputs_not_applicable_to_mode

openfec_search_legal tool

  • Types: advisory_opinions, murs (enforcement cases), adrs, admin_fines, statutes; requires at least one scoping filter (query, type, ao_number, case_number, respondent, citation, penalty bound, or a date bound)
  • Date filters are type-scoped — pick a date_kind the type records (advisory opinions: issue/request/document date; murs/adrs: open/close/document date; admin_fines: rtb/fd date; statutes have none)
  • Every result is trimmed: highlights capped at 3, the documents array replaced by a count and category summary, commission_votes cut to a date and a 200-character action — retrieve the untrimmed record with openfec_get_legal_document
  • Offset-based pagination (from_hit/hits_returned), up to 200 results per page

openfec_get_legal_document tool

  • Fetches one legal document untouched — the full documents array and complete commission_votes that openfec_search_legal trims
  • doc_type is the plural of a search result's document_type (mur → murs); no is that result's no field
  • Typed error: legal_document_not_found

openfec_lookup_calendar tool

  • Modes: events (calendar_category_id, one of 18 category codes), filing_deadlines (report_type, report_year), election_dates (state, office, election_year)
  • min_date/max_date apply in every mode; other filters are mode-specific and rejected outside their mode
  • Typed error: inputs_not_applicable_to_mode

openfec://candidate/{candidate_id} resource

  • Candidate record merged with its current financial totals and principal campaign committees (designation P)
  • candidate_id comes from openfec_search_candidates
  • Typed error: candidate_not_found

openfec://committee/{committee_id} resource

  • Committee record merged with its financial totals; totals are simply omitted for a committee that files no Form 3/3X/3P
  • committee_id comes from openfec_search_committees
  • Typed error: committee_not_found

openfec://election/{cycle}/{office} resource

  • Presidential races only (office literal P); candidates returned with financial totals for the full election period
  • Truncates to the first page when a race has more candidates than one page holds — use openfec_lookup_elections mode search to page further

openfec://election/{cycle}/{office}/{state} resource

  • Senate races, or an at-large House race in a single-district state (office S or H)
  • Same first-page truncation as the presidential variant

openfec://election/{cycle}/{office}/{state}/{district} resource

  • House district races (office H)
  • Same first-page truncation as the presidential variant

openfec_money_trail prompt

  • Args: candidate_name or candidate_id (one required), optional cycle (defaults to the current cycle)
  • Seven-step framework: identify the candidate → map committees → direct fundraising → outside independent expenditures → coordinated party spending → disbursements → synthesis

openfec_campaign_analysis prompt

  • Args: candidate_name or candidate_id (one required), optional cycle (defaults to the current cycle)
  • Seven-step framework: candidate overview → principal committee and per-cycle totals trajectory → fundraising breakdown → burn rate and spending → competitive position → outside money context → 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 automatic retry and configurable timeout
  • Keyset cursor pagination for high-volume Schedule A/B/E queries; page-based pagination everywhere else
  • Multi-mode tools reject a filter that belongs to a different mode rather than silently dropping it
  • Error sanitization strips API keys from error messages; HTTP status errors are enriched with actionable recovery hints
  • Two guided prompts (openfec_money_trail, openfec_campaign_analysis) chain multiple tools into a financial investigation

Agent-friendly output:

  • Provenance on every response — a search_criteria echo of the effective (post-default) filters, so an implicit cycle default is never hidden
  • Empty results carry a notice enrichment suggesting how to broaden the query, instead of a bare empty array
  • Typed per-tool error contracts (reason plus actionable recovery text) instead of generic validation failures

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/mcp

Prerequisites

  • Bun v1.3.0 or higher (or Node ≥24)
  • (Optional) A free OpenFEC API key for higher rate limits (1,000 req/hr vs 30 req/hr with the default DEMO_KEY)

Installation

  1. Clone the repository:
git clone https://github.com/cyanheads/openfec-mcp-server.git
  1. Navigate into the directory:
cd openfec-mcp-server
  1. Install dependencies:
bun install
  1. Configure environment:
cp .env.example .env
# edit .env and set FEC_API_KEY (optional)

Configuration

VariableDescriptionDefault
FEC_API_KEYOpenFEC API key. Optional — defaults to DEMO_KEY (30 req/hr). Provide your own key (free at api.data.gov/signup) for 1,000 req/hr.DEMO_KEY
FEC_BASE_URLOpenFEC API base URL.https://api.open.fec.gov/v1
FEC_MAX_RETRIESMax retry attempts for failed API requests.3
FEC_REQUEST_TIMEOUTRequest timeout in milliseconds.30000
MCP_TRANSPORT_TYPETransport: stdio or http.stdio
MCP_HTTP_PORTPort for HTTP server.3010
MCP_HTTP_HOSTHostname for HTTP server.localhost
MCP_SESSION_MODEHTTP session mode: auto, stateful, or stateless. createApp() declares stateless in src/; set this only to override it.stateless
MCP_AUTH_MODEAuth mode: none, jwt, or oauth.none
MCP_LOG_LEVELLog level (RFC 5424).info
LOGS_DIRDirectory for log files (Node.js only).<project-root>/logs
STORAGE_PROVIDER_TYPEStorage backend.in-memory
OTEL_ENABLEDEnable OpenTelemetry instrumentation (spans, metrics, completion logs).false

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: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

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.

Project structure

DirectoryPurpose
src/index.tscreateApp() entry point — registers tools/resources/prompts and inits services.
src/config/Server-specific environment variable parsing and validation with Zod.
src/mcp-server/tools/definitions/Tool definitions (*.tool.ts).
src/mcp-server/resources/definitions/Resource definitions (*.resource.ts).
src/mcp-server/prompts/definitions/Prompt definitions (*.prompt.ts).
src/services/openfec/OpenFEC API client and domain types.
tests/Unit and integration tests.
scripts/Build, clean, devcheck, tree, and lint scripts.
docs/Design docs and OpenAPI spec.

Development guide

See CLAUDE.md for development guidelines and architectural rules. The short version:

  • Handlers throw, framework catches — no try/catch in tool logic
  • Use ctx.log for domain-specific logging, ctx.state for storage
  • Register new tools and resources in the index.ts barrel files
  • Wrap 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 before submitting:

bun run devcheck
bun run test

License

Apache-2.0 — see LICENSE for details.

Installation

Source-derived launch command. Check the maintainer’s required arguments and credentials before running:

bash
npx -y @cyanheads/openfec-mcp-server

Set up in your AI client

Merge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.

json
{
  "mcpServers": {
    "io-github-cyanheads-openfec-mcp-server": {
      "command": "npx",
      "args": [
        "-y",
        "@cyanheads/openfec-mcp-server"
      ]
    }
  }
}

Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.

Claude Desktop setup reference

Package

@cyanheads/openfec-mcp-servernpm

Compatible MCP Clients

io.github.cyanheads/openfec-mcp-server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.

  • Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.
  • Cursor~/.cursor/mcp.jsonRestart Cursor for changes to take effect.
  • VS Code.vscode/mcp.jsonReload VS Code window for changes to take effect.
  • Windsurf~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect.
  • Claude Code.mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.

Learn More