@cyanheads/brapi-mcp-server 0.6.4 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CLAUDE.md +11 -4
  2. package/README.md +10 -6
  3. package/changelog/0.7.x/0.7.0.md +23 -0
  4. package/changelog/0.7.x/0.7.1.md +17 -0
  5. package/dist/index.js +2 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/mcp-server/resources/definitions/brapi-variable.resource.d.ts +30 -0
  8. package/dist/mcp-server/resources/definitions/brapi-variable.resource.d.ts.map +1 -0
  9. package/dist/mcp-server/resources/definitions/brapi-variable.resource.js +78 -0
  10. package/dist/mcp-server/resources/definitions/brapi-variable.resource.js.map +1 -0
  11. package/dist/mcp-server/tools/definitions/brapi-build-phenotype-matrix.tool.d.ts +82 -0
  12. package/dist/mcp-server/tools/definitions/brapi-build-phenotype-matrix.tool.d.ts.map +1 -0
  13. package/dist/mcp-server/tools/definitions/brapi-build-phenotype-matrix.tool.js +366 -0
  14. package/dist/mcp-server/tools/definitions/brapi-build-phenotype-matrix.tool.js.map +1 -0
  15. package/dist/mcp-server/tools/definitions/brapi-export-genotype-matrix.tool.d.ts +89 -0
  16. package/dist/mcp-server/tools/definitions/brapi-export-genotype-matrix.tool.d.ts.map +1 -0
  17. package/dist/mcp-server/tools/definitions/brapi-export-genotype-matrix.tool.js +494 -0
  18. package/dist/mcp-server/tools/definitions/brapi-export-genotype-matrix.tool.js.map +1 -0
  19. package/dist/mcp-server/tools/definitions/brapi-find-genotype-calls.tool.d.ts +1 -0
  20. package/dist/mcp-server/tools/definitions/brapi-find-genotype-calls.tool.d.ts.map +1 -1
  21. package/dist/mcp-server/tools/definitions/brapi-find-genotype-calls.tool.js +17 -93
  22. package/dist/mcp-server/tools/definitions/brapi-find-genotype-calls.tool.js.map +1 -1
  23. package/dist/mcp-server/tools/definitions/brapi-find-germplasm.tool.d.ts +1 -0
  24. package/dist/mcp-server/tools/definitions/brapi-find-germplasm.tool.d.ts.map +1 -1
  25. package/dist/mcp-server/tools/definitions/brapi-find-images.tool.d.ts +1 -0
  26. package/dist/mcp-server/tools/definitions/brapi-find-images.tool.d.ts.map +1 -1
  27. package/dist/mcp-server/tools/definitions/brapi-find-locations.tool.d.ts +1 -0
  28. package/dist/mcp-server/tools/definitions/brapi-find-locations.tool.d.ts.map +1 -1
  29. package/dist/mcp-server/tools/definitions/brapi-find-observations.tool.d.ts +1 -0
  30. package/dist/mcp-server/tools/definitions/brapi-find-observations.tool.d.ts.map +1 -1
  31. package/dist/mcp-server/tools/definitions/brapi-find-studies.tool.d.ts +1 -0
  32. package/dist/mcp-server/tools/definitions/brapi-find-studies.tool.d.ts.map +1 -1
  33. package/dist/mcp-server/tools/definitions/brapi-find-variables.tool.d.ts +1 -0
  34. package/dist/mcp-server/tools/definitions/brapi-find-variables.tool.d.ts.map +1 -1
  35. package/dist/mcp-server/tools/definitions/brapi-find-variants.tool.d.ts +1 -0
  36. package/dist/mcp-server/tools/definitions/brapi-find-variants.tool.d.ts.map +1 -1
  37. package/dist/mcp-server/tools/definitions/brapi-germplasm-performance.tool.d.ts +60 -0
  38. package/dist/mcp-server/tools/definitions/brapi-germplasm-performance.tool.d.ts.map +1 -0
  39. package/dist/mcp-server/tools/definitions/brapi-germplasm-performance.tool.js +416 -0
  40. package/dist/mcp-server/tools/definitions/brapi-germplasm-performance.tool.js.map +1 -0
  41. package/dist/mcp-server/tools/definitions/brapi-raw-get.tool.d.ts +1 -0
  42. package/dist/mcp-server/tools/definitions/brapi-raw-get.tool.d.ts.map +1 -1
  43. package/dist/mcp-server/tools/definitions/brapi-raw-search.tool.d.ts +1 -0
  44. package/dist/mcp-server/tools/definitions/brapi-raw-search.tool.d.ts.map +1 -1
  45. package/dist/mcp-server/tools/definitions/brapi-walk-pedigree.tool.d.ts.map +1 -1
  46. package/dist/mcp-server/tools/definitions/brapi-walk-pedigree.tool.js +19 -6
  47. package/dist/mcp-server/tools/definitions/brapi-walk-pedigree.tool.js.map +1 -1
  48. package/dist/mcp-server/tools/definitions/index.d.ts +160 -0
  49. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  50. package/dist/mcp-server/tools/definitions/index.js +6 -0
  51. package/dist/mcp-server/tools/definitions/index.js.map +1 -1
  52. package/dist/mcp-server/tools/shared/canvas-columns.d.ts +55 -0
  53. package/dist/mcp-server/tools/shared/canvas-columns.d.ts.map +1 -0
  54. package/dist/mcp-server/tools/shared/canvas-columns.js +151 -0
  55. package/dist/mcp-server/tools/shared/canvas-columns.js.map +1 -0
  56. package/dist/mcp-server/tools/shared/find-helpers.d.ts +1 -0
  57. package/dist/mcp-server/tools/shared/find-helpers.d.ts.map +1 -1
  58. package/dist/mcp-server/tools/shared/find-helpers.js +12 -0
  59. package/dist/mcp-server/tools/shared/find-helpers.js.map +1 -1
  60. package/dist/mcp-server/tools/shared/genotype-calls.d.ts +81 -0
  61. package/dist/mcp-server/tools/shared/genotype-calls.d.ts.map +1 -0
  62. package/dist/mcp-server/tools/shared/genotype-calls.js +172 -0
  63. package/dist/mcp-server/tools/shared/genotype-calls.js.map +1 -0
  64. package/dist/mcp-server/tools/shared/observations.d.ts +87 -0
  65. package/dist/mcp-server/tools/shared/observations.d.ts.map +1 -0
  66. package/dist/mcp-server/tools/shared/observations.js +285 -0
  67. package/dist/mcp-server/tools/shared/observations.js.map +1 -0
  68. package/dist/services/canvas-bridge/canvas-bridge.d.ts +6 -0
  69. package/dist/services/canvas-bridge/canvas-bridge.d.ts.map +1 -1
  70. package/dist/services/canvas-bridge/canvas-bridge.js +10 -2
  71. package/dist/services/canvas-bridge/canvas-bridge.js.map +1 -1
  72. package/manifest.json +1 -1
  73. package/package.json +3 -3
  74. package/server.json +3 -3
