---
summary: "Field-test fixes from real CassavaBase data — GeoJSON coordinates, `pageSize=0`→`pageSize=1` for v2.1-strict servers, variable text-match by dbId when PUI is sparse, pedigree leaf-count correctness — plus shared `find_*` rendering."
breaking: false
---

# 0.3.8 — 2026-04-29

A correctness pass shaken out by running the read surface against live BrAPI deployments (CassavaBase, BTI Cassava). Most fixes target single-server quirks that the v2.1 spec allows but our v0.3.x mocks didn't cover: GeoJSON-only coordinate carriers, sparse `observationVariablePUI`, and strict `pageSize` validation. The `find_*` tools also got a shared rendering layer — same data, more legible output, and a new warning when the upstream silently ignores a filter.

## Added

- **`renderFindHeader` helper (`src/mcp-server/tools/shared/find-helpers.ts`)** — standardized header for every `find_*` tool. When a dataset spillover is present, shows `{returned} returned · {dataset} in dataset · {total} total {noun}` so the middle number is no longer hidden when filters miss server-side and the dataset row count diverges from both. Falls back to `{returned} of {total}` when no spillover.
- **`renderAppliedFilters` helper** — translates server-side filter keys (e.g. `commonCropNames`, `studyTypes`) to the user-facing tool param names (`crop`, `trialTypes`) when rendering the `Applied filters` block, so the agent can correlate what they typed with what got sent upstream. Server keys without a user alias (i.e. anything from `extraFilters`) render as-is.
- **`checkFilterMatchRates` helper** — runs after distributions are computed and emits a warning when none of the requested filter values appear in the returned distribution. Detects upstreams that silently ignore an unsupported filter and return the unfiltered set instead of the requested subset. Wired into `find_studies`, `find_germplasm`, `find_locations`, `find_images`, `find_observations`, `find_variables`.
- **`extractCoordinates` helper** — pulls WGS84 from a Location record, preferring the BrAPI v2 GeoJSON Feature shape (`coordinates.geometry.coordinates = [lon, lat, alt?]`) and falling back to legacy top-level `latitude`/`longitude`/`altitude`. Used by `find_locations` (rendering + bbox filter) and `get_study` (rendering).
- **`buildRefinementHint` `availableFilters` option** — when no distribution has enough cardinality to surface a specific narrower (e.g. all distributions are empty or single-valued), falls back to listing the tool's available filter parameter names so the agent gets concrete next-step guidance. Wired across all spillover-capable `find_*` tools.
- **`OntologyCandidate.observationVariableDbId`** — the resolver now carries the source row's server-side variable DbId on every candidate when present, so callers can map candidates back to their rows even when `observationVariablePUI` is missing.
- **`Location.coordinates` on `reference-data-cache/types.ts`** — explicit type for the BrAPI v2 GeoJSON Feature carrier, alongside legacy `latitude`/`longitude`. Existing legacy fields gained a comment noting they're the legacy form.
- **Tests** — `tests/tools/brapi-find-locations.tool.test.ts` covers GeoJSON-only coordinate extraction (CassavaBase real shape) and bbox honoring GeoJSON. `tests/tools/brapi-find-variables.tool.test.ts` covers free-text promotion by dbId when PUI is missing. `tests/tools/brapi-walk-pedigree.tool.test.ts` adds a case asserting unparented ancestors count as leaves and the root does not.

## Changed

- **All eight `find_*` tools wired through the shared helpers** — `find_studies`, `find_germplasm`, `find_locations`, `find_images`, `find_observations`, `find_variables`, `find_variants`, `find_genotype_calls` now use `renderFindHeader`, `renderAppliedFilters` (where applicable), and `buildRefinementHint`'s `availableFilters` option. Six of them also surface `checkFilterMatchRates` warnings.
- **`brapi_get_image` format** — error and warning blocks now lead with a newline so they don't collapse into the previous block. Pure rendering tweak; structuredContent unchanged.

## Fixed

- **`pageSize=0` rejected by v2.1-strict servers** — BrAPI v2.1 mandates `pageSize >= 1`, and CassavaBase rejects `pageSize=0` with HTTP 400. The orientation envelope's opportunistic-count probes (`fetchOpportunisticCounts` in `src/mcp-server/tools/shared/orientation-envelope.ts`) now request `pageSize=1` and ignore the row — only `metadata.pagination.totalCount` matters. Docstrings on `brapi_get_study` and `brapi_submit_observations` updated to match.
- **GeoJSON-only locations rendered as `lat=undefined`** — modern BrAPI v2 servers (CassavaBase, BTI Cassava) return location coordinates as a GeoJSON Feature in `coordinates.geometry.coordinates`, not as top-level `latitude`/`longitude`. `find_locations` (both formatting and the optional `bbox` filter) and `get_study` now extract from either shape via `extractCoordinates`. Also dual-rendered in `get_study` when the legacy fields are absent but the GeoJSON Feature is present.
- **`find_variables` free-text promotion silently no-op'd against CassavaBase** — promotion was keyed off `observationVariablePUI`, which CassavaBase doesn't populate. The matcher now keys off `observationVariableDbId` (always present) with PUI as a fallback for any candidate that lacked the dbId. Free-text matches now actually surface to the top of the in-context set against real upstreams.
- **`brapi_walk_pedigree` `leafCount` was off in both directions** — the previous implementation counted nodes with no outgoing edges in the walked direction, which mis-classified seed germplasm as leaves and missed unparented ancestors. `computeLeafCount` now takes the walk direction explicitly, walks `to`-end of the recorded edges (ancestor leaves are nodes nothing points to as a child; descendant leaves are mirror image), and excludes roots. Caller in `brapiWalkPedigree.handler` updated to pass `input.direction`.
