---
summary: "Fixed census_list_datasets advertising pep/charv vintages the Census API does not publish, and a dataset+year miss reaching the caller as raw upstream HTML (#28); fixed string-valued variables returning estimate: null indistinguishable from genuinely missing data (#29)"
breaking: false
security: false
---

# 0.3.2 — 2026-07-31

## Fixed

- **`census_list_datasets` / all query tools** — vintages now come from one table, `DATASET_AVAILABLE_YEARS` in `variable-cache-service.ts`, that `KNOWN_DATASETS`, `DATASET_LATEST_YEARS`, and the `available_years` output all derive from, so the catalog can no longer advertise a year the query path refuses. `pep/charv` now lists `[2023]` only — its 2020-2022 estimates live inside that vintage behind the `YEAR` predicate (`predicates: {"YEAR": "2022"}`) rather than as separate vintages. `acs/acs5` and `acs/acs5/profile` now extend back to 2009 and `acs/acs1`/`acs/acs1/profile` to 2005, both previously under-advertised. `VariableCacheService.validateYear` rejects an unlisted year with a new `year_not_available` error before any request goes out, named on `census_query_data`, `census_compare_geographies`, `census_search_variables`, `census_get_variable`, and `census_list_predicate_values`. A new `censusHttpError` translator wraps every Census API fetch call: a rejected query's one-line plain-text rejection (e.g. `error: unknown variable 'NAME'`) surfaces as `data.upstreamMessage`, while a servlet container's HTML error page is discarded — `data.body`/`data.responseBody` never reach the caller either way. ([#28](https://github.com/cyanheads/census-mcp-server/issues/28))
- **`census_query_data` / `census_compare_geographies`** — a cell holding text rather than a number (`GEO_ID`, `pep/charv` `UNIVERSE`, ACS median-year-built columns like `B25035_001E`, the `"(X)"` not-applicable marker in older ACS profile vintages) now carries that text in a new optional `value` field instead of coercing through `Number()` to `NaN` → `null`, indistinguishable from a genuinely empty cell. A null `estimate` now resolves three ways: `suppressed: true` is a value the Census withheld, a `value` alongside a null `estimate` is text, and neither is an empty cell. `format()` renders the text where it used to print `N/A`. Also fixes an empty cell reading as a zero estimate via `Number("")`, and an unguarded suppression-code lookup that could resolve a cell reading `"constructor"` to a suppression reason. ([#29](https://github.com/cyanheads/census-mcp-server/issues/29))

377 tests pass across 11 files; `bun run devcheck` clean.
