---
summary: "Read-side surface expansion — 11 new tools (variables, observations, images, genotype calls, locations, variants, pedigree walks, dataset lifecycle, raw passthrough) plus OntologyResolver and binary image fetch."
breaking: false
---

# 0.2.0 — 2026-04-23

Fills out the read-side BrAPI surface. The orient + studies/germplasm trio from 0.1.1 is joined by variables, observations, images, locations, variants, genotype calls, pedigree DAG walks, a dataset lifecycle tool, and raw-endpoint escape hatches. Tool count: 7 → 18.

## Added

- **Pedigree traversal** — `brapi_walk_pedigree` BFS-walks germplasm ancestry or descendancy as a deduplicated DAG. BrAPI only exposes one generation per call; this tool handles cycle detection, depth limits (default 3, cap 10), and a 1000-node safety cap. Up to 20 roots per call, each walked concurrently. Returns nodes + edges plus traversal stats (`depthReached`, `leafCount`, `cycleCount`, `deadEndCount`, `truncated`).
- **Observation variables** — `brapi_find_variables` with free-text ranking via the new `OntologyResolver` service (`src/services/ontology-resolver/`). Scores variable records against a query across PUI / name / synonym / trait-class / trait description and surfaces the top 10 candidates alongside the full result set. Falls back to substring matching when the server doesn't expose `/ontologies`.
- **Observations** — `brapi_find_observations` with filters for study, germplasm, variable, season, observation unit, observation level, timestamp range, program, and trial. Distributions across variable / study / germplasm / level / season; dataset spillover on the standard `find_*` pattern.
- **Images** — `brapi_find_images` (metadata only, distributions across MIME type / study / observation unit / ontology terms) and `brapi_get_image` (fetch bytes inline as `type: image` content blocks, up to 5 images per call, 20 MB per image). `get_image` prefers `/images/{id}/imagecontent` and falls back to the `imageURL` metadata field; relative URLs resolve against the registered base URL, absolute URLs pass through without auth.
- **Locations** — `brapi_find_locations` with filters for country / type / abbreviation / ID and an optional client-side bounding-box filter (BrAPI has no spec-level bbox). All four corners required to activate; mismatched ranges produce a warning and the filter is skipped.
- **Variants** — `brapi_find_variants` with genomic-region filters (1-based inclusive `start` / exclusive `end` per the BrAPI spec). Distributions across `variantType`, `referenceName`, `variantSetDbId`.
- **Genotype calls** — `brapi_find_genotype_calls` handles the async-search pattern (`POST /search/calls` → `GET /search/calls/{id}` with 202-retry) transparently. Default cap of 100k calls per call, hard cap of 500k. Rows beyond `loadLimit` spill to `DatasetStore` for export. Server-reported genotype-encoding hints (`expandHomozygotes`, `unknownString`, `sepPhased`, `sepUnphased`) are echoed so the agent can interpret the values. Requires at least one filter — unfiltered pulls are rejected at the input boundary.
- **Dataset lifecycle** — `brapi_manage_dataset` consolidates list / summary / load / delete operations against `DatasetStore`. `mode: load` supports paged rows (up to 1000 per page) and optional column projection. Export (CSV / Parquet) remains deferred until a storage write path lands.
- **Raw passthroughs** — `brapi_raw_get` (any BrAPI GET) and `brapi_raw_search` (any BrAPI `POST /search/{noun}` with async polling) for niche endpoints the curated tools don't cover. Both emit a `suggestion` field routing to the goal-shaped tool when one exists — e.g. `raw_get /studies` nudges to `brapi_find_studies`. `raw_get` blocks absolute URLs in `path` to prevent cross-origin smuggling via the registered base URL.
- **`OntologyResolver` service** (`src/services/ontology-resolver/`) — stateless scorer ranking `VariableLike` records against a query. PUI exact match (100) > name exact (90) > name substring (60) > variable synonym (50) > trait synonym (40) > trait name (35) > trait class exact (30) > trait description (20). Swappable backend — an embedding-backed implementation can replace this one without changing the tool surface.
- **Binary fetch** on `BrapiClient` — new `getBinary()` honors the same retry policy as `get()` and threads the private-IP guard; `fetchBinaryUrl()` pulls arbitrary URLs (e.g. CDN-backed `imageURL`) without attaching auth. Both return a `BinaryResponse` (`{ bytes, contentType }`).
- **Shared routing hints** (`src/mcp-server/tools/shared/raw-routing-hints.ts`) — centralized map from path segment / search noun to the curated-tool suggestion, powering `raw_get` / `raw_search`.

## Changed

- **`find-helpers.ts`** — extracted shared utilities previously duplicated between `brapi_find_studies` and `brapi_find_germplasm`:
  - `DatasetHandleSchema` + `toDatasetHandle()` — the in-context dataset handle shape (drops provenance fields available via `brapi_manage_dataset summary`).
  - `maybeSpill()` — wraps `spillToDataset()` with the "only spill when `hasMore` and `totalCount > loadLimit`" guard. All nine `find_*` tools use it.
  - `asString()`, `asStringArray()`, `buildRefinementHint()` — scalar / array coercion and the refinement-hint heuristic (pick the highest-cardinality non-empty distribution).
  - `brapi_find_studies` and `brapi_find_germplasm` slimmed down to use the extracted helpers; the two files dropped ~100 lines of duplication.
- **`src/index.ts`** — registers 11 new tools and calls `initOntologyResolver()` in `setup()`.
- **`README.md`** — tool count 7 → 18, tool tables regrouped by axis (orient / retrieve / orchestrate / escape hatches), per-tool sections added for every new tool, BrAPI-specific feature bullets expanded (async-search transparency, pedigree DAG walks, image content, free-text variable ranking, last-resort escape hatches), project-structure table gains `ontology-resolver`.
- **`CLAUDE.md`** — structure section updated with all 18 tool files and the `ontology-resolver` service.
- **`docs/tree.md`** — regenerated to reflect the new files.
