@cyanheads/brapi-mcp-server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@cyanheads/brapi-mcp-serverfind studies on rice drought tolerance"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Tools
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.
Orient
Tool | Description |
| Authenticate, register the connection under an alias, cache the capability profile, and return the orientation envelope inline. One call fully orients the agent. |
| Re-fetch the orientation envelope for a registered alias — identity, auth, capabilities, content counts, attribution, notes. |
| Static BrAPI v2.1 filter catalog for any endpoint — powers |
Retrieve
Tool | Description |
| Find studies by crop / trial type / season / location / program. Distributions + dataframe spillover. |
| Fetch a study with program / trial / location FKs resolved and companion counts (observations, units, variables). |
| Find germplasm by name, synonym, accession, PUI, crop, or free-text. Distributions + dataframe spillover. |
| Fetch a germplasm with attributes, direct parents, and companion counts (studies, parents, descendants). |
| BFS-walk ancestry / descendancy as a deduplicated DAG with cycle detection, depth limits, and traversal stats. |
| Find observation variables by name / class / ontology / free-text; ranked client-side via |
| Pull observation records by study / germplasm / variable / season / unit / timestamp. Dataframe spillover. |
| Filter image metadata by unit / study / ontology / MIME type. Bytes via |
| Fetch image bytes for up to 5 imageDbIds inline as |
| Find research stations by country (ISO alpha-3 code, or English country name resolved client-side) / type / abbreviation, with optional client-side bbox filter. |
| Find variant records by variant set, reference, or genomic region (1-based inclusive / exclusive). |
| Pull genotype calls via async-search polling. Upstream pull bounded by |
Analyze
Tool | Description |
| Start here after a spillover. Lists dataframes (or describes one) with column schema, row counts, and originating-source provenance. |
| SELECT SQL across in-memory dataframes (DuckDB-backed). Spilled |
| Opt-in via |
| Opt-in via |
| 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. |
| Per-variable performance aggregates (n, mean, median, sd, min, max, studyCount) for a single germplasm across all studies where it has observations. |
| Export genotype calls for a variant set as a germplasm × variant canvas dataframe; also serializes to VCF-lite or PLINK |
Write (opt-in: BRAPI_ENABLE_WRITES=true)
Tool | Description |
| Two-phase observation write — |
Escape hatches
Tool | Description |
| Passthrough to any BrAPI |
| Passthrough to any |
Alias discovery. Built-in and operator-configured aliases are appended to the
brapi_connectdescription at server startup, so agents see the inventory ontools/list. Restart after env-var changes to refresh.
Related MCP server: Helix MCP Server
Resources
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.
URI template | Mirrors |
|
|
| Raw capability profile |
|
|
|
|
|
|
| Observation variable record (trait, scale, method, ontology) |
Prompts
Multi-step BrAPI workflow templates — pure user-message generators, no side effects.
Name | Args | Purpose |
|
| EDA playbook for one study — orient, variables, coverage, missing data, outliers, pedigree, structured report. |
|
| Cross-study meta-analysis — trait resolution, study discovery, harmonization, per-germplasm × per-study and across-study summaries. |
Multi-agent workflows
The server has two stateful layers and two scoping axes:
Layer | Default scope | Why |
Connection state (aliases, exchanged tokens) | Tenant + session | Credentials and live tokens. Tenant gates by user ( |
Dataframes ( | Tenant + session | Within one (tenant, session), agents share by |
Within one (tenant, session), dataframes act as a self-cleaning shared notebook: hand the df_<uuid> name between parallel agents on the same MCP session, persist it across a multi-step workflow, query / project / aggregate / join from any position. Address-by-name, time-bounded, scoped to that session.
Default (isolated) shape. Under MCP_AUTH_MODE=none + HTTP stateful (the default), each MCP session carves its own connection state and its own canvas. Two researchers connected to the same host don't see each other's brapi_connect aliases, exchanged SGN/OAuth tokens, or spilled df_<uuid> rows. Stdio always behaves as one session (single-process, no concurrency).
Shared-workspace shape. Set BRAPI_SESSION_ISOLATION=false for cross-session collaboration in one tenant — multiple MCP sessions then share connection state and one default canvas, the way pre-0.5.3 deployments behaved. Useful when planning, analysis, and writeup agents run as separate MCP clients but operate as one researcher on shared upstream credentials.
On privileged data. The df_<uuid> name is a capability token within a canvas — not row-level access control. Anyone holding the name within the same (tenant, session) bucket can read its rows. Under default isolation, that bucket is one MCP session. Under BRAPI_SESSION_ISOLATION=false, the bucket widens to the whole tenant (all callers under auth=none, or one user's sessions under jwt/oauth). Treat dataframe names like authenticated share links — pass within the bucket, not externally. The 24h TTL caps blast radius; the provenance trail (originating tool, baseUrl, query) supports audit. Belt-and-braces: brapi_dataframe_describe requires an explicit dataframe name on shared-trust HTTP (no list-all enumeration), and brapi_dataframe_query rejects system-catalog reads (information_schema, pg_catalog, sqlite_master, duckdb_*) — so a caller without a known df_<uuid> name can't fish through either surface.
BrAPI-specific features
Dataframe spillover —
find_*tools cap in-context rows atloadLimitand materialize larger unions (up to 50k rows / 50 pages) as DuckDB-backeddf_<uuid>canvas dataframes. Discover withbrapi_dataframe_describe, query withbrapi_dataframe_query(SQL paging viaLIMIT/OFFSET, projection, aggregation). Read-only enforcement at the SQL gate; session-scoped by default (tenant-scoped underBRAPI_SESSION_ISOLATION=false) — see Multi-agent workflows.Multi-server session —
ServerRegistrymaps aliases to live BrAPI connections; one session can span Breedbase, T3, and Sweetpotatobase in parallel.Built-in known-server registry —
bti-cassava,bti-sweetpotato,bti-breedbase-demo,t3-wheat,t3-oat,t3-barleyresolve out-of-the-box without env vars; orientation envelope carries CC-BY attribution.Capability-aware calls —
CapabilityRegistrycaches/serverinfoper connection and guards every tool call against unsupported endpoints. Falls back to/callswhen/serverinfois sparse.Dialect adaptation —
spec/brapi-test/breedbase/cassavabase/bmsdialects 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 viaBRAPI_<ALIAS>_DIALECT. Verified-vs-inferred mapping counts surface on the orientation envelope so agents see the confidence floor at a glance.DuckDB required —
@duckdb/node-apiis a regular dependency; startup fails closed when the framework canvas is unavailable. Not supported on Cloudflare Workers (no native binary in that runtime).Async-search transparency —
brapi_find_genotype_callsandbrapi_raw_searchhandle thePOST /search/{noun}→GET /search/{noun}/{id}202-retry pattern automatically.Pedigree DAG walks —
brapi_walk_pedigreeBFS-traverses ancestry / descendancy with cycle detection (BrAPI only exposes one generation per call); a 1,000-node safety cap bounds the walk and setstruncatedwhen reached. Walks larger thanloadLimitspill their node and edge sets to two JOINable canvas dataframes and return a bounded inline preview.Image content —
brapi_get_imagefetches bytes inline as MCPtype: imageblocks, preferring/images/{id}/imagecontentwithimageURLfallback.Free-text variable ranking —
OntologyResolverscores variables against a query (PUI / name / synonym / trait-class) sofind_variables text:"..."returns ranked candidates even without/ontologies.Auth variants in one schema — tagged-union covers
none/bearer/api_key/sgn(session-token exchange) /oauth2(client-credentials).Typed error contracts — every declared failure mode carries a stable
data.reason, an HTTP-stylecode, and arecovery.hintso clients can route deterministically.
Built on @cyanheads/mcp-ts-core — declarative definitions, unified error handling, pluggable auth (none / jwt / oauth), swappable storage, structured logging with optional OTel, STDIO + Streamable HTTP transports.
Working with dataframes
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.
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 for cross-session / cross-tenant rules.
1. brapi_find_observations { studies: ["s-422"] }
→ first-page rows inline + dataframe.tableName = "df_<uuid>" (when totalCount > loadLimit)
2. brapi_dataframe_describe { dataframe: "df_<uuid>" }
→ schema + provenance (originating tool, baseUrl, query, expiry)
3. brapi_dataframe_query { sql: "SELECT germplasmName, value FROM df_<uuid> WHERE observationVariableDbId = 'V1' LIMIT 100" }
→ typed columns + bounded rows
4. brapi_dataframe_query { sql: "SELECT COUNT(*) AS n, AVG(CAST(value AS DOUBLE)) AS mean FROM df_<uuid> WHERE observationVariableDbId = 'V1'" }
→ aggregate without round-tripping all rowsDataframes auto-expire via TTL (BRAPI_DATASET_TTL_SECONDS, default 24h). Set BRAPI_CANVAS_DROP_ENABLED=true to expose brapi_dataframe_drop for explicit cleanup.
Getting started
Add to your MCP client config — pick one runner:
{
"mcpServers": {
"brapi-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/brapi-mcp-server@latest"],
"env": { "MCP_TRANSPORT_TYPE": "stdio", "MCP_LOG_LEVEL": "info" }
}
}
}Swap command/args for npx -y @cyanheads/brapi-mcp-server@latest (no Bun) or docker run -i --rm -e MCP_TRANSPORT_TYPE=stdio ghcr.io/cyanheads/brapi-mcp-server:latest.
For Streamable HTTP:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcpNo env vars are required — the six built-in aliases (bti-cassava, bti-sweetpotato, bti-breedbase-demo, t3-wheat, t3-oat, t3-barley) resolve out-of-the-box, and agents can connect to any other BrAPI v2 URL at runtime via brapi_connect. For credentialed servers, prefer env vars over agent input so passwords / tokens / API keys stay out of the LLM context — see Per-alias credentials.
Prerequisites: Bun v1.3.11+ or Node.js v24+. @duckdb/node-api is a required dependency — supported on Linux/macOS/Windows × x64 plus Linux/macOS arm64 (no Windows arm64; no Cloudflare Workers).
Configuration
Every variable is optional.
Variable | Description | Default |
| Default BrAPI v2 base URL (e.g. | — |
| SGN session-token auth for the default connection. | — |
| OAuth2 client-credentials for the default connection. | — |
| Static API key for the default connection. | header |
| Comma-separated alias names (case-insensitive) to remove from the built-in registry. | — |
| In-context row cap returned by |
|
| Upstream |
|
| Per-connection concurrency cap. |
|
| Retry policy for 429/5xx with exponential backoff. |
|
| Per-request HTTP timeout. |
|
| Tighter timeout for non-critical companion enrichments (FK lookups, count probes). Companions also bypass the retry budget so a slow upstream surfaces as a warning instead of stretching the response. |
|
| Async |
|
| TTL for dataframe provenance metadata persisted alongside spilled rows. |
|
| TTL for programs / trials / locations / crops cache. |
|
| Allow RFC 1918 / loopback targets. Dev-only. |
|
| Opt-in for |
|
| Upstream row ceiling per |
|
| Distinct-variant column ceiling per |
|
| Opt-in for |
|
| Directory for | — |
| Per-query response row cap and wall-clock timeout for |
|
| Transport ( |
|
| Auth mode ( |
|
| When |
|
Per-alias overrides follow the BRAPI_<ALIAS>_* pattern — see .env.example for every override and inline comments.
Per-alias credentials
brapi_connect resolves baseUrl and auth from env vars when the agent omits them — credentials never enter the LLM context. Four layers of precedence:
Explicit agent input — always wins.
Per-alias env vars —
BRAPI_<ALIAS>_*(uppercased, hyphens → underscores:my-server→BRAPI_MY_SERVER_*).Built-in known-server registry — see Built-in aliases.
Default env vars —
BRAPI_DEFAULT_*, only when the alias differs fromdefault. Not layered on top of a built-in URL — defaults belong to the default server.
Each alias carries one credential family — auth mode is derived from which fields are set:
Vars set | Resolved |
|
|
|
|
|
|
|
|
(none set) |
|
Mixing families within an alias raises a ValidationError.
# .env — attach write credentials to the built-in 'bti-cassava' alias
BRAPI_BTI_CASSAVA_USERNAME=alice
BRAPI_BTI_CASSAVA_PASSWORD=...
# (BASE_URL omitted — built-in registry covers it)
# Static API key as alias 'prod'
BRAPI_PROD_BASE_URL=https://my-brapi.example.com/brapi/v2
BRAPI_PROD_API_KEY=...
BRAPI_PROD_API_KEY_HEADER=X-API-KeyThen the agent calls brapi_connect({ alias: 'bti-cassava' }) — no baseUrl, no auth, no secrets in the prompt.
Built-in aliases
The server ships with a curated registry of public BrAPI v2 endpoints. Each resolves out-of-the-box; the orientation envelope surfaces license, citation, and homepage in its attribution block under Creative Commons Attribution.
Alias | Upstream | Hosted by | Crop | Notes |
| Boyce Thompson Institute | Cassava | NextGen Cassava | |
| Boyce Thompson Institute | Sweet potato | ||
| Boyce Thompson Institute | Demo | Sample data only — onboarding + tests. | |
| Triticeae Toolbox (T3) | Wheat | Wheat CAP / IWYP. | |
| Triticeae Toolbox (T3) | Oat | Global Oat Genetics Database. | |
| Triticeae Toolbox (T3) | Barley | T-CAP / US Wheat & Barley Scab Initiative. |
Set BRAPI_<ALIAS>_BASE_URL to repoint at a staging mirror or fork (env wins over the built-in URL — hyphens in the alias become underscores in the env var, so t3-wheat → BRAPI_T3_WHEAT_BASE_URL). Set BRAPI_<ALIAS>_USERNAME etc. to attach credentials on top of the built-in URL — each Breedbase instance has its own user table, so write access requires separate registration on each upstream. Use BRAPI_BUILTIN_ALIASES_DISABLED=bti-cassava,t3-wheat to strip specific entries.
Citation: all six built-ins reference Morales et al. 2022, "Breedbase: a digital ecosystem for modern plant breeding." G3 12(7): jkac078. doi:10.1093/g3journal/jkac078.
Running the server
# Hot-reload dev (Bun runs TS directly)
bun --watch src/index.ts
# Production
bun run rebuild
bun run start # transport via MCP_TRANSPORT_TYPE (stdio default)
bun run start:stdio # or pin explicitly
bun run start:http
# Checks
bun run devcheck # lint + format + typecheck + security + changelog sync
bun run test # Vitest
bun run lint:mcp # validate MCP definitionsDocker
docker build -t brapi-mcp-server .
docker run --rm -p 3010:3010 brapi-mcp-serverDefaults to HTTP transport, stateful session mode (engages the mcp-session-id lifecycle — precondition for BRAPI_SESSION_ISOLATION=true; hijack protection requires layering MCP_AUTH_MODE=jwt|oauth on top), logs to /var/log/brapi-mcp-server. OTel peer deps are installed by default — --build-arg OTEL_ENABLED=false to omit.
Deployment shapes
brapi-mcp-server runs in three shapes — pick the one that matches your trust domain. The differentiator is what isolates connection state (registered aliases, cached upstream tokens) and dataframes: nothing, the MCP session, or the auth tenant.
Shape | Settings | Isolation | Best for |
Per-session (default) |
| Each MCP session carves its own connection state and canvas. Concurrent HTTP callers don't see each other's aliases, exchanged tokens, or | Multi-user host without SSO. Default for institutional / public deployment under shared-trust auth. |
Per-user credentials |
| Each user's JWT | Multi-user host with institutional SSO (Shibboleth, Okta, etc.) — strongest separation. |
Shared workspace |
| All callers in one tenant share connection state and one canvas. Possession of a | Solo, lab, or hosting where every caller is one researcher running parallel agents on shared upstream credentials. |
Shape selection guide:
Multi-user public/institutional HTTP, no SSO. Use the per-session default. Each researcher's stateful HTTP session is isolated even though they all resolve to
tenantId='default'.Multi-user with institutional SSO.
MCP_AUTH_MODE=jwt(HS256,MCP_AUTH_SECRET_KEY) oroauth(JWKS,OAUTH_ISSUER_URL+OAUTH_AUDIENCE). Each user'stidclaim carves a tenant — the outer scope.BRAPI_SESSION_ISOLATION=true(default) then sub-scopes inside each tenant for users running parallel sessions, and JWT/OAuth identity binding gives real session-hijack protection on top.One researcher, parallel agents. If multiple agents (planner, analyst, writeup) connect as separate MCP clients but should share one workspace, set
BRAPI_SESSION_ISOLATION=falseand rely on shared trust. This is the shared-workspace shape.Stdio. Always one session; isolation is moot. The flag has no effect.
Belt-and-braces under shared trust. Even with BRAPI_SESSION_ISOLATION=false, brapi_dataframe_describe requires an explicit dataframe name on HTTP (no list-all enumeration), and brapi_dataframe_query rejects system-catalog reads (information_schema, pg_catalog, sqlite_master, duckdb_*). The dataframe name is the capability token; possession proves it.
Development
See CLAUDE.md for full architectural rules. Short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor logging,ctx.statefor storage — noconsole, no direct persistenceRegister new tools in the
toolsarray ofcreateApp()insrc/index.tsWrap upstream calls: validate raw → normalize → return output schema; never fabricate missing fields
git clone https://github.com/cyanheads/brapi-mcp-server.git
cd brapi-mcp-server
bun install
cp .env.example .env # edit if you need credentials
bun run devcheck && bun run testPRs welcome.
License
Apache-2.0 — see LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/cyanheads/brapi-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server