package/CLAUDE.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** brapi-mcp-server
4
- **Version:** 0.6.4
4
+ **Version:** 0.7.1
5
5
  **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
6
6
 
7
7
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -168,7 +168,7 @@ Handlers receive a unified `ctx` object. Currently used surface:
168
168
  | `ctx.requestId` | Unique request ID — auto-attached to every `ctx.log` entry. |
169
169
  | `ctx.tenantId` | Tenant ID from JWT or `'default'` for stdio / HTTP+`auth=none` — outer scope on all `ctx.state` reads/writes. |
170
170
 
171
- `ctx.elicit` is used by `brapi_submit_observations` to gate apply-mode writes behind user confirmation (with explicit `force: true` as the bypass). `ctx.sample` and `ctx.progress` are not used yet — they'll show up when long-running workflows (pedigree traversal, genotype-call pulls) need progress reporting or LLM sampling. `ctx.fail(reason, …)` is the typed thrower keyed off declared `errors[]` contracts — used by 8 tools and 3 resources today. `ctx.recoveryFor(reason)` resolves the matching contract entry's recovery hint into `data.recovery.hint` so it surfaces on the wire.
171
+ `ctx.elicit` is used by `brapi_submit_observations` to gate apply-mode writes behind user confirmation (with explicit `force: true` as the bypass). `ctx.sample` and `ctx.progress` are not used yet — they'll show up when long-running workflows (pedigree traversal, genotype-call pulls) need progress reporting or LLM sampling. `ctx.fail(reason, …)` is the typed thrower keyed off declared `errors[]` contracts — used by 14 tools and 1 resource today. `ctx.recoveryFor(reason)` resolves the matching contract entry's recovery hint into `data.recovery.hint` so it surfaces on the wire.
172
172
 
173
173
  ---
174
174
 
@@ -176,7 +176,7 @@ Handlers receive a unified `ctx` object. Currently used surface:
176
176
 
177
177
  Handlers throw — the framework catches, classifies, and formats.
178
178
 
179
- **Default for new tools: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. The `recovery` field is required descriptive metadata (≥ 5 words, lint-validated); to surface it on the wire, spread `...ctx.recoveryFor('reason')` into `data` or pass an explicit `{ recovery: { hint: '...' } }` when runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring. Live across the BrAPI surface today: `brapi_connect`, `brapi_dataframe_query`, `brapi_describe_filters`, `brapi_find_genotype_calls`, `brapi_get_germplasm`, `brapi_get_image`, `brapi_get_study`, `brapi_raw_get`, `brapi_raw_search`, `brapi_submit_observations`, plus the `brapi://study/{studyDbId}`, `brapi://germplasm/{germplasmDbId}`, and `brapi://filters/{endpoint}` resources.
179
+ **Default for new tools: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. The `recovery` field is required descriptive metadata (≥ 5 words, lint-validated); to surface it on the wire, spread `...ctx.recoveryFor('reason')` into `data` or pass an explicit `{ recovery: { hint: '...' } }` when runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring. Live across the BrAPI surface today: `brapi_build_phenotype_matrix`, `brapi_dataframe_describe`, `brapi_dataframe_export`, `brapi_dataframe_query`, `brapi_describe_filters`, `brapi_export_genotype_matrix`, `brapi_find_genotype_calls`, `brapi_germplasm_performance`, `brapi_get_germplasm`, `brapi_get_image`, `brapi_get_study`, `brapi_raw_get`, `brapi_raw_search`, `brapi_submit_observations`, plus the `brapi://variable/{observationVariableDbId}` resource.
180
180
 
181
181
  ```ts
182
182
  errors: [
@@ -219,7 +219,7 @@ Available factories include `notFound`, `validationError`, `forbidden`, `unautho
219
219
 
