# Reported issues for brapi-mcp-server

Pod holds 24 of 46 problems reported by people outside the maintainer team. Issues filed by the project's own owners, members and collaborators are excluded entirely — a maintainer's release checklist is not a warning to a prospective user.

Back to [brapi-mcp-server](/mcp/brapi-mcp-server).

## Most discussed

### feat(loadLimit): decouple in-context cap from upstream pageSize

`loadLimit` does three jobs and the entanglement leaks into agent behavior:

1. In-context row cap returned to the LLM
2. Spillover trigger threshold (`maybeSpill` returns first-page only when `totalCount ≤ loadLimit`)
3. Upstream `pageSize` for both first call and every spillover page (`spillToCanvas` in `src/mcp-server/tools/shared/find-helpers.ts`)

## Footgun

An agent that lowers `loadLimit` to "see fewer rows inline" silently shrinks dataframe spillover capacity proportionally — `loadLimit

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/26) · 2026-05-05 · closed · 5 comments

### Add brapi_germplasm_performance tool — aggregate observations across studies

### Use case

"How does line X-401 perform across the germplasm pool's history?" is the breeder's primary question. Today this requires N `find_observations` calls + manual aggregation, and is easy to get wrong on dialects that drop the germplasm filter (returning the global aggregate instead of the per-germplasm one).

### Dependency

**Blocked by #8.** This issue depends on what `brapi_build_phenotype_matrix` outputs. The implementation path and final shape are confirmed after #8 lands.

### T

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/9) · 2026-05-01 · closed · 5 comments

### Dialect detection: URL pattern matching + verified-flag per mapping

## Motivation

Today `detectDialectFromName` only matches on `serverInfo.serverName` (and `organizationName`). Two real-world failure modes:

1. **Generic server names.** Some SGN-family deployments report `serverName: 'BrAPI'` or omit it entirely. We catch "Boyce Thompson Institute" via `organizationName` but miss anything else hosted on `*.cassavabase.org`, `*.sweetpotatobase.org`, `*.yambase.org`, etc.
2. **Mapping confidence is invisible.** `PLURAL_TO_SINGULAR` in `cassavabase-dialect.ts` mi

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/5) · 2026-05-01 · closed · 5 comments

### bug(find_observations): Sweetpotatobase study spillover can run past client timeout

### Server version

0.7.2

### mcp-ts-core version

^0.9.21

### Runtime

Bun

### Runtime version

Bun ≥ 1.3.0

### Transport

stdio / direct handler validation

### Description

`brapi_find_observations({ studies: ["1131"] })` against Sweetpotatobase is not an `all_filters_dropped` dialect failure on the current tree. The Breedbase dialect rewrites `studyDbIds` to `studyDbId`, and the upstream GET route returns the expected 1,221 observations.

The rough edge is spillover latency: at the forme

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/21) · 2026-05-02 · closed · 4 comments

### feat(walk_pedigree): spill large pedigree results to canvas dataframe(s)

`brapi_walk_pedigree` is the only walking tool in this server that does not follow the `find_*` spillover pattern. When a pedigree walk returns thousands of nodes (cassavabase descendant walks easily hit this), the full `nodes[]` and `edges[]` arrays are returned inline regardless of size. Two related problems:

1. **The `MAX_NODES = 1000` cap is loose.** The check (`src/mcp-server/tools/definitions/brapi-walk-pedigree.tool.ts:198`) runs at the start of each depth iteration, but a single depth c

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/17) · 2026-05-02 · closed · 4 comments

### bug(get_*): upstream 5xx on missing DbId surfaces as transport error + 4-retry storm; not_found contract never fires

### Server version

0.5.3

### mcp-ts-core version

0.8.17

### Runtime

Bun

### Runtime version

Bun 1.3.11

### Transport

HTTP (Streamable HTTP)

### OS

macOS 25.1.0

### Description

Singleton GET tools — `brapi_get_study`, `brapi_get_germplasm`, plus the `brapi://study/{id}` and `brapi://germplasm/{id}` resources — surface upstream-server bugs as opaque transport errors when the upstream returns 5xx for a missing DbId. On Breedbase (one of the built-in aliases), `/studies/{nonexistent}` r

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/30) · 2026-05-06 · closed · 3 comments

