@cyanheads/brapi-mcp-server
Click on "Deploy 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.
Public Hosted Server: https://brapi.caseyjhand.com/mcp
Overview
BrAPI v2.1 (the Breeding API) data from Breedbase, T3, Sweetpotatobase, and any BrAPI v2-compliant server. Search studies, germplasm, observations, genotypes, images, locations, and variants — result sets beyond the per-call cap spill into a DuckDB-backed dataframe workspace that agents on the same session can query with SQL or hand off by name, and connections to multiple upstream servers can be held open in parallel under named aliases. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
Tools
Tool | Description |
| Authenticate to a BrAPI v2 server, register the connection under an alias, and return the full orientation envelope in one call. |
| Re-fetch the orientation envelope for a registered alias, optionally forcing a capability refresh. |
| List valid filter names for a BrAPI endpoint — companion lookup for |
| Find studies by crop, trial type, season, location, or program, with distributions and dataframe spillover. |
| Fetch a study with program/trial/location resolved and companion counts (observations, units, variables). |
| Find germplasm by name, synonym, accession, PUI, crop, or free text, with distributions and dataframe spillover. |
| Fetch a germplasm with attributes, direct parents, and companion counts (studies, parents, descendants). |
| BFS-walk ancestry or descendancy as a deduplicated DAG with cycle detection and depth limits. |
| Find observation variables by name, trait class, ontology term, or free text, ranked via |
| Pull observation records by study, germplasm, variable, season, or unit, with dataframe spillover. |
| Filter image metadata by unit, observation, study, ontology term, or MIME type. Bytes via |
| Fetch image bytes for up to 5 |
| Find research stations by country, type, abbreviation, or bounding box. |
| Find variant records by variant set, reference, or genomic region. |
| Pull genotype calls via async-search polling, bounded by an upstream pull ceiling. |
| List dataframes (or describe one) with column schema, row counts, and originating-source provenance. |
| Run read-only SQL across in-memory dataframes (DuckDB-backed). |
| Opt-in. Drop a dataframe by name. Idempotent. |
| Opt-in, stdio-only. Export a dataframe to disk as CSV, Parquet, or JSON. |
| Build a germplasm × trait matrix from one or more studies, materialized as a canvas dataframe. |
| Per-variable performance aggregates (n, mean, median, sd, min, max) for a single germplasm across its studies. |
| Export genotype calls for a variant set as a germplasm × variant matrix, plus VCF-lite / PLINK serialization. |
| Opt-in. Two-phase observation write — |
| Passthrough to any BrAPI |
| Passthrough to any |
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.
Resource | Description |
| Orientation envelope for the default connection — mirrors |
| Raw capability profile ( |
| Single study record with program/trial/location resolved — mirrors |
| Single germplasm record with attributes and parents — mirrors |
| Filter catalog for one endpoint — mirrors |
| Single observation-variable record (trait, scale, method, ontology). |
Prompts
Prompt | Description |
| EDA playbook for one study — orient, variables, coverage, missing data, outliers, pedigree, then a structured report. Args: |
| Cross-study meta-analysis for a germplasm × trait combination — resolve trait, discover studies, harmonize scales, summarize within and across studies. Args: |
Related MCP server: mcp-gwas-catalog
Capability reference
brapi_connect tool
baseUrlandauthare optional — when omitted, resolved fromBRAPI_<ALIAS>_*env vars, then the built-in registry, thenBRAPI_DEFAULT_*, so credentials never enter the LLM contextalias(defaultdefault, pattern^[a-zA-Z0-9_-]+$) registers multiple concurrent connections in one sessionAuth is a tagged union:
none/bearer/api_key/sgn(Breedbase/tokenexchange) /oauth2(client-credentials)Typed errors:
auth_token_exchange_failed,auth_no_access_tokenReturns the full orientation envelope (identity, capabilities, content counts, attribution) — one call fully orients the agent; re-fetch on demand via
brapi_server_info
brapi_server_info tool
aliasoptional (defaults to the connection registered underdefault);forceRefresh(defaultfalse) bypasses the cached capability profileTyped error:
unknown_aliasReturns the same orientation envelope shape as
brapi_connect
brapi_describe_filters tool
endpointrequired — one ofstudies,germplasm,observations,variables,images,variants,locationsEach entry carries
name,type(string/integer/number/boolean/date/string[]/integer[]),description, and an example valueTyped error:
unknown_endpoint(response carriesavailableEndpointsas recovery data)Catalog reflects the BrAPI v2.1 spec; individual servers may implement subsets
brapi_find_studies tool
Filters:
crop,trialTypes,seasons,locations,programs,trials,studyNames,active, plusextraFilterspassthroughloadLimitcaps in-context rows; beyond it the full result set materializes as a canvas dataframe (query withbrapi_dataframe_query)distributionscoverprogramName,studyType,seasons,locationName,commonCropNameTyped errors:
unknown_alias,all_filters_dropped(every supplied filter was unsupported by the active dialect)Response enrichment:
totalCount,returnedCount,appliedFilters,refinementHint,notice,warnings
brapi_get_study tool
studyDbIdrequired; resolvesprogram,trial, andlocationFKs inlineCompanion counts:
observationCount,observationUnitCount,variableCount— omitted (with a warning) rather than reported as a server-wide total when the upstream can't scope a count to the studyTyped errors:
unknown_alias,study_not_found
brapi_find_germplasm tool
Filters:
names,germplasmDbIds,germplasmPUIs,accessionNumbers,crops,synonyms,collections,genus,species, plusextraFilterstextis a client-side substring match againstgermplasmName,accessionNumber,defaultDisplayName, and registered synonyms — combine with a server-side filter to narrow the upstream pull firstloadLimitcaps in-context rows; beyond it the full result set materializes as a canvas dataframedistributionscovercommonCropName,genus,species,collection,countryOfOriginCodeTyped errors:
unknown_alias,all_filters_dropped
brapi_get_germplasm tool
germplasmDbIdrequired; returns attributes (/germplasm/{id}/attributes) and direct parents (/germplasm/{id}/pedigree)Companions:
studyCount,directParentCount,directDescendantCount(from/germplasm/{id}/progeny) — signals for pedigree depth and observation coverageTyped errors:
unknown_alias,germplasm_not_found
brapi_walk_pedigree tool
1–20 root
germplasmDbIds, walked concurrently;directionisancestors(default),descendants, orboth;maxDepth1–10 (default 3)Deduplicates nodes and breaks cycles; a 1,000-node safety cap sets
truncatedwhen reachedTraversal stats:
depthReached,rootCount,leafCount,cycleCount,deadEndCountloadLimitbounds the inlinenodes/edgespreview; beyond it both sets spill to JOINable canvas dataframes (nodesDataframe,edgesDataframe)Typed error:
unknown_alias
brapi_find_variables tool
Filters:
variables,variableNames,variablePUIs,traitClasses,ontologies,studies,methods,scales,crop, plusextraFilterstextranks the full upstream union viaOntologyResolver(PUI / name / synonym / trait-class match) and fills the in-context window with matches first, unmatched rows for context — unlikebrapi_find_germplasm.text, unmatched rows aren't droppedontologyCandidatesin the response carries the ranked matches with their matchsourceloadLimitcaps in-context rows; beyond it the full result set materializes as a canvas dataframeTyped errors:
unknown_alias,all_filters_dropped
brapi_find_observations tool
Filters:
studies,germplasm,variables,observationUnits,observations,seasons,programs,trials,observationLevels,timestampFrom/timestampTo, plusextraFiltersloadLimitcaps in-context rows; beyond it the full result set materializes as a canvas dataframedistributionscoverobservationVariableName,studyName,germplasmName,observationLevel,seasonTyped errors:
unknown_alias,all_filters_dropped
brapi_find_images tool
Filters:
images,observationUnits,observations,studies,imageFileNames,mimeTypes,descriptiveOntologyTerms, plusextraFiltersMetadata only — fetch bytes via
brapi_get_imageloadLimitcaps in-context rows; beyond it the full result set materializes as a canvas dataframeTyped errors:
unknown_alias,all_filters_dropped
brapi_get_image tool
1–5
imageDbIdsper callPrefers
/images/{id}/imagecontent; falls back to the metadataimageURL—sourceon each payload names which path served itPer-image
errors[]for failed fetches andwarnings[]for loaded-but-suspect content (e.g. a non-image MIME from theimageURLfallback) — a partial batch never fails as a wholeTyped errors:
unknown_alias,images_unsupported(server doesn't advertise/images)
brapi_find_locations tool
Filters:
locations,locationNames,countryCodes(ISO 3166-1 alpha-3),countryNames(free-form English, resolved client-side to alpha-3),locationTypes,abbreviations, plusextraFiltersOptional post-fetch
bbox(minLat/maxLat/minLon/maxLon, all four required to activate); retries once with axes swapped when the spec-correct[lon, lat]reading yields zero matches on a server that stores[lat, lon], and reportscoordinateAxisOrder: "swapped"loadLimitcaps in-context rows; beyond it the full result set materializes as a canvas dataframeTyped errors:
unknown_alias,all_filters_dropped
brapi_find_variants tool
Filters:
variantSets,variants,references,referenceName+start/end(1-based inclusive/exclusive genomic region), plusextraFiltersloadLimitcaps in-context rows; beyond it the full result set materializes as a canvas dataframedistributionscovervariantType,referenceName,variantSetDbIdTyped errors:
unknown_alias,all_filters_dropped
brapi_find_genotype_calls tool
Requires at least one of
variantSetDbId,variantSetDbIds,germplasmDbIds,callSetDbIds, orvariantDbIds— unfiltered pulls are rejectedUpstream pull bounded by
BRAPI_GENOTYPE_CALLS_MAX_PULL(default 100,000, max 500,000) via the asyncPOST /search/calls→GET /search/calls/{id}patternloadLimitbounds the inline preview; the full collected set materializes as a dataframe when it exceedsloadLimitTyped errors:
unknown_alias,no_filters,search_endpoint_disabled(dialect marks this server's search route as known-dead)
brapi_dataframe_describe tool
dataframeoptional — omit to list all, or name one for full detail (columns, row count, provenance)Provenance (originating tool,
baseUrl, query, expiry) is present only for auto-registereddf_*dataframes, not user-derived ones fromregisterAsTyped 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
brapi_dataframe_query tool
sqlmust be a singleSELECT— writes, DDL,COPY,PRAGMA,ATTACH, and file reads are rejected at a three-layer gate (single statement → SELECT only → plan-walk allowlist); system-catalog reads (information_schema,pg_catalog,sqlite_master,duckdb_*) are denied separatelyLIMIT/OFFSETis the paging idiom; projection and aggregation (COUNT,GROUP BY,AVG) summarize without materializing every rowregisterAs(letters/digits/underscore, ≤63 chars) persists the result as a new dataframe;preview(≤1000) androwLimitbound what's returned inlineTyped error:
sql_rejected(carries the granular gate reason ondata.gateReason)Response enrichment:
truncated,shown,cap,notice
brapi_dataframe_drop tool
Opt-in via
BRAPI_CANVAS_DROP_ENABLED=true— omitted fromtools/listotherwiseIdempotent: returns
dropped: false(not an error) for an unknown nameDataframes also expire via TTL when left unmanaged, so explicit drop is only needed to free workspace memory immediately
brapi_dataframe_export tool
Opt-in via
BRAPI_EXPORT_DIR, stdio-only — omitted fromtools/listunder HTTP transport or when unsetformatiscsv,parquet, orjson; optionalcolumns(thin projection) orsql(full SELECT, mutually exclusive withcolumns) materializes a temporary derived table firstfilenamerejects path separators and..segments; omit for a timestamp-suffixed defaultTyped errors:
export_dir_unset,dataframe_not_found,invalid_filename,mutually_exclusive_projection
brapi_build_phenotype_matrix tool
studiesrequired (≥1) — study-anchored to avoid full-table scans; optionalvariables/germplasmsubsetsshape:wide(one row per germplasm, one column per variable) orlong(one row per observation);aggregate:mean(default),median,first, orall(forces long form even whenshape:"wide")Wide-matrix column names are SQL-safe identifiers derived from
observationVariableDbId;variableLegendmaps them back to display namesTyped errors:
unknown_alias,all_filters_dropped,no_observation_path(neither/observationsnor/observationunitsreturned data)Response enrichment:
truncated,shown,cap,notice
brapi_germplasm_performance tool
germplasmDbIdrequired; discovers the germplasm's studies automatically (capped at 200) unless an explicitstudyDbIdsset is supplied, which skips discovery entirelyPer-variable aggregates:
n,mean,median,sd(omitted when n < 2 or non-numeric),min/max,studyCount,studyDbIds,seasonsTyped errors:
unknown_alias,germplasm_not_found
brapi_export_genotype_matrix tool
variantSetDbIdrequired;formatismatrix-json(dataframe only),vcf-lite(VCF-subset text invcf, plus dataframe), orplink(.ped/.maptext, plus dataframe)maxCalls/maxColumnscan only lower the deployment ceilings (BRAPI_GENOTYPE_CALLS_MAX_PULL,BRAPI_GENOTYPE_MATRIX_MAX_COLUMNS), never raise themvariantColumnLegendmaps SQL-safe column names back to original variant IDs;truncatednames which ceiling fired when the matrix is incompleteTyped errors:
unknown_alias,no_filters,search_endpoint_disabled
brapi_submit_observations tool
studyDbIdrequired; 1–5,000 observation rows;observationDbIdpresence on a row routes it toPUT, absence toPOSTmode: "preview"(default) validates only and returns a POST/PUT routing breakdown;mode: "apply"asks the caller to confirm via a multi-round-trip input request, then writes and verifies post-state with a cheap count probeforce: trueskips the confirmation round — only for out-of-band-authorized writesAdditive only — no observation is ever destroyed
Requires
BRAPI_ENABLE_WRITES=trueto register; scoped tobrapi:write:observationsTyped errors:
unknown_alias,observations_unsupported,study_not_found,post_unsupported,put_unsupported,user_declined
brapi_raw_get tool
path(relative BrAPI route, e.g./samples) + optionalparams; last-resort escape hatch for endpoints no curated tool coversEmits a
suggestionwhen a curated tool exists for the same endpointSpills to a canvas dataframe when the upstream advertises more rows than
loadLimitand the result is a list shape; skipped when the caller drives paging viaparams.page/params.pageSizeTyped errors:
unknown_alias,cross_origin_path(a full URL was passed instead of a relative route)
brapi_raw_search tool
noun(e.g.observations,calls,germplasm) +bodyposted verbatim toPOST /search/{noun}; async polling resolved transparently,kindreportssyncorasyncEmits a
suggestionwhen a curated tool covers the same nounSame spillover behavior as
brapi_raw_getTyped errors:
unknown_alias,search_endpoint_disabled
brapi://server/info resource
No parameters — reads the cached capability profile for the
defaultconnectionTyped error:
unknown_alias
brapi://calls resource
No parameters — raw
/serverinfo+/callsprofile (server identity, crops, supported services) for thedefaultconnectionTyped error:
unknown_alias
brapi://study/{studyDbId} resource
Same payload as
brapi_get_study, addressed by URI on the default connectionTyped errors:
unknown_alias,study_not_found
brapi://germplasm/{germplasmDbId} resource
Same payload as
brapi_get_germplasm, addressed by URI on the default connectionTyped errors:
unknown_alias,germplasm_not_found
brapi://filters/{endpoint} resource
Same payload as
brapi_describe_filters; listing the resource collection returns one entry per supported endpointTyped error:
unknown_endpoint
brapi://variable/{observationVariableDbId} resource
Canonical
/variables/{id}record (trait, scale, method, ontology) on the default connection — the single-record counterpart tobrapi_find_variablesTyped errors:
unknown_alias,variable_not_found
brapi_eda_study prompt
Arguments:
studyDbIdrequired;aliasoptionalSix-step playbook — orient via
brapi_get_study, enumerate variables, pull observation coverage, quantify missing data, flag numeric outliers (IQR), and an optional pedigree walk on the top-observed germplasmEnds in a structured markdown report with a recommended-next-steps section
brapi_meta_analysis prompt
Arguments:
germplasmDbIds(comma-separated) andtraitNamerequired;aliasoptional (run once per alias for multi-server analyses)Seven-step playbook — resolve the trait to one or more observation variables, discover contributing studies, harmonize units/scales/methods across studies, then per-germplasm × per-study and across-study summary statistics
Ends in a markdown report that cites every dataframe handle or filter map used, for reproducibility
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
BrAPI-specific:
Dataframe spillover —
find_*tools cap in-context rows atloadLimitand materialize larger unions (up to 50,000 rows) as DuckDB-backeddf_<uuid>canvas dataframes, queryable viabrapi_dataframe_queryDialect adaptation — five per-server-family adapters (
spec/brapi-test/breedbase/cassavabase/bms) translate v2.1 plural filter keys to the singular form each family honors, drop known-broken filters, and escalate toPOST /search/{noun}whenGETwould silently downcastMulti-server session with a built-in known-server registry —
ServerRegistryholds live connections under named aliases; six public Breedbase/T3 endpoints resolve out-of-the-box with no env varsCapability-aware, rate-limited calls —
CapabilityRegistrycaches/serverinfoand guards every call against unsupported endpoints; a per-connection concurrency cap and exponential-backoff retry cover 429/5xxTagged-union auth (
none/bearer/api_key/sgnsession-token exchange /oauth2client-credentials), resolved per alias from env vars so credentials never enter the LLM context
Agent-friendly output:
Provenance on every dataframe —
brapi_dataframe_describereports the originating tool,baseUrl, and query for every auto-registereddf_<uuid>tableGraceful partial failure —
brapi_get_imagereturns per-itemerrors[]andwarnings[]rows instead of failing the whole batch when some images can't be loadedDiscriminated output contracts —
brapi_submit_observationsreturns amode-discriminated union (preview/apply);brapi_export_genotype_matrixand the raw-passthrough tools carry typedformat/kindfields callers branch on instead of parsing stringsResponse-shaping guidance —
find_*tools echoappliedFilters, arefinementHintwhen results are broad, and typednotice/warningsso agents can see exactly what was queried and why a response looks the way it does
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 are sanitized, and a columnLegend on the handle maps each renamed column back to its original key.
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 rowsDataframe names are capability tokens, not row-level ACLs — anyone holding the name within the same session or tenant bucket (see Deployment shapes) can read its rows. They 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
Public Hosted Instance
A public instance is available at https://brapi.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"brapi-mcp-server": {
"type": "streamable-http",
"url": "https://brapi.caseyjhand.com/mcp"
}
}
}Self-Hosted / Local
Add the following to your MCP client configuration file.
{
"mcpServers": {
"brapi-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/brapi-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}Or with npx (no Bun required):
{
"mcpServers": {
"brapi-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/brapi-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}Or with Docker:
{
"mcpServers": {
"brapi-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/brapi-mcp-server:latest"]
}
}
}For Streamable HTTP, set the transport and start the server:
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, and API keys stay out of the LLM context — see Per-alias credentials.
Prerequisites
Bun v1.4.0 or higher (or Node.js v24+).
@duckdb/node-apiis a required dependency — supported on Linux/macOS/Windows × x64 plus Linux/macOS arm64 (no Windows arm64, no Cloudflare Workers).
Installation
Clone the repository:
git clone https://github.com/cyanheads/brapi-mcp-server.gitNavigate into the directory:
cd brapi-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# edit .env if you need credentials or non-default settingsConfiguration
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. |
|
| Async |
|
| TTL for dataframe provenance metadata persisted alongside spilled rows. |
|
| TTL for programs / trials / locations / crops cache. |
|
| Allow RFC 1918 / loopback targets. Dev-only. |
|
| Feature flag. Registers |
|
| Upstream row ceiling per |
|
| Distinct-variant column ceiling per |
|
| Feature flag. Registers |
|
| Feature flag. Directory for | — |
| Per-query response row cap and wall-clock timeout for |
|
| When |
|
| Transport: |
|
| Port for HTTP server. |
|
| HTTP session mode: |
|
| Auth mode: |
|
| Log level (RFC 5424). |
|
| Storage backend. |
|
| Enable OpenTelemetry instrumentation. |
|
Per-alias overrides follow the BRAPI_<ALIAS>_* pattern. See .env.example for the full list of optional overrides.
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
Two stateful layers scope by tenant and, by default, by MCP session: connection state (registered aliases, exchanged upstream tokens) and dataframes (df_<uuid> tables — possession of the name grants full read/write/drop within its bucket, auto-expires in 24h by default, provenance recorded). brapi-mcp-server runs in three shapes that pick where those buckets end:
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 — strongest separation. |
Shared workspace |
| All callers in one tenant share connection state and one canvas. | Solo, lab, or hosting where every caller is one researcher running parallel agents on shared upstream credentials. |
Stdio is always one session, so isolation is moot there. Clients on MCP protocol revision 2026-07-28 are session-less by every transport (no ctx.sessionId), so they always land in the shared tenant workspace regardless of BRAPI_SESSION_ISOLATION — only the per-user-credentials shape isolates them.
Belt-and-braces under shared trust: brapi_dataframe_describe requires an explicit dataframe name (no list-all enumeration) and brapi_dataframe_query rejects system-catalog reads, so a caller without a known df_<uuid> name can't fish through either surface even in the shared-workspace shape.
Project structure
Directory | Purpose |
|
|
| Server-specific environment variable parsing and validation with Zod. |
| Tool definitions ( |
| Resource definitions ( |
| Prompt definitions ( |
| Domain service integrations — BrAPI client, dialect adapters, canvas bridge, capability registry, ontology resolver, reference-data cache, server registry. |
| Unit and integration tests mirroring |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor logging,ctx.statefor tenant-scoped storage — noconsole, no direct persistence accessRegister new tools in the
toolsarray ofcreateApp()insrc/index.tsWrap upstream calls: validate raw → normalize → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
Apache-2.0 — see LICENSE for details.
This server cannot be deployed
Maintenance
Related MCP Connectors
Query, browse, and automate OmegaAI workspaces from any MCP client. Streamable HTTP with OAuth 2.0.
Create, browse, remix, collaborate on, and run durable AI workflow nodes from MCP hosts.
AI agent registry — search, discover, register, and connect agents via MCP.
- OctopadOAuthapp.octopad
The back-office workspace for your team's AIs: tasks, knowledge and context shared over MCP.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn MCP server that enables AI agents to explore, search, and query API definitions from OpenAPI/Swagger JSON files.14 npmMIT
- AlicenseNot gradedqualityCmaintenanceMCP server for querying the GWAS Catalog (EBI/NHGRI), a curated catalog of genome-wide association studies. It enables AI agents to search and retrieve study data via natural language or direct tool calls.2 npmMIT
- AlicenseNot gradedqualityBmaintenanceA vendor-neutral MCP server that lets coding agents search and understand OpenAPI/Swagger documents via stdio tools, without calling real backend APIs.24 npm1MIT
- AlicenseNot gradedqualityCmaintenanceEnables MCP-compatible clients to search and query a team knowledge graph, retrieve document context, upload files for indexing, explore entities and connections, and monitor workspace usage and server health.MIT