---
summary: "Per-server dialect adapters route around upstream filter quirks. CassavaBase and SGN-family deployments now receive the singular filter forms they actually honor; `searchText` (the fictional non-spec query param earlier versions sent) is dropped."
breaking: false
---

# 0.4.0 — 2026-04-30

Live testing against `https://cassavabase.org/brapi/v2` revealed that every plural BrAPI v2.1 query-string filter the read tools sent (`commonCropNames`, `studyTypes`, `programDbIds`, `seasonDbIds`, etc.) was silently ignored — the upstream returned the unfiltered superset every time. CassavaBase and its SGN-family siblings (Sweetpotatobase, Yambase, Musabase, BananaBase) only honor the older singular forms (`commonCropName`, `studyType`, …). The fix is a new dialect-adapter service that translates the spec-shape filter map to whatever the upstream actually understands, with detection driven by `serverInfo.serverName` plus a `BRAPI_<ALIAS>_DIALECT` env override. The `searchText` parameter — invented by an earlier version of these tools and honored by no BrAPI v2 server — is dropped in the same pass.

## Added

- **`@/services/brapi-dialect/` service** — per-server adapter layer that translates outbound filter maps before serialization. The `BrapiDialect` interface starts with one capability (`adaptGetFilters(endpoint, filters) → { filters, warnings }`) and is extensible to non-filter quirks (search-body shape, output normalization) via optional methods. Two built-in dialects:
  - **`spec`** — passthrough; used for any server that implements BrAPI v2.1 query-string filters as written (BrAPI Community Test Server, modern PIPPA / GnpIS deployments, BMS, GERMINATE).
  - **`cassavabase`** — translates plurals to singulars per endpoint, downcasts multi-value arrays to the first element with a loud warning, drops the non-spec `searchText` filter. Selected automatically when `serverInfo.serverName` matches CassavaBase, Sweetpotatobase, Yambase, Musabase, or BananaBase (substring match, case-insensitive). The `organizationName` field also matches "Boyce Thompson" so generic-named deployments behind BTI hosting still resolve.
- **`BRAPI_<ALIAS>_DIALECT` env override** — pin the dialect for an alias when detection guesses wrong. Accepts any registered dialect id (`spec`, `cassavabase`); `auto` (or unset) defers to detection.
- **`resolveDialect(connection, ctx)`** — async helper that consults the env override first, then the cached `CapabilityProfile` (already fetched by `capabilities.ensure()`, so zero extra HTTP). Returns the resolved `BrapiDialect`.
- **Tests** — 42 new cases across `tests/services/brapi-dialect/` (spec passthrough, full CassavaBase translation table per endpoint, multi-value downcast warning, env-override precedence, registry fallback to spec on unknown ids, idempotent init). New cases in `brapi-find-studies.tool.test.ts` exercise the end-to-end CassavaBase wire path: `commonCropNames=Cassava` arriving as `commonCropName=Cassava` on the URL, multi-value array warnings.

## Changed

- **All seven `find_*` tools route through the dialect** — `brapi_find_studies`, `brapi_find_germplasm`, `brapi_find_observations`, `brapi_find_locations`, `brapi_find_variables`, `brapi_find_images`, `brapi_find_variants` resolve the dialect after `capabilities.ensure()` and call `dialect.adaptGetFilters(endpoint, filters)` before `loadInitialPage`. Helpers stay pure; the adapter concern is contained at the tool layer where the warning surface lives.
- **`brapi_find_germplasm` `text` parameter is now a client-side substring post-filter** rather than a server-side `searchText` query (which CassavaBase, the BrAPI Test Server, and every other v2 implementation silently ignore). Matches across `germplasmName`, `accessionNumber`, `defaultDisplayName`, and registered synonyms on the rows already returned by upstream filters. Surfaces a warning when the match drops rows from the in-context view (the dataset, when present, retains the full pre-filter set so a follow-up `brapi_manage_dataset` call can re-query). Description updated to make the client-side behavior explicit so callers combine `text` with `crops` / `genus` / `accessionNumbers` to narrow upstream first.
- **`SERVER_TO_USER` rendering maps in every find tool extended with singular forms** so `appliedFilters` rendering still maps wire keys back to the user-facing parameter after dialect translation. Both plural and singular keys point at the same user param.

## Fixed

- **CassavaBase compatibility** — `brapi_find_studies`, `brapi_find_germplasm`, `brapi_find_observations`, `brapi_find_locations`, `brapi_find_variables`, `brapi_find_images`, `brapi_find_variants` now actually filter when connected to a CassavaBase / SGN-family deployment. Empirically verified on the live CassavaBase server: `commonCropName`, `programDbId`, `trialDbId`, `seasonDbId` all narrow as expected; the unfiltered totalCount=8340 trap is gone.
- **`brapi_find_studies` removed the non-functional `text` input** — earlier versions sent `searchText` as a query parameter, which no BrAPI v2 server honors. The field appeared to work in `appliedFilters` output while the upstream returned the unfiltered superset. Removed entirely; combine `studyNames`, `crop`, and `programs` to narrow.
- **`brapi_find_germplasm` `searchText` query parameter dropped from the wire** — replaced by the client-side post-filter described above. Old behavior was misleading: `text` showed in `appliedFilters` while the upstream silently ignored it.