220
220
  ```text
221
221
  src/
222
- index.ts # createApp() entry point — registers 21 tools, 5 resources, 2 prompts; inits 7 services
222
+ index.ts # createApp() entry point — registers 25 tools, 6 resources, 2 prompts; inits 7 services
223
223
  config/
224
224
  server-config.ts # BRAPI_* env vars (Zod schema, lazy-parsed)
225
225
  alias-credentials.ts # Per-alias env-var resolution (BRAPI_<ALIAS>_*) for brapi_connect
@@ -254,6 +254,9 @@ src/
254
254
  brapi-dataframe-query.tool.ts # Run SQL across canvas dataframes (SELECT only); typed columns response
255
255
  brapi-dataframe-drop.tool.ts # Drop a dataframe by name (opt-in via BRAPI_CANVAS_DROP_ENABLED)
256
256
  brapi-dataframe-export.tool.ts # Write CSV/Parquet/JSON to BRAPI_EXPORT_DIR (opt-in, stdio-only)
257
+ brapi-build-phenotype-matrix.tool.ts # Germplasm × trait matrix from studies; materialized as canvas dataframe
258
+ brapi-germplasm-performance.tool.ts # Per-variable aggregates (n, mean, median, sd) for a single germplasm
259
+ brapi-export-genotype-matrix.tool.ts # Genotype calls → germplasm × variant dataframe + VCF-lite / PLINK serialization
257
260
  brapi-submit-observations.tool.ts # Two-phase observation write — preview / apply (POST + PUT) with elicit gate
258
261
  brapi-raw-get.tool.ts # Last-resort GET passthrough with routing nudge
259
262
  brapi-raw-search.tool.ts # Last-resort POST /search passthrough with async polling
@@ -262,6 +265,9 @@ src/
262
265
  orientation-envelope.ts # Shared envelope builder + formatter
263
266
  find-helpers.ts # Alias / loadLimit / extraFilters fragments, mergeFilters, maybeSpill, DataframeHandleSchema
264
267
  raw-routing-hints.ts # Routing nudges emitted by raw_get / raw_search when a curated tool exists
268
+ canvas-columns.ts # SQL-safe column-name sanitizer + variableLegend builder (shared by matrix tools)
269
+ observations.ts # Study-anchored observation pull shared by phenotype-matrix and germplasm-performance
270
+ genotype-calls.ts # Async genotype-call collector shared by find-genotype-calls and export-genotype-matrix
265
271
  resources/
266
272
  definitions/
267
273
  brapi-server-info.resource.ts # brapi://server/info — orientation envelope (default connection)
@@ -269,6 +275,7 @@ src/
269
275
  brapi-study.resource.ts # brapi://study/{studyDbId} — single study with FKs
270
276
  brapi-germplasm.resource.ts # brapi://germplasm/{germplasmDbId} — single germplasm with attributes + parents
271
277
  brapi-filters.resource.ts # brapi://filters/{endpoint} — filter catalog
278
+ brapi-variable.resource.ts # brapi://variable/{observationVariableDbId} — observation variable (trait, scale, method)
272
279
  prompts/
273
280
  definitions/
274
281
  brapi-eda-study.prompt.ts # EDA playbook for one study (orient → variables → coverage → outliers → report)
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  <div align="center">
2
2
  <h1>@cyanheads/brapi-mcp-server</h1>
3
3
  <p><b>A collaborative BrAPI v2.1 workspace for multi-agent research via MCP. Search studies, germplasm, genotypes, & more - across Breedbase, T3, Sweetpotatobase, & any BrAPI v2-compliant server.</b>
4
- <div>22 Tools • 5 Resources • 2 Prompts • Multi-agent collaboration</div>
4
+ <div>25 Tools • 6 Resources • 2 Prompts • Multi-agent collaboration</div>
5
5
  </p>
6
6
  </div>
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![npm](https://img.shields.io/npm/v/@cyanheads/brapi-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/brapi-mcp-server) [![Version](https://img.shields.io/badge/Version-0.6.4-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.11-blueviolet.svg?style=flat-square)](https://bun.sh/) [![Status](https://img.shields.io/badge/Status-Beta-yellow.svg?style=flat-square)](./CHANGELOG.md)
10
+ [![npm](https://img.shields.io/npm/v/@cyanheads/brapi-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/brapi-mcp-server) [![Version](https://img.shields.io/badge/Version-0.7.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.11-blueviolet.svg?style=flat-square)](https://bun.sh/) [![Status](https://img.shields.io/badge/Status-Beta-yellow.svg?style=flat-square)](./CHANGELOG.md)
11
11
 
12
12
  </div>
13
13
 
@@ -23,7 +23,7 @@
23
23
 
24
24
  ## Tools
25
25
 
26
- 22 tools grouped by shape — connection tools bootstrap a session, `find_*` tools return a summarized page plus distributions and spill overflow rows into a canvas dataframe that agents on the same session can query or hand off by ID, `get_*` tools fetch a single record with companion counts, plus pedigree walking, an embedded SQL workspace over spilled rows (DuckDB-backed), file export for human handoff, an additive write surface for observations, and raw passthrough escape hatches.
26
+ 25 tools grouped by shape — connection tools bootstrap a session, `find_*` tools return a summarized page plus distributions and spill overflow rows into a canvas dataframe that agents on the same session can query or hand off by ID, `get_*` tools fetch a single record with companion counts, plus pedigree walking, an embedded SQL workspace over spilled rows (DuckDB-backed), file export for human handoff, an additive write surface for observations, and raw passthrough escape hatches.
27
27
 
28
28
  ### Orient
29
29
 
@@ -58,6 +58,9 @@
58
58
  | `brapi_dataframe_query` | SELECT SQL across in-memory dataframes (DuckDB-backed). Spilled `find_*` rows auto-register as `df_<uuid>`. Read-only — multi-statement, non-SELECT, file-reads, and exports rejected. Returns typed columns (`{ name, type }[]`). |
59
59
  | `brapi_dataframe_drop` | _Opt-in via `BRAPI_CANVAS_DROP_ENABLED=true`._ Drop a dataframe by name. Idempotent. Dataframes also expire via TTL when left unmanaged. |
60
60
  | `brapi_dataframe_export` | _Opt-in via `BRAPI_EXPORT_DIR=<path>`, stdio-only._ Export a dataframe to disk (CSV / Parquet / JSON) under the configured directory and return the absolute path for the human to open. Optional `columns` projection or `sql` filter materializes a derived table for the export, dropped after. |
61
+ | `brapi_build_phenotype_matrix` | Build a germplasm × trait matrix from one or more studies and materialize it as a canvas dataframe. Supports wide (pivot) or long shape with configurable per-cell aggregation. |
62
+ | `brapi_germplasm_performance` | Per-variable performance aggregates (n, mean, median, sd, min, max, studyCount) for a single germplasm across all studies where it has observations. |
63
+ | `brapi_export_genotype_matrix` | Export genotype calls for a variant set as a germplasm × variant canvas dataframe; also serializes to VCF-lite or PLINK `.ped`/`.map` text. |
61
64
 
62
65
  ### Write (opt-in: `BRAPI_ENABLE_WRITES=true`)
63
66
 
@@ -87,6 +90,7 @@ URI-addressable mirrors of the curated tool surface for clients that prefer reso
87
90
  | `brapi://study/{studyDbId}` | `brapi_get_study` |
