---
summary: "BMS dialect for the CGIAR crop community, route escalation to POST /search when GET would downcast multi-value filters, walk_pedigree `direction` field, plus fixes for bbox spillover ordering, schemeless `imageURL` resolution, singleton-fetch 5xx retry storms, and large inline JSON in `find_*` text rows."
breaking: false
security: false
---

# 0.6.0 — 2026-05-22

## Added

- **`bms` dialect** ([`src/services/brapi-dialect/bms-dialect.ts`](./src/services/brapi-dialect/bms-dialect.ts)) — covers Breeding Management System servers (CIMMYT, IRRI, ICRISAT, IITA, CIP). v2.1 plural → v2.0 singular translation table per endpoint plus auto-detection from server-name (`BMS`, `BMSAPI`, `Breeding Management System`) and organization-name (CGIAR centers). Mappings ship marked `verified: false` pending live narrowing against pinned test endpoints — agents see an inferred-mapping warning on downcast so they validate result counts via distributions before trusting them. ([#7](https://github.com/cyanheads/brapi-mcp-server/issues/7))
- **`direction` field on `brapi_walk_pedigree` nodes** — every node now carries `direction: 'root' | 'ancestor' | 'descendant' | 'both'`, so bidirectional walks can distinguish the side of the root each germplasm was reached from. Convergence rule: ancestor + descendant in the same traversal upgrades to `'both'` (backcross / selfing chain). ([#16](https://github.com/cyanheads/brapi-mcp-server/issues/16))
- **Dialect filter-mapping confidence on the orientation envelope** — `verifiedMappingCount` / `inferredMappingCount` surface on `dialect` in `brapi_connect` and `brapi_server_info` so agents see the dialect's confidence floor at a glance. Drives a softer warning on downcast for inferred mappings. New `DialectFilterMapping` shape (`{ target, verified }`) on the dialect interface; mixed-shape input (string shorthand or explicit object) keeps authoring ergonomic.
- **SGN-family row normalizer** — `cassavabase` and `breedbase` dialects now strip `null` from upstream rows pre-schema (recursively, one level into nested objects + arrays of objects). Lets row schemas express the natural "field is absent" via `optional()` without every schema absorbing a `.nullish()` per field. New optional `normalizeRow(endpoint, row)` method on `BrapiDialect`; default `spec` dialect omits it (passthrough).
- **`requireRegisteredConnection` helper** ([`src/mcp-server/tools/shared/find-helpers.ts`](./src/mcp-server/tools/shared/find-helpers.ts)) — replaces inline `registry.get(ctx, alias ?? DEFAULT_ALIAS)` across tools and resources. Throws via `ctx.fail('unknown_alias', …)` so the wire-level error carries `data.recovery.hint` from the calling definition's `errors[]` contract. Adopted by every tool/resource that resolves a connection. ([#29](https://github.com/cyanheads/brapi-mcp-server/issues/29))
- **`singleton: true` on `BrapiRequestOptions`** ([`src/services/brapi-client/types.ts`](./src/services/brapi-client/types.ts)) — marks a request as a `/{noun}/{id}` fetch. The client reclassifies 5xx responses on these calls as `NotFound`, so `brapi_get_study`, `brapi_get_germplasm`, and `brapi_walk_pedigree`'s per-root probes fast-fail on missing DbIds without burning the retry budget. ([#30](https://github.com/cyanheads/brapi-mcp-server/issues/30))

## Changed

- **Route escalation to POST `/search/{noun}`** ([`src/mcp-server/tools/shared/find-helpers.ts`](./src/mcp-server/tools/shared/find-helpers.ts), [`src/services/brapi-dialect/singularizing-dialect.ts`](./src/services/brapi-dialect/singularizing-dialect.ts)) — when the active dialect would have downcast a multi-value array filter to its first scalar on the GET wire (SGN-family + BMS only honor singular filter keys) and POST `/search/{noun}` is advertised and not disabled, `resolveFindRoute` now picks search. The search body preserves the original multi-value semantics that the GET wire shape would have silently lost. New `requiresEscalation` field on `DialectAdaptation`. ([#15](https://github.com/cyanheads/brapi-mcp-server/issues/15))
- **`brapi_find_locations` bbox filter runs before spillover** ([`src/mcp-server/tools/definitions/brapi-find-locations.tool.ts`](./src/mcp-server/tools/definitions/brapi-find-locations.tool.ts)) — axis-order pick (`spec` vs `swapped` for non-conformant servers) is now made over the first-page rows, then handed to `maybeSpill` as a `rowFilter`. The dataframe materialized on spillover holds post-bbox rows, so SQL on the `df_*` handle sees the same set the inline result preview did. ([#28](https://github.com/cyanheads/brapi-mcp-server/issues/28))
- **Orientation envelope headline `Connected` → `Registered` when no services advertised** ([`src/mcp-server/tools/shared/orientation-envelope.ts`](./src/mcp-server/tools/shared/orientation-envelope.ts)) — registering a connection against an unreachable server (or one returning a zero-capability `/serverinfo`) shouldn't read as "Connected" to a skimming agent. The `Registered` headline signals "alias is bound, server is not responding usefully." ([#32](https://github.com/cyanheads/brapi-mcp-server/issues/32))
- **Canvas dataframe naming `df_<uuid>` → `df_XXXXX_XXXXX`** ([`src/services/canvas-bridge/canvas-bridge.ts`](./src/services/canvas-bridge/canvas-bridge.ts)) — 10 random chars from `A–Z 0–9` split into two 5-char groups by `_`, generated via the framework's `idGenerator`. Keyspace ~3.7×10^15 (36^10), unguessable for the (tenant, session)-bucketed capability-token model and short enough to render cleanly in inline tool output. ([#34](https://github.com/cyanheads/brapi-mcp-server/issues/34))
- **`AliasInput` description clarified** — omitting `alias` reads the connection registered under alias `default` (i.e. a prior `brapi_connect` call that did not specify an alias). Calls that used a non-default alias must pass that same alias back; previous wording suggested an implicit "most recent connect" which never existed. ([#32](https://github.com/cyanheads/brapi-mcp-server/issues/32))
- **`brapi_dataframe_describe` shared-HTTP error language** — `list_all_disabled_on_shared_http` no longer mentions `MCP_AUTH_MODE=none` by name (operators may configure shared-trust HTTP differently). Headline and recovery now describe the deployment shape rather than its env-var implementation. ([#32](https://github.com/cyanheads/brapi-mcp-server/issues/32))

## Fixed

- **`brapi_get_image` resolves schemeless `imageURL` values correctly** ([`src/mcp-server/tools/definitions/brapi-get-image.tool.ts`](./src/mcp-server/tools/definitions/brapi-get-image.tool.ts)) — protocol-relative (`//host/path`) gets an `https:` prefix; domain-shaped values (`breedbase.org/data/images/.../medium.jpg`) get an `https://` prefix; root-relative and bare paths still concatenate with the BrAPI `baseUrl` (the legitimate "image lives on the same BrAPI host" case). Previously, the domain-shaped form was silently treated as a relative path and produced an unresolvable URL. ([#31](https://github.com/cyanheads/brapi-mcp-server/issues/31))
- **Large nested objects no longer dump multi-KB inline in `find_*` text rows** ([`src/mcp-server/tools/shared/find-helpers.ts`](./src/mcp-server/tools/shared/find-helpers.ts)) — passthrough rendering for both `collectPassthroughParts` (bullet rows) and `appendPassthroughLines` (detail-view lines) applies a 240-char inline cap; values over the cap collapse to `<N keys, Xkb — see structuredContent>` (or `<N entries, …>` for arrays). The full payload remains in `structuredContent` so structured clients (Codex Desktop) see the same data. Affects `find_studies` (`additionalInfo`), `find_variables` (`trait`, `scale`), and any other surface that carries large nested objects through the passthrough path. ([#33](https://github.com/cyanheads/brapi-mcp-server/issues/33))

## Dependencies

- **`@duckdb/node-api` `^1.5.2-r.1 → ^1.5.3-r.1`**, **`@types/node` `^25.8.0 → ^25.9.1`**, **`vitest` `^4.1.6 → ^4.1.7`** — patch refreshes.
