---
summary: "Independent-expenditure and legal-search filters now reach the API instead of being silently dropped (#16, #17); itemized Schedule B and E queries are cycle-scoped (#19, #22); outbound parameter names are checked against the OpenFEC spec"
breaking: true
security: false
---

# 0.5.0 — 2026-07-26

## Breaking

- **`openfec_search_legal` date inputs are now type-scoped.** `min_date` and `max_date` require `type` and a new `date_kind`. `/v1/legal/search/` has no generic date bound — each document type carries its own prefix and its own set of dates, so a single pair could not be mapped without picking one kind and dropping the rest. Valid combinations: `advisory_opinions` → `issue_date`, `request_date`, `document_date`; `murs` and `adrs` → `open_date`, `close_date`, `document_date`; `admin_fines` → `rtb_date`, `fd_date`; `statutes` are not date-filterable. An incomplete filter raises `date_filter_incomplete`, and a kind the type does not record raises `date_kind_not_valid_for_type` with the valid kinds attached. ([#16](https://github.com/cyanheads/openfec-mcp-server/issues/16))
- **`openfec_search_expenditures` renamed its scoping error reason** to `by_candidate_requires_scope`, since `by_candidate` now accepts a race scope as well as a `candidate_id`. ([#17](https://github.com/cyanheads/openfec-mcp-server/issues/17))

## Fixed

- **`openfec_search_expenditures` `by_candidate` filters reach the endpoint.** The branch was sending the itemized endpoint's names — `support_oppose_indicator`, `candidate_office` (`H`/`S`/`P`), `candidate_office_state`, `candidate_party` — to `/v1/schedules/schedule_e/by_candidate/`, which accepts `support_oppose`, `office` (`house`/`senate`/`president`), `state`, `district`, and has no party filter. OpenFEC answers 200 for an unrecognized parameter and ignores it, so every filtered call returned the unfiltered set: `candidate_id=P80001571&cycle=2024` gave 329 rows with or without `support_oppose: S`. It now returns 138. `candidate_party` in this mode raises `candidate_party_not_supported_by_candidate` instead of being dropped. ([#17](https://github.com/cyanheads/openfec-mcp-server/issues/17))
- **`openfec_search_legal` penalty bounds reach the endpoint.** `min_penalty_amount`/`max_penalty_amount` map to `case_min_penalty_amount`/`case_max_penalty_amount`; the former names were ignored upstream. With `type: murs`, a 1,000,000 minimum narrows 7,670 documents to 2. Penalty and date bounds also satisfy the `missing_filter` gate on their own, which previously rejected them before any request was made. ([#16](https://github.com/cyanheads/openfec-mcp-server/issues/16))
- **Itemized `openfec_search_disbursements` defaults `cycle` to the current cycle**, matching itemized `openfec_search_contributions`. A `committee_id`-only call scanned Schedule B across all history, which on an active committee timed out and was retried to the full retry budget before any error reached the caller. ([#19](https://github.com/cyanheads/openfec-mcp-server/issues/19))
- **Itemized `openfec_search_expenditures` defaults `cycle` to the current cycle.** A bare call previously issued an unbounded Schedule E query. ([#22](https://github.com/cyanheads/openfec-mcp-server/issues/22))
- **HTTP 504 carries a recovery hint.** `enrichStatusError` covered 400/403/404/422/429/500/502/503; a gateway timeout reached the caller as a bare `Status: 504`. It now names narrowing the query — supplying `cycle`, a committee or candidate ID, or a tighter date range — as the fix. ([#19](https://github.com/cyanheads/openfec-mcp-server/issues/19))

## Added

- **Outbound parameter-name assertions.** Every request funnels through one URL builder, which now checks parameter names against a per-endpoint allowlist copied from `docs/openapi-spec.json` and throws when one is not accepted, rather than letting OpenFEC answer 200 with the filter silently dropped. Covers `/schedules/schedule_b/`, `/schedules/schedule_e/`, `/schedules/schedule_e/by_candidate/`, and `/legal/search/`; endpoints without an entry are not checked. ([#16](https://github.com/cyanheads/openfec-mcp-server/issues/16), [#17](https://github.com/cyanheads/openfec-mcp-server/issues/17))
- **`candidate_office_district` input on `openfec_search_expenditures`**, sent as `candidate_office_district` in itemized mode and `district` in `by_candidate`. With it, `by_candidate` accepts a whole race as an alternative to a `candidate_id`, scoped to what the endpoint requires: `candidate_office=P` on its own, `S` with `candidate_office_state`, `H` with both the state and the district. The endpoint answers 422 for `house` or `senate` without a state and for `house` without a district, so those are required up front; the presidency is a national race whose rows carry no state, and supplying one matches nothing. ([#17](https://github.com/cyanheads/openfec-mcp-server/issues/17))