88
91
  | `brapi://germplasm/{germplasmDbId}` | `brapi_get_germplasm` |
89
92
  | `brapi://filters/{endpoint}` | `brapi_describe_filters` |
93
+ | `brapi://variable/{observationVariableDbId}` | Observation variable record (trait, scale, method, ontology) |
90
94
 
91
95
  ---
92
96
 
@@ -129,7 +133,7 @@ Within one (tenant, session), dataframes act as a self-cleaning shared notebook:
129
133
  - **Dialect adaptation** — `spec` / `brapi-test` / `breedbase` / `cassavabase` / `bms` dialects translate v2.1 plural filter keys to the singular form each server family honors, drop filters known to be broken, normalize sparse-shape encodings, and escalate to POST `/search/{noun}` when GET would silently downcast multi-value filters. Detected from `/serverinfo` (server-name / organization-name); pin per-alias via `BRAPI_<ALIAS>_DIALECT`. Verified-vs-inferred mapping counts surface on the orientation envelope so agents see the confidence floor at a glance.
130
134
  - **DuckDB required** — `@duckdb/node-api` is a regular dependency; startup fails closed when the framework canvas is unavailable. Not supported on Cloudflare Workers (no native binary in that runtime).
131
135
  - **Async-search transparency** — `brapi_find_genotype_calls` and `brapi_raw_search` handle the `POST /search/{noun}` → `GET /search/{noun}/{id}` 202-retry pattern automatically.
132
- - **Pedigree DAG walks** — `brapi_walk_pedigree` BFS-traverses ancestry / descendancy with cycle detection (BrAPI only exposes one generation per call).
136
+ - **Pedigree DAG walks** — `brapi_walk_pedigree` BFS-traverses ancestry / descendancy with cycle detection (BrAPI only exposes one generation per call); a 1,000-node safety cap bounds the walk and sets `truncated` when reached.
133
137
  - **Image content** — `brapi_get_image` fetches bytes inline as MCP `type: image` blocks, preferring `/images/{id}/imagecontent` with `imageURL` fallback.
134
138
  - **Free-text variable ranking** — `OntologyResolver` scores variables against a query (PUI / name / synonym / trait-class) so `find_variables text:"..."` returns ranked candidates even without `/ontologies`.
135
139
  - **Auth variants in one schema** — tagged-union covers `none` / `bearer` / `api_key` / `sgn` (session-token exchange) / `oauth2` (client-credentials).
