Skip to main content
Glama
statefinance-mcp

statefinance-mcp

by cstillick

statefinance-mcp

State-level campaign finance, as an MCP server. Donors, expenditures, committees, and candidates from state disclosure systems — the data the FEC doesn't cover and that no MCP server currently exposes — through one common schema. Oklahoma first; more states via adapters.

Why this exists

Federal campaign finance (FEC) is wrapped to death. State campaign finance — where most money-in-politics questions actually live — is fragmented across ~46 portals with no unified API, and the one historical normalizer (FollowTheMoney) is winding down. The hard, valuable part isn't the MCP plumbing; it's the normalization layer that maps a messy state portal into a clean, sourced schema. That's the product.

Related MCP server: @cyanheads/openfec-mcp-server

Quick start (offline, no keys)

The v1 demo runs on a committed synthetic Oklahoma extract (see the data note below), so a fresh checkout works with no network and no keys.

uv sync

uv run statefinance ingest ok            # load the OK 2024 sample into the store
uv run statefinance top-donors "Tallchief for Oklahoma"
#   Redbud Ranch LLC: $10,000
#   Acme Energy, LLC: $5,000   (spelling variants combined)
#   Patterson, John Q.: $1,500
uv run statefinance summary cycle 2024
#   raised $30,650, spent $76,200, top recipient: Tallchief for Oklahoma

uv run statefinance donor-history "Patterson, John Q."   # all of a donor's giving
uv run statefinance serve                                # run the MCP server (stdio)

Use it as an MCP server

{
  "mcpServers": {
    "statefinance": {
      "command": "uv",
      "args": ["run", "statefinance", "serve"],
      "cwd": "/path/to/statefinance-mcp"
    }
  }
}

Tools

Tool

What it does

search_contributions

Filter contributions by donor, recipient, candidate, amount/date range, state, cycle. Returns matches + full-match count and total.

search_expenditures

Filter expenditures by committee, payee, purpose, date, state, cycle.

get_committee

A committee plus its contribution and expenditure totals.

get_candidate

A candidate (by id or name), their committees, and money raised/spent.

top_donors

Top donors to a committee, grouped by a light-normalized donor key.

donor_history

Every contribution by a donor, with a per-recipient breakdown.

summary

Aggregate totals for a committee, candidate, or cycle.

Every result carries each record's source_url + as_of — this is accountability data, so a wrong number is worse than no number.

Design: one schema, many adapters

state portal ─▶ StateAdapter.fetch()  ─▶ raw snapshot (data/raw/<state>/)
                StateAdapter.normalize() ─▶ common schema ─▶ DuckDB store
                                                                  │
                                            MCP server (reads the store only)
  • Ingestion and serving are decoupled. Ingest populates a local normalized store; the MCP server only reads it, so tool calls are fast and portals stay un-hammered.

  • Adding a state is implement fetch + normalize, ship a fixture, register — core, store, and tools untouched. See docs/adding-a-state.md (enforced by a test that adds a second state through the adapter contract alone).

  • Donor normalization is light and honest (trim/case/whitespace, org suffixes). Fuzzy entity resolution is explicitly deferred — see docs/donor-normalization.md.

⚠️ Data note

The committed Oklahoma sample is synthetic (fictional committees, candidates, donors — clearly labeled), so the offline demo never asserts fabricated facts about real people. Acquisition of real Oklahoma data and the ToS posture are documented in docs/sources/oklahoma.md; live ingestion is intentionally gated — enable ingest ok --live only after confirming the portal's current export method and terms of service.

Development

uv run pytest        # offline suite (committed synthetic extract)
uv run ruff check .  # lint

If uv run statefinance ever reports No module named 'statefinance' (a known editable-install quirk on some setups, e.g. paths with spaces), run with PYTHONPATH=src uv run statefinance ....

See SPEC.md and BUILD_PLAN.md for the design.

License

MIT.

Available Tools

9 tools
donor_historyC

Every contribution by a donor, with totals and a per-recipient breakdown.

Inputs: name (required), cycle, state.

get_candidateC

A candidate (by id or name), their committees, and money raised/spent.

Inputs: candidate_id (required).

get_committeeC

A committee plus its contribution and expenditure totals.

Inputs: committee_id (required).

pingA

Health check. Returns 'pong' if the server is alive.

Inputs: no named parameters. Returns: result

search_contributionsA

Search campaign contributions by donor, recipient committee, candidate, amount range, date range, state, or cycle. Returns matching contributions (each with source_url + as_of) plus the full-match count and total.

Inputs: donor, recipient, candidate, min_amount, +6 more.

search_expendituresA

Search expenditures by committee, payee, purpose, date range, state, or cycle. Returns matching expenditures (with provenance) plus count and total.

Inputs: committee, payee, purpose, date_from, +4 more.

store_statsB

Row counts for the normalized store (committees, candidates, ...).

Inputs: no named parameters.

summaryC

Aggregate totals for a committee, candidate, or cycle.

Inputs: scope_type (required), scope_id (required).

top_donorsB

The top donors to a recipient committee, ranked by total given (spelling variants combined via light normalization).

Inputs: recipient (required), cycle, n.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.2/5.0

    Scored across 9 tools

    Disambiguation5/5

    Each tool serves a distinct purpose: donor history, candidate info, committee info, health check, contribution search, expenditure search, store stats, summary aggregates, and top donors. There is no ambiguity between them.

    Naming Consistency2/5

    Tool names mix verb-noun patterns (get_candidate, search_contributions) with noun phrases (donor_history, summary, ping) and inconsistent styles. This lack of a consistent pattern reduces predictability.

    Tool Count5/5

    With 9 tools, the set is well-scoped for a state finance data server. It covers essential queries without being overwhelming or too sparse.

    Completeness4/5

    The tools cover core workflows: lookup candidates, committees, donors, contributions, expenditures, and summaries. Minor gaps exist (e.g., no listing of all candidates or committees), but the surface is largely complete for a read-oriented server.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      C
      maintenance
      Query FEC campaign finance data — search candidates, track donations, analyze spending, and monitor Super PAC activity via the OpenFEC API.
      8
      5 npm
      4
      MIT
    • A
      license
      B
      quality
      F
      maintenance
      MCP server for Oklahoma campaign finance data, enabling queries on combined pre-primary and continuing contributions, candidate flags, and more via natural language.
      13
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Access Federal Election Commission campaign finance data through MCP tools. Enables querying OpenFEC data using natural language via ask_pipeworx or direct tool calls.
      295 npm
      MIT