---
summary: "census_query_data/census_compare_geographies: fixed cross-geography filtering with a new geography_geoid field (#16), and a geography level missing a required parent now fails before the query with an actionable error instead of a raw upstream 400 (#19)"
breaking: false
security: false
---

# 0.1.13 — 2026-07-31

## Added

- **`geography_geoid`** output field on `census_query_data` and `census_compare_geographies` rows — the queried level concatenated with every parent column the Census API returned (e.g. `"53033"` for a county, `"53033000101"` for a tract), composed by `CensusApiService.parseResponse` and additive alongside the existing `geography_fips` (bare level code, still the value the `for=` clause takes).
- **`CensusApiService.checkGeography`** pre-validates a geography level and its supplied parents against the dataset's own `geography.json` before a data query runs, driven by each level's `requires` and `optionalWithWCFor` metadata rather than a hand-maintained hierarchy copy. Results are cached per dataset+year on the discovery TTL, shared with `census_list_geographies`.
- **`census_compare_geographies`**: the `geographies` filter now matches a row on its full GEOID (nationally unique, so a list may span states) as well as the bare level code. Entries that match no row, and bare codes that match more than one state, are named in the response notice; a list that matches nothing at all throws `no_data` with `unmatchedGeographies` instead of returning an empty ranking.

## Fixed

- **`census_query_data` / `census_compare_geographies`**: the `geographies` filter matched only the bare `geography_fips`, which is not unique across states — a list mixing counties from different states silently dropped or mismatched rows. Fixed by the new `geography_geoid` field above. ([#16](https://github.com/cyanheads/census-mcp-server/issues/16))
- **`CensusApiService.parseResponse`**: matched the queried level's header column with an exact-case comparison, so a mis-cased `geography_level` (e.g. `"County"`) produced a row with an empty `geography_fips`. The match is now case-insensitive, matching `checkGeography`'s own comparison.
- **`census_query_data` / `census_compare_geographies`**: querying a geography level without a parent it requires (a tract with no state, a block group with no county) surfaced a raw upstream `Status: 400` with no `data.reason`. Both tools now throw `parent_required` before the query, naming the missing parents and a recovery hint tailored to the tool's own inputs (`parent_fips`/`county_fips` or `within`/`within_county`), including a `*`-wildcard hint when relaxing the target would drop the missing parent. `geography_not_supported` is reachable for the first time and now carries the dataset's available levels. ([#19](https://github.com/cyanheads/census-mcp-server/issues/19))
- **`CensusApiService.queryData`**: a well-formed query matching nothing answers `204 No Content` with an empty body, which was fed to `JSON.parse` — the resulting throw surfaced as a retryable `upstream_error` after four attempts, so `no_data` was unreachable outside tests. A `204` or empty body is now read as zero rows.
- **`no_data` recovery**: no longer a static string suggesting `acs/acs5` regardless of dataset. The ACS1 65K-population-floor hint now only fires for `acs/acs1`; every other dataset gets FIPS/level confirmation guidance instead of being told to switch to the dataset it's already querying.

Tests: `tests/services/census-api/census-api-service.test.ts` covers `parseResponse` GEOID composition across county/tract/block-group/place/ZCTA levels, an MOE-variable case, and mis-cased level names, plus `checkGeography` against the real `geography.json` shape (including the wildcard-cutoff table) and the `queryData` `204` path. `census-query-data.tool.test.ts` and `census-compare-geographies.tool.test.ts` cover `parent_required` thrown before `queryData` is called, the county-parent variant, wildcard/out-of-reach hints, `geography_not_supported` with available levels, `no_data` recovery on `acs/acs5` and `acs/acs1`, cross-state GEOID matching, the multi-state-ambiguity notice, the partial-drop notice, and the total-drop `no_data`.
