---
summary: "Row schemas accept null fields from sparse upstreams. FK match-rate checks across find_studies / find_observations / find_images surface silently-ignored filters as warnings. CassavaBase locationDbIds dropped on /studies."
breaking: false
---

# 0.4.2 — 2026-04-30

Field testing against `https://cassavabase.org/brapi/v2` after 0.4.1 surfaced two classes of correctness gaps: (1) several upstream record types return `null` for missing optional fields where row schemas required `string | undefined` — Zod parse failed before the agent ever saw the data; (2) the post-fetch `checkFilterMatchRates` heuristic was wired up unevenly across find tools, so when a CassavaBase-side filter was silently ignored (e.g. `locationDbId` on `/studies`), the result envelope arrived clean with no signal that the filter had been dropped. This release closes both gaps and removes the one mapping that field testing proved doesn't filter even after dialect translation.

## Added

- **`fkMatchCheck(paramName, requestedValues, rows, fieldName)` helper in `find-helpers`** — builds a `FilterMatchCheck` for an FK identifier filter inline. Replaces the 4-line `computeDistribution(...)` + `{ paramName, requestedValues, distribution }` construction repeated nine times across the three find tools that gained match-rate coverage. The DbId distribution is computed only for the check; not exposed in `result.distributions` (raw FK frequencies don't carry semantic meaning to agents).
- **FK filter match-rate checks** — `brapi_find_studies` now checks `locations` / `programs` / `trials`; `brapi_find_observations` adds `studies` / `germplasm` / `variables` / `observationUnits`; `brapi_find_images` adds `studies` / `observationUnits`. When the upstream silently ignores a filter and returns an unrelated row set, the user receives a `Filter 'X' requested […] but no returned row matches — the server may not honor this filter` warning instead of a clean envelope they'll trust.
- **Tests** — 9 new cases. `tests/tools/brapi-find-{observations,images}.tool.test.ts` cover explicit-null upstream rows (the CassavaBase shape that broke 0.4.1 in the wild). `tests/tools/brapi-find-studies.tool.test.ts` covers the new locations/programs/trials warnings. `tests/services/brapi-dialect/cassavabase-dialect.test.ts` covers the `locationDbIds` drop on `/studies` for both plural and singular forms.

## Changed

- **`ObservationRowSchema` (14 fields), `ImageRowSchema` (12 fields), `VariantRowSchema` (3 top-level identifier fields), `CallRowSchema` (5 fields + `genotype` block), and `callFormatting` echo block converted from `.optional()` to `.nullish()`** — accepts the explicit `null` values CassavaBase and other sparse-upstream servers return for missing optional fields. Pre-0.4.2, a row like `{ observationDbId: 'obs-1', observationTimeStamp: null, germplasmName: null, ... }` failed Zod parse on the entire envelope. Post-0.4.2, the agent receives the row with the nulls preserved on output. `StudyRowSchema`, `GermplasmRowSchema`, `VariableRowSchema`, and `LocationRowSchema` already used `.nullish()` from prior live-testing rounds — this release brings the remaining row schemas to parity.
- **`brapi_find_variables.text` description rewritten** to make the rank-not-subset behavior explicit and call out the divergence from `brapi_find_germplasm.text` (which subsets). Same parameter name, different semantics — the documentation now says so loudly. Use exact filters (`variables`, `variableNames`, `variablePUIs`, `traitClasses`, `ontologies`) to actually narrow the result set.

## Fixed

- **CassavaBase `locationDbIds` on `/studies` moved to `DROPPED_FILTERS`** — empirically verified that the singular `locationDbId` form is silently ignored too: requesting `locationDbId=3` against the live `/studies` endpoint returned studies for unrelated locations (Mokwa, Zaria). 0.4.0's plural→singular translation produced a wire shape that looked correct but didn't filter. The dialect now drops the filter from the wire entirely and surfaces a `dropped filter 'locationDbIds'` warning so the agent knows the request can't be served as-is. Until we have a working alternative, narrow location-wise post-fetch via `locationName` distribution or a follow-up `brapi_get_study` call. See [#5](https://github.com/cyanheads/brapi-mcp-server/issues/5) for the broader verified-flag-per-mapping work this is one step toward.
