@cyanheads/brapi-mcp-server 0.7.13 → 0.8.0
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.
- package/AGENTS.md +15 -8
- package/CLAUDE.md +15 -8
- package/README.md +206 -240
- package/changelog/0.8.x/0.8.0.md +48 -0
- package/dist/config/alias-credentials.d.ts +28 -7
- package/dist/config/alias-credentials.d.ts.map +1 -1
- package/dist/config/alias-credentials.js +91 -24
- package/dist/config/alias-credentials.js.map +1 -1
- package/dist/config/builtin-aliases.d.ts +11 -6
- package/dist/config/builtin-aliases.d.ts.map +1 -1
- package/dist/config/builtin-aliases.js +23 -37
- package/dist/config/builtin-aliases.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/resources/definitions/brapi-server-info.resource.js +1 -1
- package/dist/mcp-server/resources/definitions/brapi-server-info.resource.js.map +1 -1
- package/dist/mcp-server/tools/definitions/brapi-connect.tool.d.ts +46 -0
- package/dist/mcp-server/tools/definitions/brapi-connect.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/brapi-connect.tool.js +65 -15
- package/dist/mcp-server/tools/definitions/brapi-connect.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/brapi-describe-filters.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/brapi-describe-filters.tool.js +7 -2
- package/dist/mcp-server/tools/definitions/brapi-describe-filters.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/brapi-server-info.tool.d.ts +12 -0
- package/dist/mcp-server/tools/definitions/brapi-server-info.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/brapi-server-info.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/brapi-server-info.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +123 -65
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/shared/find-helpers.d.ts +6 -0
- package/dist/mcp-server/tools/shared/find-helpers.d.ts.map +1 -1
- package/dist/mcp-server/tools/shared/find-helpers.js +17 -5
- package/dist/mcp-server/tools/shared/find-helpers.js.map +1 -1
- package/dist/mcp-server/tools/shared/orientation-envelope.d.ts +26 -0
- package/dist/mcp-server/tools/shared/orientation-envelope.d.ts.map +1 -1
- package/dist/mcp-server/tools/shared/orientation-envelope.js +75 -6
- package/dist/mcp-server/tools/shared/orientation-envelope.js.map +1 -1
- package/dist/services/brapi-client/brapi-client.d.ts.map +1 -1
- package/dist/services/brapi-client/brapi-client.js +29 -26
- package/dist/services/brapi-client/brapi-client.js.map +1 -1
- package/dist/services/capability-registry/capability-registry.d.ts +8 -1
- package/dist/services/capability-registry/capability-registry.d.ts.map +1 -1
- package/dist/services/capability-registry/capability-registry.js +37 -6
- package/dist/services/capability-registry/capability-registry.js.map +1 -1
- package/dist/services/reference-data-cache/reference-data-cache.d.ts.map +1 -1
- package/dist/services/reference-data-cache/reference-data-cache.js +9 -4
- package/dist/services/reference-data-cache/reference-data-cache.js.map +1 -1
- package/dist/services/server-registry/server-registry.d.ts +17 -3
- package/dist/services/server-registry/server-registry.d.ts.map +1 -1
- package/dist/services/server-registry/server-registry.js +30 -4
- package/dist/services/server-registry/server-registry.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +2 -2
- package/server.json +3 -3
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/cyanheads/brapi-mcp-server/pkgs/container/brapi-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/brapi-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/) [](./CHANGELOG.md)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -29,343 +29,295 @@
|
|
|
29
29
|
|
|
30
30
|
## Overview
|
|
31
31
|
|
|
32
|
-
BrAPI
|
|
32
|
+
Plant-breeding data from any BrAPI (Breeding API) v2 server, including Breedbase instances such as Cassavabase and Sweetpotatobase and the Triticeae Toolbox (T3), with several servers connected at once under named aliases. Search studies, germplasm, observations, genotype calls, images, locations, and variants, walk pedigrees, and build phenotype and genotype matrices; results past the per-call cap spill into a DuckDB dataframe workspace that agents in the same session query with SQL. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
|
|
33
33
|
|
|
34
34
|
### Tools
|
|
35
35
|
|
|
36
36
|
| Tool | Description |
|
|
37
37
|
|:---|:---|
|
|
38
|
-
| `brapi_connect` | Authenticate to a BrAPI v2 server, register
|
|
39
|
-
| `brapi_server_info` | Re-fetch the orientation envelope for a registered alias, optionally
|
|
40
|
-
| `brapi_describe_filters` | List valid filter names for
|
|
41
|
-
| `brapi_find_studies` | Find studies by crop, trial type, season, location, or program
|
|
42
|
-
| `brapi_get_study` | Fetch a study with program
|
|
43
|
-
| `brapi_find_germplasm` | Find germplasm by name, synonym, accession, PUI, crop, or free text
|
|
44
|
-
| `brapi_get_germplasm` | Fetch a germplasm with attributes, direct parents, and companion counts
|
|
45
|
-
| `brapi_walk_pedigree` |
|
|
46
|
-
| `brapi_find_variables` | Find observation variables by name, trait class, ontology term,
|
|
47
|
-
| `brapi_find_observations` | Pull observation records by study, germplasm, variable, season, or unit
|
|
48
|
-
| `brapi_find_images` |
|
|
49
|
-
| `brapi_get_image` | Fetch
|
|
50
|
-
| `brapi_find_locations` | Find research stations by country, type, abbreviation, or bounding box
|
|
51
|
-
| `brapi_find_variants` | Find
|
|
52
|
-
| `brapi_find_genotype_calls` | Pull genotype calls
|
|
53
|
-
| `brapi_dataframe_describe` | List dataframes
|
|
54
|
-
| `brapi_dataframe_query` | Run read-only SQL across
|
|
55
|
-
| `brapi_dataframe_drop` | _Opt-in._ Drop a dataframe by name
|
|
56
|
-
| `brapi_dataframe_export` | _Opt-in, stdio-only._ Export a dataframe to disk as CSV, Parquet, or JSON
|
|
57
|
-
| `brapi_build_phenotype_matrix` | Build a germplasm × trait matrix from one or more studies
|
|
58
|
-
| `brapi_germplasm_performance` | Per-variable
|
|
59
|
-
| `brapi_export_genotype_matrix` |
|
|
60
|
-
| `brapi_submit_observations` | _Opt-in._ Two-phase observation write
|
|
61
|
-
| `brapi_raw_get` | Passthrough to any
|
|
62
|
-
| `brapi_raw_search` | Passthrough to any `POST /search/{noun}` endpoint, with async polling handled
|
|
38
|
+
| `brapi_connect` | Authenticate to a BrAPI v2 server, register it under an alias, and return the orientation envelope |
|
|
39
|
+
| `brapi_server_info` | Re-fetch the orientation envelope for a registered alias, optionally refreshing capabilities |
|
|
40
|
+
| `brapi_describe_filters` | List valid filter names for an endpoint, for use in any finder's `extraFilters` |
|
|
41
|
+
| `brapi_find_studies` | Find studies by crop, trial type, season, location, or program |
|
|
42
|
+
| `brapi_get_study` | Fetch a study with program, trial, and location resolved, plus companion counts |
|
|
43
|
+
| `brapi_find_germplasm` | Find germplasm by name, synonym, accession, PUI, crop, or free text |
|
|
44
|
+
| `brapi_get_germplasm` | Fetch a germplasm with attributes, direct parents, and companion counts |
|
|
45
|
+
| `brapi_walk_pedigree` | Walk ancestry or descendancy as a deduplicated DAG with cycle detection |
|
|
46
|
+
| `brapi_find_variables` | Find observation variables by name, trait class, or ontology term, with free-text ranking |
|
|
47
|
+
| `brapi_find_observations` | Pull observation records by study, germplasm, variable, season, or unit |
|
|
48
|
+
| `brapi_find_images` | Find image metadata by unit, observation, study, ontology term, or MIME type |
|
|
49
|
+
| `brapi_get_image` | Fetch up to 5 images inline as image content blocks |
|
|
50
|
+
| `brapi_find_locations` | Find research stations by country, type, abbreviation, or bounding box |
|
|
51
|
+
| `brapi_find_variants` | Find variants by variant set, reference, or genomic region |
|
|
52
|
+
| `brapi_find_genotype_calls` | Pull genotype calls through async search, bounded by a deployment pull ceiling |
|
|
53
|
+
| `brapi_dataframe_describe` | List dataframes, or describe one with columns, row count, and provenance |
|
|
54
|
+
| `brapi_dataframe_query` | Run read-only SQL across dataframes |
|
|
55
|
+
| `brapi_dataframe_drop` | _Opt-in._ Drop a dataframe by name |
|
|
56
|
+
| `brapi_dataframe_export` | _Opt-in, stdio-only._ Export a dataframe to disk as CSV, Parquet, or JSON |
|
|
57
|
+
| `brapi_build_phenotype_matrix` | Build a germplasm × trait matrix from one or more studies as a dataframe |
|
|
58
|
+
| `brapi_germplasm_performance` | Per-variable aggregates (n, mean, median, sd, min, max) for one germplasm across its studies |
|
|
59
|
+
| `brapi_export_genotype_matrix` | Pivot a variant set's calls into a germplasm × variant matrix, with VCF-lite or PLINK text |
|
|
60
|
+
| `brapi_submit_observations` | _Opt-in._ Two-phase observation write: `preview` validates, `apply` confirms and writes |
|
|
61
|
+
| `brapi_raw_get` | Passthrough to any `GET /{path}` endpoint no curated tool covers |
|
|
62
|
+
| `brapi_raw_search` | Passthrough to any `POST /search/{noun}` endpoint, with async polling handled |
|
|
63
63
|
|
|
64
64
|
### Resources
|
|
65
65
|
|
|
66
|
-
URI-addressable mirrors of the curated tool surface for clients that prefer resources. All resources use the default connection — multi-server workflows route through tools.
|
|
67
|
-
|
|
68
66
|
| Resource | Description |
|
|
69
67
|
|:---|:---|
|
|
70
|
-
| `brapi://server/info` | Orientation envelope for the default connection
|
|
71
|
-
| `brapi://calls` | Raw capability profile (`/serverinfo` + `/calls`) for the default connection
|
|
72
|
-
| `brapi://study/{studyDbId}` |
|
|
73
|
-
| `brapi://germplasm/{germplasmDbId}` |
|
|
74
|
-
| `brapi://filters/{endpoint}` | Filter catalog for one endpoint
|
|
75
|
-
| `brapi://variable/{observationVariableDbId}` |
|
|
68
|
+
| `brapi://server/info` | Orientation envelope for the default connection |
|
|
69
|
+
| `brapi://calls` | Raw capability profile (`/serverinfo` + `/calls`) for the default connection |
|
|
70
|
+
| `brapi://study/{studyDbId}` | One study with program, trial, and location resolved |
|
|
71
|
+
| `brapi://germplasm/{germplasmDbId}` | One germplasm with attributes and parents |
|
|
72
|
+
| `brapi://filters/{endpoint}` | Filter catalog for one endpoint |
|
|
73
|
+
| `brapi://variable/{observationVariableDbId}` | One observation variable (trait, scale, method, ontology) |
|
|
74
|
+
|
|
75
|
+
Every resource reads the `default` connection and mirrors a tool; tool-only clients and multi-server workflows use the tools.
|
|
76
76
|
|
|
77
77
|
### Prompts
|
|
78
78
|
|
|
79
79
|
| Prompt | Description |
|
|
80
80
|
|:---|:---|
|
|
81
|
-
| `brapi_eda_study` |
|
|
82
|
-
| `brapi_meta_analysis` | Cross-study meta-analysis for a germplasm × trait combination
|
|
81
|
+
| `brapi_eda_study` | Exploratory-data-analysis playbook for one study, ending in a structured report |
|
|
82
|
+
| `brapi_meta_analysis` | Cross-study meta-analysis playbook for a germplasm × trait combination |
|
|
83
83
|
|
|
84
84
|
## Capability reference
|
|
85
85
|
|
|
86
86
|
### `brapi_connect` <sub>tool</sub>
|
|
87
87
|
|
|
88
|
-
- `baseUrl` and `auth` are optional
|
|
89
|
-
- `
|
|
90
|
-
-
|
|
91
|
-
- Typed errors: `auth_token_exchange_failed`, `auth_no_access_token`
|
|
92
|
-
- Returns the full orientation envelope (identity, capabilities, content counts, attribution) — one call fully orients the agent; re-fetch on demand via `brapi_server_info`
|
|
88
|
+
- `baseUrl` and `auth` are optional: omitted values come from `BRAPI_<ALIAS>_*` env vars, the built-in aliases, then `BRAPI_DEFAULT_*` (see [Per-alias credentials](#per-alias-credentials)); `alias` (default `default`, pattern `^[a-zA-Z0-9_-]+$`) keeps several servers registered at once; `auth.mode` is `none`, `bearer`, `api_key`, `sgn` (Breedbase `/token` exchange), or `oauth2` (client credentials)
|
|
89
|
+
- Returns the orientation envelope: `server` identity, `auth` summary, `capabilities` (`supported`, `notableGaps`), active `dialect`, `content` counts, `attribution` for built-in servers, and `nextToolSuggestions` naming the entry-point finders this server can serve
|
|
90
|
+
- Typed errors: `auth_session_required`, `auth_base_url_mismatch`, `alias_base_url_unset`, `auth_token_exchange_failed`, `auth_no_access_token`, `upstream_unauthorized`, `upstream_forbidden`; a failed connect leaves any earlier registration under the alias intact
|
|
93
91
|
|
|
94
92
|
---
|
|
95
93
|
|
|
96
94
|
### `brapi_server_info` <sub>tool</sub>
|
|
97
95
|
|
|
98
|
-
- `alias` optional
|
|
99
|
-
-
|
|
100
|
-
- Returns the same orientation envelope shape as `brapi_connect`
|
|
96
|
+
- `alias` optional; `forceRefresh` (default `false`) refetches the capability profile instead of reading the cache
|
|
97
|
+
- Returns the same orientation envelope as `brapi_connect`
|
|
101
98
|
|
|
102
99
|
---
|
|
103
100
|
|
|
104
101
|
### `brapi_describe_filters` <sub>tool</sub>
|
|
105
102
|
|
|
106
|
-
- `endpoint`
|
|
107
|
-
- Each
|
|
108
|
-
- Typed error: `unknown_endpoint` (response carries `availableEndpoints` as recovery data)
|
|
109
|
-
- Catalog reflects the BrAPI v2.1 spec; individual servers may implement subsets
|
|
103
|
+
- `endpoint` is one of `studies`, `germplasm`, `observations`, `variables`, `images`, `variants`, `locations`; `unknown_endpoint` carries `availableEndpoints`
|
|
104
|
+
- Each filter has `name`, `type`, `description`, and `example`; the catalog follows the v2.1 spec, and individual servers may honor a subset
|
|
110
105
|
|
|
111
106
|
---
|
|
112
107
|
|
|
113
108
|
### `brapi_find_studies` <sub>tool</sub>
|
|
114
109
|
|
|
115
|
-
- Filters: `crop`, `trialTypes`, `seasons`, `locations`, `programs`, `trials`, `studyNames`, `active
|
|
116
|
-
- `
|
|
117
|
-
- `distributions` cover `programName`, `studyType`, `seasons`, `locationName`, `commonCropName`
|
|
118
|
-
- Typed errors: `unknown_alias`, `all_filters_dropped` (every supplied filter was unsupported by the active dialect)
|
|
119
|
-
- Response enrichment: `totalCount`, `returnedCount`, `appliedFilters`, `refinementHint`, `notice`, `warnings`
|
|
110
|
+
- Filters: `crop`, `trialTypes`, `seasons`, `locations`, `programs`, `trials`, `studyNames`, `active`
|
|
111
|
+
- `distributions` over `programName`, `studyType`, `seasons`, `locationName`, `commonCropName`
|
|
120
112
|
|
|
121
113
|
---
|
|
122
114
|
|
|
123
115
|
### `brapi_get_study` <sub>tool</sub>
|
|
124
116
|
|
|
125
|
-
- `studyDbId` required; resolves `program`, `trial`, and `location`
|
|
126
|
-
- Companion counts
|
|
127
|
-
- Typed errors: `unknown_alias`, `study_not_found`
|
|
117
|
+
- `studyDbId` required; resolves `program`, `trial`, and `location` inline; `study_not_found` when the upstream has no such study
|
|
118
|
+
- Companion counts `observationCount`, `observationUnitCount`, `variableCount`; a count the server can't scope to the study is omitted with a warning, never reported as the server-wide total
|
|
128
119
|
|
|
129
120
|
---
|
|
130
121
|
|
|
131
122
|
### `brapi_find_germplasm` <sub>tool</sub>
|
|
132
123
|
|
|
133
|
-
- Filters: `names`, `germplasmDbIds`, `germplasmPUIs`, `accessionNumbers`, `crops`, `synonyms`, `collections`, `genus`, `species
|
|
134
|
-
- `
|
|
135
|
-
- `loadLimit` caps in-context rows; beyond it the full result set materializes as a canvas dataframe
|
|
136
|
-
- `distributions` cover `commonCropName`, `genus`, `species`, `collection`, `countryOfOriginCode`
|
|
137
|
-
- Typed errors: `unknown_alias`, `all_filters_dropped`
|
|
124
|
+
- Filters: `names`, `germplasmDbIds`, `germplasmPUIs`, `accessionNumbers`, `crops`, `synonyms`, `collections`, `genus`, `species`; `text` is a client-side substring match on name, accession, display name, and synonyms that drops non-matching rows, so pair it with a server-side filter
|
|
125
|
+
- `distributions` over `commonCropName`, `genus`, `species`, `collection`, `countryOfOriginCode`
|
|
138
126
|
|
|
139
127
|
---
|
|
140
128
|
|
|
141
129
|
### `brapi_get_germplasm` <sub>tool</sub>
|
|
142
130
|
|
|
143
|
-
- `germplasmDbId` required; returns attributes
|
|
144
|
-
-
|
|
145
|
-
- Typed errors: `unknown_alias`, `germplasm_not_found`
|
|
131
|
+
- `germplasmDbId` required; returns `attributes` and direct `parents`; `germplasm_not_found` when the upstream has no such germplasm
|
|
132
|
+
- Companion counts `studyCount`, `directParentCount`, `directDescendantCount`
|
|
146
133
|
|
|
147
134
|
---
|
|
148
135
|
|
|
149
136
|
### `brapi_walk_pedigree` <sub>tool</sub>
|
|
150
137
|
|
|
151
|
-
- 1–20 root `germplasmDbIds
|
|
152
|
-
-
|
|
153
|
-
- Traversal stats: `depthReached`, `rootCount`, `leafCount`, `cycleCount`, `deadEndCount`
|
|
154
|
-
- `loadLimit` bounds the inline `nodes`/`edges` preview; beyond it both sets spill to JOINable canvas dataframes (`nodesDataframe`, `edgesDataframe`)
|
|
155
|
-
- Typed error: `unknown_alias`
|
|
138
|
+
- 1–20 root `germplasmDbIds`; `direction` is `ancestors` (default), `descendants`, or `both`; `maxDepth` 1–10 (default 3); the walk stops at 1,000 nodes and sets `truncated`
|
|
139
|
+
- Deduplicated `nodes` and `edges` with `depthReached`, `rootCount`, `leafCount`, `cycleCount`, `deadEndCount`; past `loadLimit`, both sets spill to `nodesDataframe` and `edgesDataframe`
|
|
156
140
|
|
|
157
141
|
---
|
|
158
142
|
|
|
159
143
|
### `brapi_find_variables` <sub>tool</sub>
|
|
160
144
|
|
|
161
|
-
- Filters: `variables`, `variableNames`, `variablePUIs`, `traitClasses`, `ontologies`, `studies`, `methods`, `scales`, `crop
|
|
162
|
-
- `text` ranks the full
|
|
163
|
-
- `
|
|
164
|
-
- `loadLimit` caps in-context rows; beyond it the full result set materializes as a canvas dataframe
|
|
165
|
-
- Typed errors: `unknown_alias`, `all_filters_dropped`
|
|
145
|
+
- Filters: `variables`, `variableNames`, `variablePUIs`, `traitClasses`, `ontologies`, `studies`, `methods`, `scales`, `crop`
|
|
146
|
+
- `text` ranks the full result set and moves matches to the top without dropping the rest; `ontologyCandidates` lists the ranked matches, each with `source` (`puiMatch`, `nameMatch`, `synonymMatch`, `traitClassMatch`)
|
|
147
|
+
- `distributions` over `ontologyDbId`, `traitClass`, `scaleName`
|
|
166
148
|
|
|
167
149
|
---
|
|
168
150
|
|
|
169
151
|
### `brapi_find_observations` <sub>tool</sub>
|
|
170
152
|
|
|
171
|
-
- Filters: `studies`, `germplasm`, `variables`, `observationUnits`, `observations`, `seasons`, `programs`, `trials`, `observationLevels`, `timestampFrom
|
|
172
|
-
- `
|
|
173
|
-
- `distributions` cover `observationVariableName`, `studyName`, `germplasmName`, `observationLevel`, `season`
|
|
174
|
-
- Typed errors: `unknown_alias`, `all_filters_dropped`
|
|
153
|
+
- Filters: `studies`, `germplasm`, `variables`, `observationUnits`, `observations`, `seasons`, `programs`, `trials`, `observationLevels`, `timestampFrom` / `timestampTo`
|
|
154
|
+
- `distributions` over `observationVariableName`, `studyName`, `germplasmName`, `observationLevel`, `season`
|
|
175
155
|
|
|
176
156
|
---
|
|
177
157
|
|
|
178
158
|
### `brapi_find_images` <sub>tool</sub>
|
|
179
159
|
|
|
180
|
-
- Filters: `images`, `observationUnits`, `observations`, `studies`, `imageFileNames`, `mimeTypes`, `descriptiveOntologyTerms
|
|
181
|
-
-
|
|
182
|
-
- `loadLimit` caps in-context rows; beyond it the full result set materializes as a canvas dataframe
|
|
183
|
-
- Typed errors: `unknown_alias`, `all_filters_dropped`
|
|
160
|
+
- Filters: `images`, `observationUnits`, `observations`, `studies`, `imageFileNames`, `mimeTypes`, `descriptiveOntologyTerms`; returns metadata only, with bytes via `brapi_get_image`
|
|
161
|
+
- `distributions` over `mimeType`, `studyName`, `observationUnitName`, `descriptiveOntologyTerms`
|
|
184
162
|
|
|
185
163
|
---
|
|
186
164
|
|
|
187
165
|
### `brapi_get_image` <sub>tool</sub>
|
|
188
166
|
|
|
189
|
-
- 1–5 `imageDbIds` per call
|
|
190
|
-
-
|
|
191
|
-
- Per-image `errors[]` for failed fetches and `warnings[]` for loaded-but-suspect content (e.g. a non-image MIME from the `imageURL` fallback) — a partial batch never fails as a whole
|
|
192
|
-
- Typed errors: `unknown_alias`, `images_unsupported` (server doesn't advertise `/images`)
|
|
167
|
+
- 1–5 `imageDbIds` per call, up to 20 MB each; `images_unsupported` when the server doesn't advertise `/images`
|
|
168
|
+
- Each image's `source` is `imagecontent` or the `imageURL` fallback; failed fetches land in per-image `errors[]` and suspect payloads (a non-image MIME type) in `warnings[]`, so a partial batch still returns
|
|
193
169
|
|
|
194
170
|
---
|
|
195
171
|
|
|
196
172
|
### `brapi_find_locations` <sub>tool</sub>
|
|
197
173
|
|
|
198
|
-
- Filters: `locations`, `locationNames`, `countryCodes` (ISO 3166-1 alpha-3), `countryNames` (
|
|
199
|
-
-
|
|
200
|
-
- `loadLimit` caps in-context rows; beyond it the full result set materializes as a canvas dataframe
|
|
201
|
-
- Typed errors: `unknown_alias`, `all_filters_dropped`
|
|
174
|
+
- Filters: `locations`, `locationNames`, `countryCodes` (ISO 3166-1 alpha-3), `countryNames` (English names resolved to alpha-3), `locationTypes`, `abbreviations`; optional `bbox` needs all four of `minLat`, `maxLat`, `minLon`, `maxLon` and applies after the fetch
|
|
175
|
+
- `distributions` over `countryCode`, `locationType`; `coordinateAxisOrder: "swapped"` reports a server that stores coordinates as `[lat, lon]`
|
|
202
176
|
|
|
203
177
|
---
|
|
204
178
|
|
|
205
179
|
### `brapi_find_variants` <sub>tool</sub>
|
|
206
180
|
|
|
207
|
-
- Filters: `variantSets`, `variants`, `references`, `referenceName` + `start
|
|
208
|
-
- `
|
|
209
|
-
- `distributions` cover `variantType`, `referenceName`, `variantSetDbId`
|
|
210
|
-
- Typed errors: `unknown_alias`, `all_filters_dropped`
|
|
181
|
+
- Filters: `variantSets`, `variants`, `references`, and a genomic region of `referenceName` + `start` (inclusive) / `end` (exclusive), 1-based
|
|
182
|
+
- `distributions` over `variantType`, `referenceName`, `variantSetDbId`
|
|
211
183
|
|
|
212
184
|
---
|
|
213
185
|
|
|
214
186
|
### `brapi_find_genotype_calls` <sub>tool</sub>
|
|
215
187
|
|
|
216
|
-
-
|
|
217
|
-
-
|
|
218
|
-
-
|
|
219
|
-
- Typed errors: `unknown_alias`, `no_filters`, `search_endpoint_disabled` (dialect marks this server's search route as known-dead)
|
|
188
|
+
- Needs at least one of `variantSetDbId`, `variantSetDbIds`, `germplasmDbIds`, `callSetDbIds`, `variantDbIds` (`no_filters` otherwise); optional `callFormat` (`VCF`, `FLAPJACK`, `DARTSEQ`, `JSON`)
|
|
189
|
+
- `distributions` over `callSetName`, `variantName`, `variantSetDbId`, plus the server's `callFormatting`; `search_endpoint_disabled` when the active dialect marks `POST /search/calls` as dead
|
|
190
|
+
- The upstream pull stops at `BRAPI_GENOTYPE_CALLS_MAX_PULL` (default 100,000) and sets `truncated`; `loadLimit` bounds only the inline preview
|
|
220
191
|
|
|
221
192
|
---
|
|
222
193
|
|
|
223
194
|
### `brapi_dataframe_describe` <sub>tool</sub>
|
|
224
195
|
|
|
225
|
-
- `dataframe` optional
|
|
226
|
-
-
|
|
227
|
-
- Typed error: `list_all_disabled_on_shared_http` — listing without a name is refused on a shared HTTP deployment without per-caller auth, since every caller shares one tenant workspace
|
|
196
|
+
- `dataframe` optional: omit to list every dataframe, or name one for columns, row count, and provenance (originating tool, `baseUrl`, query, expiry), which only auto-registered `df_*` tables carry
|
|
197
|
+
- Listing without a name fails with `list_all_disabled_on_shared_http` on an HTTP deployment where every caller shares the `default` tenant
|
|
228
198
|
|
|
229
199
|
---
|
|
230
200
|
|
|
231
201
|
### `brapi_dataframe_query` <sub>tool</sub>
|
|
232
202
|
|
|
233
|
-
- `sql`
|
|
234
|
-
- `
|
|
235
|
-
- `registerAs` (
|
|
236
|
-
- Typed error: `sql_rejected` (carries the granular gate reason on `data.gateReason`)
|
|
237
|
-
- Response enrichment: `truncated`, `shown`, `cap`, `notice`
|
|
203
|
+
- `sql` is a single `SELECT`; writes, DDL, `COPY`, `PRAGMA`, `ATTACH`, file reads, and system-catalog reads fail as `sql_rejected`, with the specific reason in `data.gateReason`
|
|
204
|
+
- Returns `rowCount`, typed `columns`, and `rows` bounded by `preview` (≤1,000), `rowLimit`, and `BRAPI_CANVAS_MAX_ROWS`; `truncated`, `shown`, `cap`, and `notice` disclose the cut
|
|
205
|
+
- `registerAs` (identifier, ≤63 characters) saves the full result as a new dataframe for chaining
|
|
238
206
|
|
|
239
207
|
---
|
|
240
208
|
|
|
241
209
|
### `brapi_dataframe_drop` <sub>tool</sub>
|
|
242
210
|
|
|
243
|
-
-
|
|
244
|
-
-
|
|
245
|
-
- Dataframes also expire via TTL when left unmanaged, so explicit drop is only needed to free workspace memory immediately
|
|
211
|
+
- `dataframe` required; returns `dropped: false`, not an error, for an unknown name
|
|
212
|
+
- Registered only when `BRAPI_CANVAS_DROP_ENABLED=true`
|
|
246
213
|
|
|
247
214
|
---
|
|
248
215
|
|
|
249
216
|
### `brapi_dataframe_export` <sub>tool</sub>
|
|
250
217
|
|
|
251
|
-
-
|
|
252
|
-
- `format` is `csv`, `parquet`, or `json`; optional `columns` (thin projection) or `sql` (full SELECT, mutually exclusive with `columns`) materializes a temporary derived table first
|
|
253
|
-
- `filename` rejects path separators and `..` segments; omit for a timestamp-suffixed default
|
|
218
|
+
- `format` is `csv`, `parquet`, or `json`; optional `columns` or `sql` (mutually exclusive) and `filename` (no path separators or `..`; omit for a timestamp-suffixed default); returns the absolute `path`, `sizeBytes`, and `rowCount`
|
|
254
219
|
- Typed errors: `export_dir_unset`, `dataframe_not_found`, `invalid_filename`, `mutually_exclusive_projection`
|
|
220
|
+
- Registered only over stdio with `BRAPI_EXPORT_DIR` set
|
|
255
221
|
|
|
256
222
|
---
|
|
257
223
|
|
|
258
224
|
### `brapi_build_phenotype_matrix` <sub>tool</sub>
|
|
259
225
|
|
|
260
|
-
- `studies` required (≥1)
|
|
261
|
-
-
|
|
262
|
-
- Wide-matrix column names are SQL-safe identifiers derived from `observationVariableDbId`; `variableLegend` maps them back to display names
|
|
263
|
-
- Typed errors: `unknown_alias`, `all_filters_dropped`, `no_observation_path` (neither `/observations` nor `/observationunits` returned data)
|
|
264
|
-
- Response enrichment: `truncated`, `shown`, `cap`, `notice`
|
|
226
|
+
- `studies` required (≥1), optional `variables` / `germplasm` subsets; `shape` `wide` (default) or `long`; `aggregate` `mean` (default), `median`, `first`, or `all` (always long form); `loadLimit` caps observations per study
|
|
227
|
+
- Returns the matrix as a dataframe plus `observationCount`, `germplasmCount`, `variableCount`, and `variableLegend` mapping SQL-safe column names to variable names; `truncated` / `cap` flag a study that hit `loadLimit`; `no_observation_path` when neither `/observations` nor `/observationunits` returns data
|
|
265
228
|
|
|
266
229
|
---
|
|
267
230
|
|
|
268
231
|
### `brapi_germplasm_performance` <sub>tool</sub>
|
|
269
232
|
|
|
270
|
-
- `germplasmDbId` required; discovers
|
|
271
|
-
-
|
|
272
|
-
- Typed errors: `unknown_alias`, `germplasm_not_found`
|
|
233
|
+
- `germplasmDbId` required; discovers its studies (up to 200) unless `studyDbIds` is supplied; optional `variables` subset; `germplasm_not_found` when the upstream has no such germplasm
|
|
234
|
+
- `perVariable` rows carry `n`, `mean`, `median`, `sd` (omitted when n < 2 or non-numeric), `min`, `max`, `studyCount`, `studyDbIds`, and `seasons`
|
|
273
235
|
|
|
274
236
|
---
|
|
275
237
|
|
|
276
238
|
### `brapi_export_genotype_matrix` <sub>tool</sub>
|
|
277
239
|
|
|
278
|
-
- `variantSetDbId` required
|
|
279
|
-
- `
|
|
280
|
-
- `
|
|
281
|
-
- Typed errors: `unknown_alias`, `no_filters`, `search_endpoint_disabled`
|
|
240
|
+
- `variantSetDbId` required (`no_filters` otherwise), optional `germplasmDbIds`; `format` is `matrix-json`, `vcf-lite` (adds `vcf` text), or `plink` (adds `ped` / `map` text), and every format registers the germplasm × variant dataframe
|
|
241
|
+
- `variantColumnLegend` maps SQL-safe column names back to variant IDs; `search_endpoint_disabled` when the active dialect marks `POST /search/calls` as dead
|
|
242
|
+
- `maxCalls` / `maxColumns` can lower `BRAPI_GENOTYPE_CALLS_MAX_PULL` / `BRAPI_GENOTYPE_MATRIX_MAX_COLUMNS` but never raise them; `truncated` means a ceiling fired, and `warnings` names which
|
|
282
243
|
|
|
283
244
|
---
|
|
284
245
|
|
|
285
246
|
### `brapi_submit_observations` <sub>tool</sub>
|
|
286
247
|
|
|
287
|
-
- `studyDbId`
|
|
288
|
-
- `mode: "preview"` (default)
|
|
289
|
-
- `
|
|
290
|
-
- Additive only — no observation is ever destroyed
|
|
291
|
-
- Requires `BRAPI_ENABLE_WRITES=true` to register; scoped to `brapi:write:observations`
|
|
292
|
-
- Typed errors: `unknown_alias`, `observations_unsupported`, `study_not_found`, `post_unsupported`, `put_unsupported`, `user_declined`
|
|
248
|
+
- `studyDbId` plus 1–5,000 `observations`; a row with `observationDbId` updates via `PUT`, one without creates via `POST`, and nothing is deleted
|
|
249
|
+
- `mode: "preview"` (default) returns `valid`, `invalid`, `routing` counts, and `perRowWarnings` without writing; `mode: "apply"` asks the caller to confirm (`force: true` skips it), writes, and returns `posted`, `updated`, and `studyObservationCount`; failures are `observations_unsupported`, `study_not_found`, `post_unsupported`, `put_unsupported`, and `user_declined`
|
|
250
|
+
- Registered only when `BRAPI_ENABLE_WRITES=true`; requires the `brapi:write:observations` scope
|
|
293
251
|
|
|
294
252
|
---
|
|
295
253
|
|
|
296
254
|
### `brapi_raw_get` <sub>tool</sub>
|
|
297
255
|
|
|
298
|
-
- `path`
|
|
299
|
-
-
|
|
300
|
-
- Spills to a canvas dataframe when the upstream advertises more rows than `loadLimit` and the result is a list shape; skipped when the caller drives paging via `params.page`/`params.pageSize`
|
|
301
|
-
- Typed errors: `unknown_alias`, `cross_origin_path` (a full URL was passed instead of a relative route)
|
|
256
|
+
- `path` is a relative route such as `/samples` (a full URL fails as `cross_origin_path`); optional `params` and `loadLimit`
|
|
257
|
+
- Returns the raw envelope (`url`, `metadata`, `result`) plus a `suggestion` when a curated tool covers the endpoint; list results past `loadLimit` spill to a dataframe unless `params.page` / `params.pageSize` drive paging
|
|
302
258
|
|
|
303
259
|
---
|
|
304
260
|
|
|
305
261
|
### `brapi_raw_search` <sub>tool</sub>
|
|
306
262
|
|
|
307
|
-
- `noun` (e.g. `observations`, `calls`, `germplasm`)
|
|
308
|
-
-
|
|
309
|
-
- Same spillover behavior as `brapi_raw_get`
|
|
310
|
-
- Typed errors: `unknown_alias`, `search_endpoint_disabled`
|
|
263
|
+
- `noun` (e.g. `observations`, `calls`, `germplasm`) and a `body` posted verbatim to `POST /search/{noun}`; async searches are polled to completion
|
|
264
|
+
- Returns `kind` (`sync` or `async`), `searchResultsDbId`, `result`, and a `suggestion`; spills like `brapi_raw_get` unless `body.page` / `body.pageSize` is set; `search_endpoint_disabled` when the active dialect marks the route as dead
|
|
311
265
|
|
|
312
266
|
---
|
|
313
267
|
|
|
314
268
|
### `brapi://server/info` <sub>resource</sub>
|
|
315
269
|
|
|
316
|
-
-
|
|
317
|
-
-
|
|
270
|
+
- Orientation envelope for the `default` connection as `application/json`
|
|
271
|
+
- Same payload as `brapi_server_info` called with no arguments
|
|
318
272
|
|
|
319
273
|
---
|
|
320
274
|
|
|
321
275
|
### `brapi://calls` <sub>resource</sub>
|
|
322
276
|
|
|
323
|
-
-
|
|
324
|
-
-
|
|
277
|
+
- Capability profile for the `default` connection: supported services with their HTTP methods and versions, plus crops
|
|
278
|
+
- Reflects what `/serverinfo` + `/calls` returned at the last load
|
|
325
279
|
|
|
326
280
|
---
|
|
327
281
|
|
|
328
282
|
### `brapi://study/{studyDbId}` <sub>resource</sub>
|
|
329
283
|
|
|
330
|
-
- Same payload as `brapi_get_study
|
|
331
|
-
-
|
|
284
|
+
- Same payload as `brapi_get_study` on the `default` connection
|
|
285
|
+
- `study_not_found` when the upstream has no such study
|
|
332
286
|
|
|
333
287
|
---
|
|
334
288
|
|
|
335
289
|
### `brapi://germplasm/{germplasmDbId}` <sub>resource</sub>
|
|
336
290
|
|
|
337
|
-
- Same payload as `brapi_get_germplasm
|
|
338
|
-
-
|
|
291
|
+
- Same payload as `brapi_get_germplasm` on the `default` connection
|
|
292
|
+
- `germplasm_not_found` when the upstream has no such germplasm
|
|
339
293
|
|
|
340
294
|
---
|
|
341
295
|
|
|
342
296
|
### `brapi://filters/{endpoint}` <sub>resource</sub>
|
|
343
297
|
|
|
344
|
-
- Same payload as `brapi_describe_filters`;
|
|
345
|
-
-
|
|
298
|
+
- Same payload as `brapi_describe_filters`; `unknown_endpoint` for an endpoint outside the catalog
|
|
299
|
+
- Listing `brapi://filters` returns one resource per endpoint
|
|
346
300
|
|
|
347
301
|
---
|
|
348
302
|
|
|
349
303
|
### `brapi://variable/{observationVariableDbId}` <sub>resource</sub>
|
|
350
304
|
|
|
351
|
-
-
|
|
352
|
-
-
|
|
305
|
+
- The `/variables/{id}` record (trait, scale, method, ontology) on the `default` connection
|
|
306
|
+
- `variable_not_found` when the upstream has no such variable
|
|
353
307
|
|
|
354
308
|
---
|
|
355
309
|
|
|
356
310
|
### `brapi_eda_study` <sub>prompt</sub>
|
|
357
311
|
|
|
358
312
|
- Arguments: `studyDbId` required; `alias` optional
|
|
359
|
-
-
|
|
360
|
-
- Ends in a structured markdown report with a recommended-next-steps section
|
|
313
|
+
- Returns one user message: a six-step playbook (orient, variables, coverage, missing data, IQR outliers, optional pedigree walk) ending in a markdown report with recommended next steps
|
|
361
314
|
|
|
362
315
|
---
|
|
363
316
|
|
|
364
317
|
### `brapi_meta_analysis` <sub>prompt</sub>
|
|
365
318
|
|
|
366
|
-
- Arguments: `germplasmDbIds` (comma-separated) and `traitName` required; `alias` optional
|
|
367
|
-
-
|
|
368
|
-
- Ends in a markdown report that cites every dataframe handle or filter map used, for reproducibility
|
|
319
|
+
- Arguments: `germplasmDbIds` (comma-separated) and `traitName` required; `alias` optional, run once per alias for multi-server analyses
|
|
320
|
+
- Returns one user message: a seven-step playbook (resolve the trait, discover studies, build the observation table, harmonize scales, summarize per study and across studies, optional pedigree walk) ending in a report that cites every dataframe handle or filter map used
|
|
369
321
|
|
|
370
322
|
## Features
|
|
371
323
|
|
|
@@ -373,22 +325,22 @@ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): s
|
|
|
373
325
|
|
|
374
326
|
BrAPI-specific:
|
|
375
327
|
|
|
376
|
-
-
|
|
377
|
-
-
|
|
378
|
-
-
|
|
379
|
-
-
|
|
380
|
-
-
|
|
328
|
+
- Shared finder contract: the seven filter finders (studies, germplasm, variables, observations, images, locations, variants) take `alias`, `loadLimit`, and `extraFilters` (keys from `brapi_describe_filters`), and return `results`, `hasMore`, `distributions`, and an optional `dataframe` handle
|
|
329
|
+
- Dataframe spillover: past `loadLimit`, finders page the rest of the result (up to 50 pages and 50,000 rows) into a DuckDB `df_<uuid>` table and return its handle
|
|
330
|
+
- Dialect adapters (`spec`, `brapi-test`, `breedbase`, `cassavabase`, `bms`), detected per connection, translate v2.1 plural filters into the form each server family honors, drop filters it ignores, and switch to `POST /search/{noun}` when a `GET` would narrow a multi-value filter; `BRAPI_<ALIAS>_DIALECT` pins one
|
|
331
|
+
- Several connections at once under named aliases, with three public Breedbase servers built in and credentials resolved per alias from env vars, so they stay out of the LLM context
|
|
332
|
+
- Capability-aware calls: each connection's `/serverinfo` + `/calls` profile is cached and checked before a tool calls an endpoint; a per-connection concurrency cap and exponential-backoff retries cover 429/5xx
|
|
381
333
|
|
|
382
334
|
Agent-friendly output:
|
|
383
335
|
|
|
384
|
-
-
|
|
385
|
-
-
|
|
386
|
-
-
|
|
387
|
-
-
|
|
336
|
+
- Typed failures: every connection-scoped tool and resource fails with `unknown_alias` until `brapi_connect` registers the alias, and a filter finder or `brapi_build_phenotype_matrix` fails with `all_filters_dropped` instead of widening to an unfiltered pull when the dialect drops every filter supplied
|
|
337
|
+
- Query echo on every finder: `totalCount`, `returnedCount`, the exact `appliedFilters` sent upstream, a `refinementHint` on broad results, an empty-result `notice`, and `warnings`
|
|
338
|
+
- Graceful partial failure: `brapi_get_image` returns per-image `errors[]` and `warnings[]` rows instead of failing the batch
|
|
339
|
+
- Discriminated outputs: `brapi_submit_observations` returns a `mode`-discriminated result (`preview` / `apply`), `brapi_raw_search` reports `kind`, and `brapi_get_image` reports each image's `source`
|
|
388
340
|
|
|
389
|
-
|
|
341
|
+
### Working with dataframes
|
|
390
342
|
|
|
391
|
-
When a
|
|
343
|
+
When a finder's upstream total exceeds `loadLimit`, the response carries a `dataframe` handle: `tableName`, `rowCount`, `columns`, `createdAt`, `expiresAt`, plus `truncated`, `maxRows`, and `totalCount` when a cap fired. Columns renamed to pass the SQL identifier check map back to their upstream keys in `columnLegend`.
|
|
392
344
|
|
|
393
345
|
```text
|
|
394
346
|
1. brapi_find_observations { studies: ["s-422"] }
|
|
@@ -399,7 +351,7 @@ When a `find_*` tool's upstream total exceeds `loadLimit`, the full union materi
|
|
|
399
351
|
→ typed columns + bounded rows
|
|
400
352
|
```
|
|
401
353
|
|
|
402
|
-
Dataframe names are capability tokens, not row-level ACLs
|
|
354
|
+
Dataframe names are capability tokens, not row-level ACLs: anyone holding a name in the same session or tenant bucket (see [Deployment shapes](#deployment-shapes)) can read its rows. Provenance lasts `BRAPI_DATASET_TTL_SECONDS` (default 24h); set `BRAPI_CANVAS_DROP_ENABLED=true` to expose `brapi_dataframe_drop` for explicit cleanup.
|
|
403
355
|
|
|
404
356
|
## Getting started
|
|
405
357
|
|
|
@@ -477,12 +429,12 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
|
|
|
477
429
|
# Server listens at http://localhost:3010/mcp
|
|
478
430
|
```
|
|
479
431
|
|
|
480
|
-
No env vars are required
|
|
432
|
+
No env vars are required: the built-in aliases (`bti-cassava`, `bti-sweetpotato`, `bti-breedbase-demo`) connect as-is, and `brapi_connect` accepts any other BrAPI v2 URL at runtime. For servers that need a login, set credentials as env vars so passwords, tokens, and keys stay out of the LLM context (see [Per-alias credentials](#per-alias-credentials)).
|
|
481
433
|
|
|
482
434
|
### Prerequisites
|
|
483
435
|
|
|
484
436
|
- [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
|
|
485
|
-
- [`@duckdb/node-api`](https://www.npmjs.com/package/@duckdb/node-api)
|
|
437
|
+
- [`@duckdb/node-api`](https://www.npmjs.com/package/@duckdb/node-api), installed as a regular dependency, with prebuilt native bindings for macOS, Linux (glibc and musl), and Windows on x64 and arm64. Cloudflare Workers is not supported.
|
|
486
438
|
|
|
487
439
|
### Installation
|
|
488
440
|
|
|
@@ -517,48 +469,54 @@ Every variable is optional.
|
|
|
517
469
|
|
|
518
470
|
| Variable | Description | Default |
|
|
519
471
|
|:---------|:------------|:--------|
|
|
520
|
-
| `BRAPI_DEFAULT_BASE_URL` |
|
|
521
|
-
| `
|
|
522
|
-
| `
|
|
523
|
-
| `
|
|
524
|
-
| `
|
|
525
|
-
| `
|
|
526
|
-
| `BRAPI_PAGE_SIZE` | Upstream `pageSize` used during canvas spillover walks (decoupled from `BRAPI_LOAD_LIMIT`). Dataframe ceiling = `pageSize × 50`. | `1000` |
|
|
472
|
+
| `BRAPI_DEFAULT_BASE_URL` | Base URL for the `default` alias (e.g. `https://test-server.brapi.org/brapi/v2`). | — |
|
|
473
|
+
| `BRAPI_DEFAULT_*` credentials | One credential family for the `default` alias; see [Per-alias credentials](#per-alias-credentials). | — |
|
|
474
|
+
| `BRAPI_DEFAULT_API_KEY_HEADER` | API-key header for the `default` alias, and the fallback header for any `api_key` auth that names none. | `Authorization` |
|
|
475
|
+
| `BRAPI_BUILTIN_ALIASES_DISABLED` | Comma-separated built-in aliases to remove (case-insensitive). | — |
|
|
476
|
+
| `BRAPI_LOAD_LIMIT` | Default inline row cap for finders before spilling to a dataframe. | `1000` |
|
|
477
|
+
| `BRAPI_PAGE_SIZE` | Upstream `pageSize` for spillover page walks. The dataframe ceiling is `pageSize × 50`, capped at 50,000 rows. | `1000` |
|
|
527
478
|
| `BRAPI_MAX_CONCURRENT_REQUESTS` | Per-connection concurrency cap. | `4` |
|
|
528
|
-
| `BRAPI_RETRY_MAX_ATTEMPTS` / `BRAPI_RETRY_BASE_DELAY_MS` |
|
|
479
|
+
| `BRAPI_RETRY_MAX_ATTEMPTS` / `BRAPI_RETRY_BASE_DELAY_MS` | Retries on 429/5xx and the exponential-backoff base delay. | `3` / `500` |
|
|
529
480
|
| `BRAPI_REQUEST_TIMEOUT_MS` | Per-request HTTP timeout. | `30000` |
|
|
530
|
-
| `BRAPI_COMPANION_TIMEOUT_MS` |
|
|
531
|
-
| `BRAPI_SEARCH_POLL_TIMEOUT_MS` / `
|
|
532
|
-
| `BRAPI_DATASET_TTL_SECONDS` |
|
|
533
|
-
| `BRAPI_REFERENCE_CACHE_TTL_SECONDS` | TTL for programs
|
|
534
|
-
| `BRAPI_ALLOW_PRIVATE_IPS` | Allow RFC 1918 / loopback targets. Dev
|
|
535
|
-
| `
|
|
536
|
-
| `
|
|
537
|
-
| `
|
|
538
|
-
| `
|
|
539
|
-
| `
|
|
540
|
-
| `
|
|
541
|
-
| `
|
|
481
|
+
| `BRAPI_COMPANION_TIMEOUT_MS` | Timeout for non-critical enrichment calls (FK lookups, count probes), which also skip retries. | `8000` |
|
|
482
|
+
| `BRAPI_SEARCH_POLL_TIMEOUT_MS` / `BRAPI_SEARCH_POLL_INTERVAL_MS` | Async `/search` polling budget and interval. | `60000` / `1000` |
|
|
483
|
+
| `BRAPI_DATASET_TTL_SECONDS` | Lifetime of dataframe provenance (the handle's `expiresAt`). | `86400` |
|
|
484
|
+
| `BRAPI_REFERENCE_CACHE_TTL_SECONDS` | TTL for cached capability profiles and reference data (programs, trials, locations, crops). | `3600` |
|
|
485
|
+
| `BRAPI_ALLOW_PRIVATE_IPS` | Allow RFC 1918 / loopback targets. Dev only. | `false` |
|
|
486
|
+
| `BRAPI_SESSION_ISOLATION` | Scope connections and the default canvas to the MCP session when one exists; `false` shares them across the tenant. See [Deployment shapes](#deployment-shapes). | `true` |
|
|
487
|
+
| `BRAPI_ENABLE_WRITES` | **Feature flag.** Registers `brapi_submit_observations`. | `false` |
|
|
488
|
+
| `BRAPI_CANVAS_DROP_ENABLED` | **Feature flag.** Registers `brapi_dataframe_drop`. | `false` |
|
|
489
|
+
| `BRAPI_EXPORT_DIR` | **Feature flag.** Output directory for `brapi_dataframe_export`; setting it registers the tool, over stdio only. | — |
|
|
490
|
+
| `BRAPI_CANVAS_MAX_ROWS` / `BRAPI_CANVAS_QUERY_TIMEOUT_MS` | Response row cap and per-query timeout for `brapi_dataframe_query`. | `10000` / `30000` |
|
|
491
|
+
| `BRAPI_GENOTYPE_CALLS_MAX_PULL` | Upstream call ceiling per `brapi_find_genotype_calls` or `brapi_export_genotype_matrix` call. Max `500000`. | `100000` |
|
|
492
|
+
| `BRAPI_GENOTYPE_MATRIX_MAX_COLUMNS` | Variant-column ceiling per `brapi_export_genotype_matrix` matrix. Max `500000`. | `10000` |
|
|
542
493
|
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
|
|
543
|
-
| `MCP_HTTP_PORT` |
|
|
544
|
-
| `MCP_SESSION_MODE` | HTTP session mode
|
|
545
|
-
| `MCP_AUTH_MODE` |
|
|
546
|
-
| `MCP_LOG_LEVEL` | Log level (
|
|
547
|
-
| `STORAGE_PROVIDER_TYPE` | Storage backend
|
|
548
|
-
| `OTEL_ENABLED` | Enable [OpenTelemetry
|
|
494
|
+
| `MCP_HTTP_PORT` | HTTP server port. | `3010` |
|
|
495
|
+
| `MCP_SESSION_MODE` | HTTP session mode. This server requires `stateful` (apply-mode writes confirm over the session, and isolation keys off it); an HTTP start fails if it resolves to `stateless`. | `stateful` |
|
|
496
|
+
| `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth`. | `none` |
|
|
497
|
+
| `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.). | `info` |
|
|
498
|
+
| `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv/r2/d1`. | `in-memory` |
|
|
499
|
+
| `OTEL_ENABLED` | Enable [OpenTelemetry](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry). | `false` |
|
|
549
500
|
|
|
550
|
-
|
|
501
|
+
See [`.env.example`](./.env.example) for the full list of optional overrides.
|
|
551
502
|
|
|
552
503
|
### Per-alias credentials
|
|
553
504
|
|
|
554
|
-
`brapi_connect`
|
|
505
|
+
`brapi_connect` fills `baseUrl` and `auth` from env vars when the agent omits them, in this order:
|
|
506
|
+
|
|
507
|
+
1. **Agent input**, within the pairing rules below.
|
|
508
|
+
2. **Per-alias env vars**: `BRAPI_<ALIAS>_*`, uppercased with hyphens as underscores (`my-server` → `BRAPI_MY_SERVER_*`).
|
|
509
|
+
3. **Built-in aliases**: see [Built-in aliases](#built-in-aliases).
|
|
510
|
+
4. **`BRAPI_DEFAULT_BASE_URL`**, for an alias with no URL or credentials of its own.
|
|
511
|
+
|
|
512
|
+
Env credentials go only to the server configured with them:
|
|
555
513
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
3. **Built-in known-server registry** — see [Built-in aliases](#built-in-aliases).
|
|
559
|
-
4. **Default env vars** — `BRAPI_DEFAULT_*`, only when the alias differs from `default`. Not layered on top of a built-in URL — defaults belong to the default server.
|
|
514
|
+
- An alias's credentials pair with its `BRAPI_<ALIAS>_BASE_URL`, else its enabled built-in URL. A caller `baseUrl` that points elsewhere fails with `auth_base_url_mismatch`. Credentials with no URL of their own, including those left behind by a built-in disabled via `BRAPI_BUILTIN_ALIASES_DISABLED`, fail with `alias_base_url_unset` and are never sent to `BRAPI_DEFAULT_BASE_URL`.
|
|
515
|
+
- `BRAPI_DEFAULT_*` credentials attach only when the resolved URL is `BRAPI_DEFAULT_BASE_URL`; an alias pointed anywhere else connects without auth unless it has credentials of its own. With no `BRAPI_DEFAULT_BASE_URL` set, default credentials fail with `alias_base_url_unset`.
|
|
560
516
|
|
|
561
|
-
|
|
517
|
+
URLs compare after normalizing host case, default ports, and trailing slashes. Caller-supplied `auth` is never mixed with env credentials.
|
|
518
|
+
|
|
519
|
+
Each alias carries one credential family, and the auth mode follows from which fields are set. Mixing families within an alias raises a `ValidationError`.
|
|
562
520
|
|
|
563
521
|
| Vars set | Resolved `mode` |
|
|
564
522
|
|:---------|:----------------|
|
|
@@ -568,13 +526,13 @@ Each alias carries **one** credential family — auth mode is derived from which
|
|
|
568
526
|
| `_OAUTH_CLIENT_ID` + `_OAUTH_CLIENT_SECRET` (+ optional `_OAUTH_TOKEN_URL`) | `oauth2` |
|
|
569
527
|
| _(none set)_ | `none` |
|
|
570
528
|
|
|
571
|
-
|
|
529
|
+
`BRAPI_<ALIAS>_DIALECT` pins the dialect adapter (`spec`, `brapi-test`, `breedbase`, `cassavabase`, `bms`) when detection picks the wrong one; `auto` or unset detects it.
|
|
572
530
|
|
|
573
531
|
```sh
|
|
574
532
|
# .env — attach write credentials to the built-in 'bti-cassava' alias
|
|
575
533
|
BRAPI_BTI_CASSAVA_USERNAME=alice
|
|
576
534
|
BRAPI_BTI_CASSAVA_PASSWORD=...
|
|
577
|
-
# (BASE_URL omitted — built-in registry covers it)
|
|
535
|
+
# (BASE_URL omitted — the built-in registry covers it)
|
|
578
536
|
|
|
579
537
|
# Static API key as alias 'prod'
|
|
580
538
|
BRAPI_PROD_BASE_URL=https://my-brapi.example.com/brapi/v2
|
|
@@ -582,42 +540,50 @@ BRAPI_PROD_API_KEY=...
|
|
|
582
540
|
BRAPI_PROD_API_KEY_HEADER=X-API-Key
|
|
583
541
|
```
|
|
584
542
|
|
|
585
|
-
|
|
543
|
+
The agent then calls `brapi_connect({ alias: 'bti-cassava' })` with no `baseUrl`, no `auth`, and no secrets in the prompt.
|
|
586
544
|
|
|
587
545
|
### Built-in aliases
|
|
588
546
|
|
|
589
|
-
|
|
547
|
+
These public BrAPI v2 endpoints connect with no configuration. Their orientation envelope carries license, citation, and homepage in an `attribution` block ([Creative Commons Attribution](https://creativecommons.org/licenses/by/4.0/)).
|
|
590
548
|
|
|
591
549
|
| Alias | Upstream | Hosted by | Crop | Notes |
|
|
592
550
|
|:------|:---------|:----------|:-----|:------|
|
|
593
551
|
| `bti-cassava` | [cassavabase.org](https://cassavabase.org/) | Boyce Thompson Institute | Cassava | NextGen Cassava |
|
|
594
552
|
| `bti-sweetpotato` | [sweetpotatobase.org](https://sweetpotatobase.org/) | Boyce Thompson Institute | Sweet potato | |
|
|
595
|
-
| `bti-breedbase-demo` | [breedbase.org](https://breedbase.org/) | Boyce Thompson Institute | _Demo_ | Sample data only
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
| `t3-barley` | [barley.triticeaetoolbox.org](https://barley.triticeaetoolbox.org/) | Triticeae Toolbox (T3) | Barley | T-CAP / US Wheat & Barley Scab Initiative. |
|
|
553
|
+
| `bti-breedbase-demo` | [breedbase.org](https://breedbase.org/) | Boyce Thompson Institute | _Demo_ | Sample data only, for onboarding and tests |
|
|
554
|
+
|
|
555
|
+
The registry holds only servers verified for anonymous reads. Servers that require login, including the Triticeae Toolbox (T3) wheat, oat, and barley hosts, connect through `BRAPI_<ALIAS>_BASE_URL` plus credentials (see [`.env.example`](./.env.example)).
|
|
599
556
|
|
|
600
|
-
|
|
557
|
+
`BRAPI_<ALIAS>_BASE_URL` overrides a built-in URL, e.g. to point `bti-sweetpotato` at a staging mirror via `BRAPI_BTI_SWEETPOTATO_BASE_URL`. `BRAPI_<ALIAS>_USERNAME` and friends attach credentials on top of the built-in URL; each Breedbase instance has its own user table, so write access needs a separate account on each. `BRAPI_BUILTIN_ALIASES_DISABLED=bti-cassava,bti-breedbase-demo` removes entries.
|
|
601
558
|
|
|
602
|
-
**Citation:** all
|
|
559
|
+
**Citation:** all three built-ins reference Morales et al. 2022, _"Breedbase: a digital ecosystem for modern plant breeding."_ G3 12(7): jkac078. [doi:10.1093/g3journal/jkac078](https://doi.org/10.1093/g3journal/jkac078).
|
|
603
560
|
|
|
604
561
|
## Running the server
|
|
605
562
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
bun run
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
bun run
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
563
|
+
### Local development
|
|
564
|
+
|
|
565
|
+
- **Build and run the production version**:
|
|
566
|
+
|
|
567
|
+
```sh
|
|
568
|
+
# One-time build
|
|
569
|
+
bun run rebuild
|
|
570
|
+
|
|
571
|
+
# Run the built server
|
|
572
|
+
bun run start # transport from MCP_TRANSPORT_TYPE (stdio default)
|
|
573
|
+
bun run start:stdio
|
|
574
|
+
bun run start:http
|
|
575
|
+
|
|
576
|
+
# Or run from source with hot reload
|
|
577
|
+
bun --watch src/index.ts
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
- **Run checks and tests**:
|
|
581
|
+
|
|
582
|
+
```sh
|
|
583
|
+
bun run devcheck # Lint, format, typecheck, security, changelog sync
|
|
584
|
+
bun run test # Vitest suite
|
|
585
|
+
bun run lint:mcp # Validate MCP definitions
|
|
586
|
+
```
|
|
621
587
|
|
|
622
588
|
### Docker
|
|
623
589
|
|
|
@@ -626,32 +592,32 @@ docker build -t brapi-mcp-server .
|
|
|
626
592
|
docker run --rm -p 3010:3010 brapi-mcp-server
|
|
627
593
|
```
|
|
628
594
|
|
|
629
|
-
|
|
595
|
+
The image defaults to HTTP transport, `stateful` session mode, and logs to `/var/log/brapi-mcp-server`. OpenTelemetry peer dependencies are installed by default; build with `--build-arg OTEL_ENABLED=false` to omit them.
|
|
630
596
|
|
|
631
597
|
### Deployment shapes
|
|
632
598
|
|
|
633
|
-
Two
|
|
599
|
+
Two kinds of state scope by tenant and, by default, by MCP session: **connection state** (registered aliases and exchanged upstream tokens) and **dataframes** (`df_<uuid>` tables, usable by anyone who holds the name within its bucket). Three configurations set where those buckets end:
|
|
634
600
|
|
|
635
601
|
| Shape | Settings | Isolation | Best for |
|
|
636
602
|
|:------|:---------|:----------|:---------|
|
|
637
|
-
| **Per-session (default)** | `MCP_AUTH_MODE=none` + HTTP stateful + `BRAPI_SESSION_ISOLATION=true` | Each MCP session
|
|
638
|
-
| **Per-user credentials** | `MCP_AUTH_MODE=jwt` or `oauth` (+ HTTP stateful) | Each user's JWT `tid` claim
|
|
639
|
-
| **Shared workspace** | `MCP_AUTH_MODE=none` + `BRAPI_SESSION_ISOLATION=false` | All callers
|
|
603
|
+
| **Per-session (default)** | `MCP_AUTH_MODE=none` + HTTP stateful + `BRAPI_SESSION_ISOLATION=true` | Each MCP session gets its own connections and canvas. Requests without a session share one tenant-wide namespace, so `brapi_connect` refuses their caller-supplied `auth` with `auth_session_required`; keyless connections and operator env credentials still work. | Multi-user hosting without SSO |
|
|
604
|
+
| **Per-user credentials** | `MCP_AUTH_MODE=jwt` or `oauth` (+ HTTP stateful) | Each user's JWT `tid` claim is its own tenant; sessions sub-scope inside it when isolation is on. | Multi-user hosting with institutional SSO; the strongest separation |
|
|
605
|
+
| **Shared workspace** | `MCP_AUTH_MODE=none` + `BRAPI_SESSION_ISOLATION=false` | All callers share one tenant's connections and canvas. | One researcher running parallel agents on shared upstream credentials |
|
|
640
606
|
|
|
641
|
-
Stdio is always
|
|
607
|
+
Stdio is always a single session. Clients on MCP protocol revision 2026-07-28 carry no session on any transport, so outside the per-user-credentials shape they land in the shared tenant workspace, where re-registering an alias re-points every such caller's later calls to it.
|
|
642
608
|
|
|
643
|
-
|
|
609
|
+
On HTTP without per-user auth, `brapi_dataframe_describe` won't list dataframes without a name, and `brapi_dataframe_query` rejects system-catalog reads in every shape, so a caller without a known `df_<uuid>` name can't enumerate other callers' tables.
|
|
644
610
|
|
|
645
611
|
## Project structure
|
|
646
612
|
|
|
647
613
|
| Directory | Purpose |
|
|
648
614
|
|:----------|:--------|
|
|
649
|
-
| `src/index.ts` | `createApp()` entry point
|
|
650
|
-
| `src/config` | Server-
|
|
651
|
-
| `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). Twenty-five tools across connection, retrieval, analysis, write, and raw
|
|
615
|
+
| `src/index.ts` | `createApp()` entry point: registers tools (behind their feature flags), resources, and prompts, and inits services. |
|
|
616
|
+
| `src/config` | Server env parsing (Zod), per-alias credential resolution, and the built-in alias registry. |
|
|
617
|
+
| `src/mcp-server/tools` | Tool definitions (`*.tool.ts`) and shared helpers. Twenty-five tools across connection, retrieval, analysis, write, and raw passthrough. |
|
|
652
618
|
| `src/mcp-server/resources` | Resource definitions (`*.resource.ts`). |
|
|
653
619
|
| `src/mcp-server/prompts` | Prompt definitions (`*.prompt.ts`). |
|
|
654
|
-
| `src/services` |
|
|
620
|
+
| `src/services` | BrAPI client, dialect adapters, filter catalog, canvas bridge, capability registry, ISO country resolver, ontology resolver, reference-data cache, server registry. |
|
|
655
621
|
| `tests/` | Unit and integration tests mirroring `src/`. |
|
|
656
622
|
|
|
657
623
|
## Development guide
|
|
@@ -660,7 +626,7 @@ See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rule
|
|
|
660
626
|
|
|
661
627
|
- Handlers throw, framework catches — no `try/catch` in tool logic
|
|
662
628
|
- Use `ctx.log` for logging, `ctx.state` for tenant-scoped storage — no `console`, no direct persistence access
|
|
663
|
-
-
|
|
629
|
+
- Add new tools to the matching group in `src/mcp-server/tools/definitions/index.ts`; `src/index.ts` composes the groups behind their feature flags
|
|
664
630
|
- Wrap upstream calls: validate raw → normalize → return output schema; never fabricate missing fields
|
|
665
631
|
|
|
666
632
|
## Contributing
|