---
summary: "BrAPI Test Server v2.0 dialect, shared singularizing engine, and typed all_filters_dropped guard across find_* tools."
breaking: false
---

# 0.4.6 — 2026-05-01

Real-server dialect work. The BrAPI Community Test Server advertises v2.1 but its GET list endpoints honor only the older v2.0 singular filter names — `studyDbIds` is silently ignored, `studyDbId` filters correctly. Two Issues surfaced from live probing: `find_observations` returned the unfiltered set when scoped by study, and `get_germplasm`'s studyCount probe returned the global studies total instead of the per-germplasm count. Both root in the same v2.0/v2.1 mismatch. This release adds a real `brapi-test` dialect, factors the SGN/Breedbase translation engine into a shared module, and wires a typed `all_filters_dropped` failure across the `find_*` surface so an upstream that drops every supplied scope filter no longer silently widens to the unfiltered baseline.

## Added

- **`brapi-test` dialect** — was a stub that passed filters through verbatim; now translates v2.1 plurals to v2.0 singulars on `/studies`, `/germplasm`, `/observations`, `/locations`, `/variables`, `/images`, `/variants`, `/trials`, `/programs`. Drops `searchText` on `/germplasm` and `observationLevel(s)` + `studyDbId(s)` on `/observations` (broken upstream in both forms — verified empirically). POST `/search/{noun}` routes are NOT marked disabled — the test server serves them correctly with v2.1 plurals, so multi-value queries can still escalate to `/search`.
- **`createSingularizingDialect` factory** — `src/services/brapi-dialect/singularizing-dialect.ts`. Generic engine for dialects that translate plural BrAPI v2.1 filter names to v2.0 singulars, drop blacklisted keys, and downcast multi-value arrays to the first element with a loud warning. Used by both the SGN/Breedbase family and the BrAPI Community Test Server — the engine is shared, only the per-server data (mapping, drop list, disabled-search list, notes) lives in each dialect module.
- **Typed `all_filters_dropped` error contract** — declared on every `find_*` tool (`brapi_find_germplasm`, `brapi_find_images`, `brapi_find_locations`, `brapi_find_observations`, `brapi_find_studies`, `brapi_find_variables`, `brapi_find_variants`). When the active dialect drops every agent-supplied scope filter, the tool now throws a `ValidationError` with `data.reason = 'all_filters_dropped'`, the dropped filter list, and a per-tool recovery hint listing the supported filter paths — instead of silently widening the query to the unfiltered baseline. Bare-baseline calls (no filters supplied) are exempt.

## Changed

- **CassavaBase dialect refactor** — the per-endpoint translation engine moved out of `cassavabase-dialect.ts` into the shared `singularizing-dialect.ts` factory. CassavaBase now supplies only its SGN-specific data (mapping, drop list, disabled-search list, notes) and delegates the engine. No behavior change for SGN-family servers; the same plural→singular translation, search-route gating, and warning text apply.
- **`DialectAdaptation`** now carries a structured `dropped: readonly string[]` alongside `filters` and `warnings`. Lets callers detect "agent intended to scope, dialect couldn't honor any of it" without parsing warning strings.
- **`get_germplasm` studyCount probe** routes through the dialect adapter so per-server key translation takes effect. Previously sent `germplasmDbIds` (plural) verbatim, which the BrAPI Test Server silently ignored — the probe returned the global studies total instead of the per-germplasm count. Now sends the dialect-translated key (`germplasmDbId` on the test server, the relevant v2.0 form on SGN-family servers) and skips the probe entirely with a warning when the dialect drops the filter.
- **`applyDialectFiltersOrFail` helper** in `src/mcp-server/tools/shared/find-helpers.ts` consolidates the dialect-apply + all-dropped-throw block that was previously duplicated 13 lines per tool across all seven `find_*` tools. ~85 lines net removed.

## Fixed

- **`find_observations` on the BrAPI Test Server** — `studies: ['studyN']` no longer returns the unfiltered observation set. The dialect now drops `studyDbId(s)` entirely (broken in both GET singular and POST `/search` plural forms upstream — neither shape narrows the result set), surfaces a warning, and the typed `all_filters_dropped` guard prevents the query from running unfiltered when `studies` was the only filter supplied. Agents should scope by `germplasm`, `observationUnits`, or `variables` instead — all of which DO filter correctly on the test server.
- **`get_germplasm` studyCount on the BrAPI Test Server** — was returning the global studies total because `/studies?germplasmDbIds=X` is silently ignored upstream; now sends `germplasmDbId=X` (singular) via the dialect adapter and the cross-check confirms the filtered count differs from the unfiltered baseline.