@@ -141,7 +145,7 @@ Built on [`@cyanheads/mcp-ts-core`](https://www.npmjs.com/package/@cyanheads/mcp
141
145
 
142
146
  ## Working with dataframes
143
147
 
144
- When a `find_*` tool's upstream total exceeds `loadLimit`, the full union materializes as a canvas dataframe and the response carries an inline `dataframe` handle (`{ tableName, rowCount, columns, createdAt, expiresAt, … }`). SQL is the paging idiom — use `LIMIT/OFFSET` to walk pages, projection (`SELECT col1, col2`) to trim columns, and aggregation (`COUNT`, `GROUP BY`, `AVG`) to summarize without materializing every row.
148
+ When a `find_*` tool's upstream total exceeds `loadLimit`, the full union materializes as a canvas dataframe and the response carries an inline `dataframe` handle (`{ tableName, rowCount, columns, createdAt, expiresAt, … }`). Upstream column names that aren't SQL-safe identifiers — reserved words like `end`, digit-leading IDs — are sanitized for the dataframe, and a `columnLegend` on the handle maps each renamed column back to its original key. SQL is the paging idiom — use `LIMIT/OFFSET` to walk pages, projection (`SELECT col1, col2`) to trim columns, and aggregation (`COUNT`, `GROUP BY`, `AVG`) to summarize without materializing every row.
145
149
 
146
150
  Dataframe names are session-scoped capability tokens by default — pass `tableName` to any other agent on the same MCP session (or a downstream step in the same workflow) and they query the same workspace by name without re-pulling from the upstream. The `brapi_dataframe_*` tools offer SQL manipulation and more. See [Multi-agent workflows](#multi-agent-workflows) for cross-session / cross-tenant rules.
147
151
 
@@ -167,7 +171,7 @@ Add to your MCP client config — pick one runner:
167
171
  ```json
168
172
  {
169
173
  "mcpServers": {
170
- "brapi": {
174
+ "brapi-mcp-server": {
171
175
  "type": "stdio",
172
176
  "command": "bunx",
173
177
  "args": ["@cyanheads/brapi-mcp-server@latest"],
@@ -0,0 +1,23 @@
1
+ ---
2
+ summary: "phenotype matrix, germplasm performance, genotype export, observation variable resource"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.7.0 — 2026-06-01
8
+
9
+ ## Added
10
+
11
+ - **`brapi_build_phenotype_matrix`** — builds a germplasm × trait observation matrix from one or more studies and materializes it as a canvas dataframe. Supports wide (pivot) and long shape with configurable per-cell aggregation; wide-column names are SQL-safe identifiers with a `variableLegend` map back to display names. Study-anchored pull prefers `GET /observations?studyDbId`, falls back to observation-unit embedded observations then germplasm-anchored observations for servers where the `studyDbId` filter is unsupported. Shared observation-pull machinery with `brapi_germplasm_performance`. ([#8](https://github.com/cyanheads/brapi-mcp-server/issues/8))
12
+ - **`brapi_germplasm_performance`** — per-variable performance aggregates (n, mean, median, sample sd, min, max, studyCount, studyDbIds, seasons) for a single germplasm across all studies where it has observations. Discovers candidate studies via `GET /studies?germplasmDbIds` with a dialect-honor cross-check that warns when a server silently ignores the filter. ([#9](https://github.com/cyanheads/brapi-mcp-server/issues/9))
13
+ - **`brapi_export_genotype_matrix`** — exports genotype calls for a variant set as a germplasm × variant canvas dataframe (SQL-safe column names, `variantColumnLegend` map); also serializes to VCF-lite (`#CHROM POS ID REF ALT` + per-sample genotype columns) and PLINK (`.ped` + `.map`). Reuses `tools/shared/genotype-calls.ts` extracted from `brapi_find_genotype_calls`. ([#12](https://github.com/cyanheads/brapi-mcp-server/issues/12))
14
+ - **`brapi://variable/{observationVariableDbId}`** resource — single observation-variable record (trait, scale, method, ontology) on the default connection via `GET /variables/{id}`. ([#14](https://github.com/cyanheads/brapi-mcp-server/issues/14))
15
+ - **`tools/shared/canvas-columns.ts`** — SQL-safe column-name sanitizer and `variableLegend` / `variantColumnLegend` builder, shared by `brapi_build_phenotype_matrix` and `brapi_export_genotype_matrix`.
16
+ - **`tools/shared/observations.ts`** — study-anchored observation pull shared by `brapi_build_phenotype_matrix` and `brapi_germplasm_performance`.
17
+ - **`tools/shared/genotype-calls.ts`** — async genotype-call collector extracted from `brapi_find_genotype_calls`, shared by `brapi_export_genotype_matrix`.
18
+
19
+ ## Changed
20
+
21
+ - **`brapi_find_genotype_calls`** refactored to consume `tools/shared/genotype-calls.ts`; behavior unchanged.
22
+ - **`@duckdb/node-api`** `^1.5.3-r.2` → `^1.5.3-r.3`
23
+ - **`vitest`** `^4.1.7` → `^4.1.8` (dev)
@@ -0,0 +1,17 @@
1
+ ---
2
+ summary: "fix: find_* dataframe spillover survives reserved-word columns (end, order, etc.) via centralized sanitization; brapi_walk_pedigree now enforces its 1,000-node cap mid-traversal as a hard ceiling"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.7.1 — 2026-06-01
8
+
9
+ ## Added
10
+
11
+ - **`DataframeHandleSchema.columnLegend`** — optional `Record<string, string>` mapping each sanitized SQL column name back to its original upstream key (e.g. `end_` → `end`). Present only when one or more columns were renamed; absent otherwise. Surfaces in `brapi_dataframe_query`/`brapi_dataframe_describe` consumers so agents know which columns to reference in SQL. ([#37](https://github.com/cyanheads/brapi-mcp-server/issues/37))
12
+ - **`brapi_walk_pedigree` truncation warning** — emits a warning when the 1,000-node cap cuts a walk short; `truncated: true` propagates to the result. ([#17](https://github.com/cyanheads/brapi-mcp-server/issues/17))
13
+
14
+ ## Fixed
15
+
16
+ - **`find_*` dataframe spillover** no longer throws `Canvas column name "end" is a reserved SQL keyword` when an upstream row carries a reserved word or invalid identifier shape. Sanitization is centralized in `CanvasBridge.registerDataframe` via `sanitizeRowColumns`, covering germplasm, studies, observations, locations, images, variables, variants, and any future `find_*` tool automatically. ([#37](https://github.com/cyanheads/brapi-mcp-server/issues/37))
17
+ - **`brapi_walk_pedigree` node cap** was enforced only at the start of each depth iteration; a single high-fan-out expansion could overshoot to ~4× the limit before the next check. The cap is now a hard per-registration ceiling enforced inside the expansion loop. ([#17](https://github.com/cyanheads/brapi-mcp-server/issues/17))
package/dist/index.js CHANGED
@@ -46,6 +46,7 @@ import { brapiFiltersResource } from './mcp-server/resources/definitions/brapi-f
46
46
  import { brapiGermplasmResource } from './mcp-server/resources/definitions/brapi-germplasm.resource.js';
47
47
  import { brapiServerInfoResource } from './mcp-server/resources/definitions/brapi-server-info.resource.js';
48
48
  import { brapiStudyResource } from './mcp-server/resources/definitions/brapi-study.resource.js';
49
+ import { brapiVariableResource } from './mcp-server/resources/definitions/brapi-variable.resource.js';
49
50
  import { dropToolDefinitions, exportToolDefinitions, readOnlyToolDefinitions, writeToolDefinitions, } from './mcp-server/tools/definitions/index.js';
50
51
  const serverConfig = getServerConfig();
51
52
  /**
@@ -100,6 +101,7 @@ await createApp({
100
101
  brapiCallsResource,
101
102
  brapiStudyResource,
102
103
  brapiGermplasmResource,
104
+ brapiVariableResource,
103
105
  brapiFiltersResource,
104
106
  ],
105
107
  prompts: [brapiEdaStudy, brapiMetaAnalysis],
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AAEH,0EAA0E;AAC1E,yEAAyE;AACzE,qEAAqE;AACrE,uEAAuE;AACvE,0BAA0B;AAC1B,EAAE;AACF,2EAA2E;AAC3E,uEAAuE;AACvE,0EAA0E;AAC1E,gEAAgE;AAChE,wDAAwD;AACxD,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,QAAQ,CAAC;AAE9C,6EAA6E;AAC7E,wEAAwE;AACxE,4EAA4E;AAC5E,yEAAyE;AACzE,wEAAwE;AACxE,yEAAyE;AACzE,kBAAkB;AAClB,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;AAClE,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACjF,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAC;AAClF,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,4DAA4D,CAAC;AAC3F,OAAO,EAAE,iBAAiB,EAAE,MAAM,gEAAgE,CAAC;AACnG,OAAO,EAAE,kBAAkB,EAAE,MAAM,4DAA4D,CAAC;AAChG,OAAO,EAAE,oBAAoB,EAAE,MAAM,8DAA8D,CAAC;AACpG,OAAO,EAAE,sBAAsB,EAAE,MAAM,gEAAgE,CAAC;AACxG,OAAO,EAAE,uBAAuB,EAAE,MAAM,kEAAkE,CAAC;AAC3G,OAAO,EAAE,kBAAkB,EAAE,MAAM,4DAA4D,CAAC;AAChG,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,yCAAyC,CAAC;AAEjD,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;AAEvC;;;;GAIG;AACH,MAAM,YAAY,GAAG;IACnB,MAAM,EAAE,gEAAgE;IACxE,IAAI,EAAE,iHAAiH;CACxH,CAAC;AAEF,MAAM,cAAc,GAAG;IACrB,MAAM,EAAE,kDAAkD;IAC1D,IAAI,EAAE,gEAAgE;CACvE,CAAC;AAEF;;;;;;;;GAQG;AACH,SAAS,iBAAiB;IACxB,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,OAAO,CAAC;IAChE,IAAI,aAAa,KAAK,OAAO,EAAE,CAAC;QAC9B,OAAO;YACL,MAAM,EAAE,4CAA4C;YACpD,IAAI,EAAE,mHAAmH;SAC1H,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC;QAC5B,OAAO;YACL,MAAM,EAAE,4DAA4D;YACpE,IAAI,EAAE,wFAAwF;SAC/F,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,cAAc,GAAG,iBAAiB,EAAE,CAAC;AAE3C,MAAM,KAAK,GAAG;IACZ,GAAG,uBAAuB;IAC1B,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC/B,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,YAAY,CAAC,CACnE;IACD,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3F,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAChC,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,cAAc,CAAC,CAChE;CACF,CAAC;AAEF,MAAM,SAAS,CAAC;IACd,KAAK;IACL,SAAS,EAAE;QACT,uBAAuB;QACvB,kBAAkB;QAClB,kBAAkB;QAClB,sBAAsB;QACtB,oBAAoB;KACrB;IACD,OAAO,EAAE,CAAC,aAAa,EAAE,iBAAiB,CAAC;IAC3C,+EAA+E;IAC/E,6EAA6E;IAC7E,+EAA+E;IAC/E,OAAO,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE;IAC/B,YAAY,EACV,onBAAonB;IACtnB,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,kBAAkB,CACtB,kLAAkL,EAClL,EAAE,kBAAkB,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,QAAQ,EAAE,CACrE,CAAC;QACJ,CAAC;QACD,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAC5C,eAAe,CAAC,YAAY,CAAC,CAAC;QAC9B,sBAAsB,CAAC,YAAY,CAAC,CAAC;QACrC,wBAAwB,EAAE,CAAC;QAC3B,sBAAsB,CAAC,YAAY,CAAC,CAAC;QACrC,kBAAkB,CAAC,YAAY,CAAC,CAAC;QACjC,oBAAoB,EAAE,CAAC;IACzB,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @fileoverview `brapi://variable/{observationVariableDbId}` — single
3
+ * observation-variable record on the default connection. The single-record,
4
+ * URI-addressable counterpart to `brapi_find_variables` (which returns a list
5
+ * envelope). Fills the resource gap alongside `brapi://study/{id}` and
6
+ * `brapi://germplasm/{id}`.
7
+ *
8
+ * Fetches the canonical `/variables/{observationVariableDbId}` payload directly
9
+ * (trait, scale, method, ontology) and returns it verbatim — mirroring how
10
+ * `brapi_get_germplasm` reads `/germplasm/{id}`. A 404 (or an empty record)
11
+ * surfaces as the typed `variable_not_found` error.
12
+ *
13
+ * @module mcp-server/resources/definitions/brapi-variable.resource
14
+ */
15
+ import { z } from '@cyanheads/mcp-ts-core';
16
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
17
+ export declare const brapiVariableResource: import("@cyanheads/mcp-ts-core").ResourceDefinition<z.ZodObject<{
18
+ observationVariableDbId: z.ZodString;
19
+ }, z.core.$strip>, undefined, readonly [{
20
+ readonly reason: "unknown_alias";
21
+ readonly code: JsonRpcErrorCode.NotFound;
22
+ readonly when: "No default BrAPI connection has been registered";
23
+ readonly recovery: "Call brapi_connect (without an alias, or with alias `default`) before reading this resource.";
24
+ }, {
25
+ readonly reason: "variable_not_found";
26
+ readonly code: JsonRpcErrorCode.NotFound;
27
+ readonly when: "Upstream returned no observation variable record for the requested DbId";
28
+ readonly recovery: "Verify the observationVariableDbId on the target server, or run brapi_find_variables to discover valid IDs.";
29
+ }]>;
30
+ //# sourceMappingURL=brapi-variable.resource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brapi-variable.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/brapi-variable.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAQjE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;GA8EhC,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @fileoverview `brapi://variable/{observationVariableDbId}` — single
3
+ * observation-variable record on the default connection. The single-record,
4
+ * URI-addressable counterpart to `brapi_find_variables` (which returns a list
5
+ * envelope). Fills the resource gap alongside `brapi://study/{id}` and
6
+ * `brapi://germplasm/{id}`.
7
+ *
8
+ * Fetches the canonical `/variables/{observationVariableDbId}` payload directly
9
+ * (trait, scale, method, ontology) and returns it verbatim — mirroring how
10
+ * `brapi_get_germplasm` reads `/germplasm/{id}`. A 404 (or an empty record)
11
+ * surfaces as the typed `variable_not_found` error.
12
+ *
13
+ * @module mcp-server/resources/definitions/brapi-variable.resource
14
+ */
15
+ import { resource, z } from '@cyanheads/mcp-ts-core';
16
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
17
+ import { buildRequestOptions, isUpstreamNotFound, requireRegisteredConnection, } from '../../../mcp-server/tools/shared/find-helpers.js';
18
+ import { getBrapiClient } from '../../../services/brapi-client/index.js';
19
+ export const brapiVariableResource = resource('brapi://variable/{observationVariableDbId}', {
20
+ name: 'brapi-variable',
21
+ title: 'BrAPI observation variable record',
22
+ description: 'Single observation-variable resource on the default BrAPI connection — the canonical /variables/{id} record (trait, scale, method, ontology), addressable by URI. The single-record equivalent of brapi_find_variables.',
23
+ mimeType: 'application/json',
24
+ errors: [
25
+ {
26
+ reason: 'unknown_alias',
27
+ code: JsonRpcErrorCode.NotFound,
28
+ when: 'No default BrAPI connection has been registered',
29
+ recovery: 'Call brapi_connect (without an alias, or with alias `default`) before reading this resource.',
30
+ },
31
+ {
32
+ reason: 'variable_not_found',
33
+ code: JsonRpcErrorCode.NotFound,
34
+ when: 'Upstream returned no observation variable record for the requested DbId',
35
+ recovery: 'Verify the observationVariableDbId on the target server, or run brapi_find_variables to discover valid IDs.',
36
+ },
37
+ ],
38
+ params: z.object({
39
+ observationVariableDbId: z
40
+ .string()
41
+ .min(1)
42
+ .describe('Observation variable identifier on the default connection.'),
43
+ }),
44
+ async handler(params, ctx) {
45
+ const client = getBrapiClient();
46
+ const connection = await requireRegisteredConnection(ctx, undefined);
47
+ const id = encodeURIComponent(params.observationVariableDbId);
48
+ let env;
49
+ try {
50
+ env = await client.get(connection.baseUrl, `/variables/${id}`, ctx, buildRequestOptions(connection, undefined, { singleton: true }));
51
+ }
52
+ catch (err) {
53
+ if (isUpstreamNotFound(err)) {
54
+ throw ctx.fail('variable_not_found', `Observation variable '${params.observationVariableDbId}' not found on ${connection.baseUrl}.`, {
55
+ observationVariableDbId: params.observationVariableDbId,
56
+ baseUrl: connection.baseUrl,
57
+ ...ctx.recoveryFor('variable_not_found'),
58
+ }, { cause: err });
59
+ }
60
+ throw err;
61
+ }
62
+ const variable = env.result;
63
+ if (!variable ||
64
+ typeof variable !== 'object' ||
65
+ !variable.observationVariableDbId) {
66
+ throw ctx.fail('variable_not_found', `Observation variable '${params.observationVariableDbId}' not found on ${connection.baseUrl}.`, {
67
+ observationVariableDbId: params.observationVariableDbId,
68
+ baseUrl: connection.baseUrl,
69
+ ...ctx.recoveryFor('variable_not_found'),
70
+ });
71
+ }
72
+ return variable;
73
+ },
74
+ list: async () => ({
75
+ resources: [],
76
+ }),
77
+ });
78
+ //# sourceMappingURL=brapi-variable.resource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brapi-variable.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/brapi-variable.resource.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @fileoverview `brapi_build_phenotype_matrix` — pull observations across one or
3
+ * more studies and pivot them into a germplasm × trait matrix materialized as a
4
+ * canvas dataframe. Returns a dataframe handle plus a summary of dimensions,
5
+ * aggregate method, and any warnings encountered during the pull.
6
+ *
7
+ * The per-study observation pull (with its `/observations` → `/observationunits`
8
+ * fallback chain) lives in `../shared/observations.ts`, shared with
9
+ * `brapi_germplasm_performance`.
10
+ *
11
+ * Column safety: wide-matrix column names are SQL-safe identifiers derived from
12
+ * `observationVariableDbId` via `../shared/canvas-columns.ts` — BrAPI DbIds are
13
+ * routinely numeric (Breedbase) or collide with reserved SQL words, both of
14
+ * which the canvas identifier gate rejects. A `variableLegend` mapping safe
15
+ * column → display name is returned so callers can resolve columns back.
16
+ *
17
+ * @module mcp-server/tools/definitions/brapi-build-phenotype-matrix.tool
18
+ */
19
+ import { z } from '@cyanheads/mcp-ts-core';
20
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
21
+ export declare const brapiBuildPhenotypeMatrix: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
22
+ alias: z.ZodOptional<z.ZodString>;
23
+ studies: z.ZodArray<z.ZodString>;
24
+ variables: z.ZodOptional<z.ZodArray<z.ZodString>>;
25
+ germplasm: z.ZodOptional<z.ZodArray<z.ZodString>>;
26
+ shape: z.ZodDefault<z.ZodEnum<{
27
+ wide: "wide";
28
+ long: "long";
29
+ }>>;
30
+ aggregate: z.ZodDefault<z.ZodEnum<{
31
+ all: "all";
32
+ mean: "mean";
33
+ median: "median";
34
+ first: "first";
35
+ }>>;
36
+ loadLimit: z.ZodOptional<z.ZodNumber>;
37
+ extraFilters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
38
+ }, z.core.$strip>, z.ZodObject<{
39
+ alias: z.ZodString;
40
+ studies: z.ZodArray<z.ZodString>;
41
+ shape: z.ZodEnum<{
42
+ wide: "wide";
43
+ long: "long";
44
+ }>;
45
+ aggregate: z.ZodEnum<{
46
+ all: "all";
47
+ mean: "mean";
48
+ median: "median";
49
+ first: "first";
50
+ }>;
51
+ observationCount: z.ZodNumber;
52
+ germplasmCount: z.ZodNumber;
53
+ variableCount: z.ZodNumber;
54
+ variableLegend: z.ZodRecord<z.ZodString, z.ZodString>;
55
+ dataframe: z.ZodOptional<z.ZodObject<{
56
+ tableName: z.ZodString;
57
+ rowCount: z.ZodNumber;
58
+ columns: z.ZodArray<z.ZodString>;
59
+ columnLegend: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
60
+ createdAt: z.ZodString;
61
+ expiresAt: z.ZodString;
62
+ truncated: z.ZodOptional<z.ZodBoolean>;
63
+ maxRows: z.ZodOptional<z.ZodNumber>;
64
+ }, z.core.$strip>>;
65
+ warnings: z.ZodArray<z.ZodString>;
66
+ }, z.core.$strip>, readonly [{
67
+ readonly reason: "unknown_alias";
68
+ readonly code: JsonRpcErrorCode.NotFound;
69
+ readonly when: "No connection has been registered under the requested alias";
70
+ readonly recovery: "Run brapi_connect with this alias (or omit `alias` to use the default connection) before calling brapi_build_phenotype_matrix.";
71
+ }, {
72
+ readonly reason: "all_filters_dropped";
73
+ readonly code: JsonRpcErrorCode.ValidationError;
74
+ readonly when: "The active dialect dropped every filter supplied — the call would silently widen to the unfiltered baseline";
75
+ readonly recovery: "Drop unsupported filters or use studies / germplasm / variables to scope the query to supported filter paths on the active dialect.";
76
+ }, {
77
+ readonly reason: "no_observation_path";
78
+ readonly code: JsonRpcErrorCode.ServiceUnavailable;
79
+ readonly when: "Neither /observations nor /observationunits returned data for any requested study after probing both paths";
80
+ readonly recovery: "Verify the studyDbIds exist and the BrAPI server exposes /observations or /observationunits. Use brapi_server_info to inspect the capability list.";
81
+ }], undefined>;
82
+ //# sourceMappingURL=brapi-build-phenotype-matrix.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brapi-build-phenotype-matrix.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/brapi-build-phenotype-matrix.tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAoEjE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAgPpC,CAAC"}