### bug(error-contracts): data.recovery.hint missing on the wire for several declared-contract throws

### Server version

0.5.3

### mcp-ts-core version

0.8.17

### Runtime

Bun

### Runtime version

Bun 1.3.11

### Transport

HTTP (Streamable HTTP)

### OS

macOS 25.1.0

### Description

Several tools declare typed error contracts with a `recovery` field but throw via `ctx.fail(reason, ...)` **without** spreading `ctx.recoveryFor(reason)` into the data payload. The framework requires the spread to opt the contract recovery onto the wire (see `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` "Err

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/29) · 2026-05-06 · closed · 3 comments

### Dialect: add normalizeRow hook for null-vs-omitted shape coercion

## Motivation

CassavaBase returns `null` for many optional fields where the BrAPI v2.1 spec says the field should be omitted entirely. Today every BrAPI tool's row schema absorbs this with `.nullish()` — repeated across dozens of fields and growing every time we discover a new sparse upstream shape.

That's a structural smell:
- The same defensive change has to be applied in every schema.
- Renderer code has to repeat `if (g.value)` guards row-by-row to avoid `'lat=null'` style output.
- Future

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/6) · 2026-05-01 · closed · 3 comments

## Most recent

### bug(builtin-aliases): t3-wheat / t3-oat / t3-barley now answer 401 on /serverinfo

### Server version

0.7.10 (reproduces on the 0.7.11 tree)

### mcp-ts-core version

0.12.3

### Runtime

Bun

### Runtime version

Bun 1.4.0

### Transport

stdio

### Description

The three built-in T3 aliases in `src/config/builtin-aliases.ts` (`t3-wheat`, `t3-oat`, `t3-barley`) are documented as no-auth public endpoints in `.env.example` and the README. All three hosts now answer HTTP 401 on `GET /brapi/v2/serverinfo` anonymously:

