---
summary: "Adopt mcp-ts-core 0.8.6 typed-error contracts across the BrAPI surface, fix a germplasmOrigin schema bug that broke output validation against spec-compliant servers."
breaking: false
---

# 0.3.2 — 2026-04-29

Tools and resources now declare typed `errors: [...]` contracts and throw via `ctx.fail(reason, …)` instead of `notFound()`/`validationError()` factories. Each declared failure carries a stable `data.reason`, an auto-mirrored `recovery.hint` on the wire, and a `code` that matches its contract entry — so agent clients can route errors deterministically. Verified end-to-end against `test-server.brapi.org`. Also ships a real bug fix: `germplasmOrigin` was schema-typed as a string, but BrAPI v2 spec defines it as an array of collection-site objects, so `find_germplasm` and `get_germplasm` were throwing on every call against a compliant server.

## Added

- **Typed error contracts on tools & resources.** New `errors: [{ reason, code, when, recovery }]` blocks declared on:
  - Tools: `brapi_connect` (auth_token_exchange_failed, auth_no_access_token), `brapi_describe_filters` (unknown_endpoint), `brapi_find_genotype_calls` (no_filters), `brapi_get_germplasm` (germplasm_not_found), `brapi_get_image` (images_unsupported), `brapi_get_study` (study_not_found), `brapi_manage_dataset` (datasetid_required, dataset_not_found, dataset_rows_missing), `brapi_raw_get` (cross_origin_path), `brapi_submit_observations` (observations_unsupported, post_unsupported, put_unsupported, elicit_unavailable, user_declined).
  - Resources: `brapi://filters/{endpoint}` (unknown_endpoint), `brapi://germplasm/{germplasmDbId}` (germplasm_not_found), `brapi://study/{studyDbId}` (study_not_found).
- **`bun run start` script** — defers transport selection to `MCP_TRANSPORT_TYPE` (stdio default) so consumers can wire one entry point and switch transports via env.

## Changed

- **Bumped `@cyanheads/mcp-ts-core` from `^0.7.4` → `^0.8.6`** to pick up the typed-`HandlerContext<R>` API, `ctx.fail` / `ctx.recoveryFor`, and the `error-contract-conformance` lint.
- **Tool & resource handlers** swapped ad-hoc `notFound()` / `validationError()` / `forbidden()` throws for `ctx.fail('reason', msg, { ...ctx.recoveryFor('reason') })` against their declared contracts. Recovery hints now ride on `data.recovery.hint` and are auto-mirrored into `content[].text`.
- **`brapi_submit_observations` confirmApply** typed against `HandlerContext<reason>` via a new `SubmitCtx` alias so all five declared failure modes are catchable at compile time.
- **Service-layer errors** (`brapi-client`, `dataset-store`, `server-registry`) now stamp a stable `reason` field into `error.data` for upstream-classified failures (`upstream_not_found`, `upstream_unauthorized`, `upstream_rate_limited`, `auth_no_access_token`, `dataset_rows_missing`, `unknown_alias`, etc.) — observable even though they're thrown outside a contract.
- **Skills synced to framework 0.8.6** — `add-tool`, `add-resource`, `add-service`, `api-context`, `api-errors`, `api-linter`, `api-testing`, `field-test`, and others updated with the typed-contract guidance and current API surface.
- **Project `CLAUDE.md`** updated with the typed-contract pattern and the `bun run start` script.

## Fixed

- **`germplasmOrigin` schema** in `brapi_find_germplasm` and `brapi_get_germplasm` was declared as `z.string()`, but BrAPI v2 spec defines the field as an array of `GermplasmOrigin` objects (collection coordinates + uncertainty). Output validation was failing on every response from a spec-compliant server — confirmed end-to-end against `test-server.brapi.org`. Schema is now `z.array(z.object({}).passthrough())` and the format renderers report `germplasmOrigin=<count> record(s)` instead of stringifying objects.
- **`brapi_raw_get` cross-origin guard** is now thrown via `ctx.fail('cross_origin_path', …)` rather than the bare `validationError` factory so the recovery hint surfaces on the wire.