```
curl -s -o /dev/null -w '%{http_code}' https://wheat.tri

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/52) · 2026-08-22 · open · 0 comments

### bug(brapi-client): singleton HTTP 500 bypasses the Breedbase not-found reclassification

### Server version

0.7.10 (reproduces on the 0.7.11 tree)

### mcp-ts-core version

0.12.3

### Runtime

Bun

### Runtime version

Bun 1.4.0

### Transport

http

### Description

`reclassifyHttpError` in `src/services/brapi-client/brapi-client.ts` documents that a 5xx on a singleton `/{noun}/{id}` fetch is treated as `NotFound`, because Breedbase answers HTTP 500 for an unknown DbId. The guard at the top of the function returns early unless `err.code === JsonRpcErrorCode.ServiceUnavailable`, b

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/51) · 2026-08-22 · open · 0 comments

### feat(dataframe): name the dataframe tools in responses that stage a dataframe

Thirteen tools stage rows as a canvas dataframe and hand back a `df_XXXXX_XXXXX` handle, but nothing in the response payload tells the agent to reach that data with `brapi_dataframe_describe` and `brapi_dataframe_query`. The handle is an opaque generated name; an agent that receives one without a pointer either reports the truncation and stops, or re-runs the producing tool with narrower filters — re-fetching rows that are already staged one SQL query away.

The gap is in the **runtime response*

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/50) · 2026-08-13 · open · 0 comments

### feat(isolation): fail closed on credentialed connection state when no session key exists

Under `BRAPI_SESSION_ISOLATION=true` (the default), both the connection registry and the canvas bridge key their state on `ctx.sessionId`, and when it is absent both fall back silently to the shared per-tenant namespace (`scopePrefix()` in `src/services/server-registry/server-registry.ts`, `DEFAULT_CANVAS_KEY` in `src/services/canvas-bridge/canvas-bridge.ts`). For the connection registry that shared namespace holds `RegisteredServer` entries whose `resolvedAuth` is the live upstream credential h

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/49) · 2026-07-27 · open · 0 comments

### feat(observations): parallelize per-study pulls in germplasm_performance and build_phenotype_matrix

`brapi_germplasm_performance` and `brapi_build_phenotype_matrix` both collect observations study-by-study through a sequential `for` loop around the shared `pullStudyObservations` helper — one full HTTP round trip (plus the `/observationunits` fallback) per study, awaited before starting the next. Neither tool bounds concurrency. `BRAPI_MAX_CONCURRENT_REQUESTS` (default 4, documented in README/.env.example as a "per-connection concurrency cap") is declared in `server-config.ts` but isn't read by

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/48) · 2026-07-16 · closed · 1 comment

### bug(dataframe_describe): renders every column name uncapped

### Server version

0.7.6

### mcp-ts-core version

^0.10.14

### Runtime

Bun

### Runtime version

Bun 1.3.14

### Transport

HTTP JSON-RPC

### Description

`brapi_dataframe_describe` renders one line per column with no size bound. For a dataframe with a normal shape (a `find_*` spillover, ~18 columns) that is correct and cheap. For a genotype matrix, one column is registered per variant, so a wide matrix makes the tool emit a column name per variant into `content[]`.

Related: #46

### Steps

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/47) · 2026-07-16 · closed · 1 comment

### feat(export_genotype_matrix): bound columnCount, not just total calls

`brapi_export_genotype_matrix` bounds the total genotype calls it pulls (`maxCalls`, clamped to `BRAPI_GENOTYPE_CALLS_MAX_PULL`), but nothing bounds how those calls distribute across the germplasm × variant grid. A schema-legal skew — one germplasm × `maxCalls` variants — makes `columnCount` equal the entire call budget, and every per-variant output scales with it.

Related: #42, #45

## Proposal

Bound `columnCount` directly, mirroring the `maxCalls` clamp #45 established: a deployment-level ce

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/46) · 2026-07-16 · closed · 1 comment

### bug(export_genotype_matrix): maxCalls is unbounded and bypasses the deployment call ceiling

### Server version

0.7.6

### mcp-ts-core version

^0.10.9

### Runtime

Bun

### Runtime version

Bun 1.3.14

### Transport

HTTP

### Description

`brapi_export_genotype_matrix`'s `maxCalls` input carries no `.max()` bound and is never clamped against the deployment's `genotypeCallsMaxPull` config ceiling. A caller can pass an arbitrarily large `maxCalls` and the tool honors it, so the documented "hard ceiling" is advisory only.

`brapi_find_genotype_calls` does not have this gap — it uses th

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/45) · 2026-07-16 · closed · 1 comment

### bug(docker): HEALTHCHECK uses curl, which the bun slim base image does not ship

### Server version

0.7.6

### mcp-ts-core version

^0.10.9

### Runtime

Bun

### Runtime version

Bun 1.3.14

### Transport

HTTP

### Description

The `Dockerfile` `HEALTHCHECK` probes `/healthz` with `curl -f`, but the `oven/bun:*-slim` production base ships neither `curl` nor `wget`. The probe can never succeed, so a container run from the published image reports `unhealthy` for its entire lifetime even while the server is serving correctly.

Any orchestrator that gates on container health 

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/44) · 2026-07-16 · closed · 1 comment

### feat(germplasm_performance): add a retrieval path when study discovery hits the cap

### Server version

0.7.6

### mcp-ts-core version

^0.10.14

### Runtime

Bun

### Description

`brapi_germplasm_performance` caps automatic study discovery at `STUDY_DISCOVERY_CAP = 200`. When that cap is hit, the response warns that aggregates may be incomplete and suggests narrowing with a study-specific phenotype matrix, but the tool does not expose a continuation cursor, an offset, or a dataframe/list handle that lets an agent retrieve the omitted study slice.

That leaves a truncation pat

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/43) · 2026-07-05 · closed · 1 comment

### bug(export_genotype_matrix): content[] previews VCF/PLINK text and column legend

### Server version

0.7.6

### mcp-ts-core version

^0.10.9

### Runtime

Bun

### Runtime version

Bun 1.3.14

### Transport

HTTP JSON-RPC / direct formatter check

### Description

`brapi_export_genotype_matrix` puts generated VCF/PLINK serializations in `structuredContent`, but `format()` emits only a preview and tells clients to read `structuredContent.<field>`. Content-only clients lose generated rows.

The same formatter caps the variant column legend at 20 remappings.

### Steps to repro

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/42) · 2026-07-05 · closed · 1 comment

### bug(format): shared renderers omit full distributions and large passthrough fields from content[]

### Server version

0.7.6

### mcp-ts-core version

^0.10.9

### Runtime

Bun

### Runtime version

Bun 1.3.14

### Transport

HTTP JSON-RPC

### Description

Several shared format helpers render summaries in `content[]` for fields that are complete in `structuredContent`. Content-only clients cannot recover those values because the text points to `structuredContent`, which those clients do not read.

This breaks parity between the two result surfaces. Both surfaces are used by different AI clie

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/41) · 2026-07-05 · closed · 1 comment

### bug(get_study): variableCount/observationCount report server-wide totals, not study-scoped

### Server version

0.7.4

### mcp-ts-core version

0.10.6 (`^0.10.6`)

### Runtime

Bun

### Transport

HTTP (Streamable HTTP)

### Description

`brapi_get_study` returns companion counts (`variableCount`, `observationCount`) that are **server-wide totals, not scoped to the study**. The enrichment scopes them with a `studyDbIds` filter, which the `brapi-test` dialect drops upstream (the response even carries the `dropped filter 'studyDbIds'` warning), so the counts silently collapse to the glob

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/40) · 2026-06-18 · closed · 1 comment

### bug(dataframe_query): every query fails on internal probe — "Ambiguous reference to column name table_name"

### Server version

0.7.4

### mcp-ts-core version

0.10.6 (`^0.10.6`)

### Runtime

Bun

### Transport

HTTP (Streamable HTTP)

### Description

Every `brapi_dataframe_query` call fails *before* the user SQL runs — including `SELECT 1`. The handler issues an internal pre-flight probe against the DuckDB catalog whose `WHERE` clause references an unqualified `table_name`, which DuckDB rejects as ambiguous. Named `brapi_dataframe_describe` hits the same wall (the no-arg list mode returns the inten

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/39) · 2026-06-18 · closed · 1 comment

### bug(submit_observations): apply mode hard-blocked — ctx.elicit never defined at runtime (upstream mcp-ts-core#211)

`brapi_submit_observations` apply mode requires an elicitation confirm and correctly fails closed when the client lacks it: `elicit_unavailable` (Forbidden) unless `force=true`. Upstream cyanheads/mcp-ts-core#211 means `ctx.elicit` is `undefined` on every production request regardless of client capability — the framework's capability detection sniffs methods the SDK never attaches.

Net effect today: apply mode returns `elicit_unavailable` on **all** clients, including elicit-capable ones. The r

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/38) · 2026-06-10 · closed · 1 comment

### bug(find_variants): dataframe spillover throws on reserved-word column "end"

`brapi_find_variants` throws `Canvas column name "end" is a reserved SQL keyword. Choose another name.` whenever its result spills to a canvas dataframe. BrAPI `/variants` records carry an `end` field (variant end position), and the generic `find_*` spillover path registers rows under their raw upstream keys as DuckDB column names — `end` is a reserved word, so `assertValidIdentifier` rejects it at `registerTable` and the whole call fails (no dataframe is produced).

### Reproduce

Against the B

[Read the thread](https://github.com/cyanheads/brapi-mcp-server/issues/37) · 2026-06-02 · closed · 2 comments

The remaining reports are on [the project's issue tracker](https://github.com/cyanheads/brapi-mcp-server/issues).
