@cyanheads/brapi-mcp-server
MCP serverEverything elseCollaborative BrAPI v2.1 MCP workspace, studies, germplasm, genotypes across Breedbase, T3, more.
Available today. Use it from your connected AI after setup.
Needs your own MCP Auth Mode account. Credentials stay encrypted.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use @cyanheads/brapi-mcp-server
From the project's README
As published by cyanheads/brapi-mcp-server in README.md.
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 |
|---|---|
brapi_connect | Authenticate to a BrAPI v2 server, register the connection under an alias, and return the full orientation envelope in one call. |
brapi_server_info | Re-fetch the orientation envelope for a registered alias, optionally forcing a capability refresh. |
brapi_describe_filters | List valid filter names for a BrAPI endpoint — companion lookup for extraFilters on any find_* tool. |
brapi_find_studies | Find studies by crop, trial type, season, location, or program, with distributions and dataframe spillover. |
brapi_get_study | Fetch a study with program/trial/location resolved and companion counts (observations, units, variables). |
brapi_find_germplasm | Find germplasm by name, synonym, accession, PUI, crop, or free text, with distributions and dataframe spillover. |
brapi_get_germplasm | Fetch a germplasm with attributes, direct parents, and companion counts (studies, parents, descendants). |
brapi_walk_pedigree | BFS-walk ancestry or descendancy as a deduplicated DAG with cycle detection and depth limits. |
brapi_find_variables | Find observation variables by name, trait class, ontology term, or free text, ranked via OntologyResolver. |
brapi_find_observations | Pull observation records by study, germplasm, variable, season, or unit, with dataframe spillover. |
brapi_find_images | Filter image metadata by unit, observation, study, ontology term, or MIME type. Bytes via brapi_get_image. |
brapi_get_image | Fetch image bytes for up to 5 imageDbIds inline as type: image content blocks. |
brapi_find_locations | Find research stations by country, type, abbreviation, or bounding box. |
brapi_find_variants | Find variant records by variant set, reference, or genomic region. |
brapi_find_genotype_calls | Pull genotype calls via async-search polling, bounded by an upstream pull ceiling. |
brapi_dataframe_describe | List dataframes (or describe one) with column schema, row counts, and originating-source provenance. |
brapi_dataframe_query | Run read-only SQL across in-memory dataframes (DuckDB-backed). |
brapi_dataframe_drop | Opt-in. Drop a dataframe by name. Idempotent. |
brapi_dataframe_export | Opt-in, stdio-only. Export a dataframe to disk as CSV, Parquet, or JSON. |
brapi_build_phenotype_matrix | Build a germplasm × trait matrix from one or more studies, materialized as a canvas dataframe. |
brapi_germplasm_performance | Per-variable performance aggregates (n, mean, median, sd, min, max) for a single germplasm across its studies. |
brapi_export_genotype_matrix | Export genotype calls for a variant set as a germplasm × variant matrix, plus VCF-lite / PLINK serialization. |
brapi_submit_observations | Opt-in. Two-phase observation write — preview validates, apply confirms and writes. |
brapi_raw_get | Passthrough to any BrAPI GET /{path} endpoint not covered by a curated tool. |
brapi_raw_search | Passthrough to any POST /search/{noun} endpoint, with async polling handled transparently. |
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 |
|---|---|
brapi://server/info | Orientation envelope for the default connection — mirrors brapi_server_info. |
brapi://calls | Raw capability profile (/serverinfo + /calls) for the default connection. |
brapi://study/{studyDbId} | Single study record with program/trial/location resolved — mirrors brapi_get_study. |
brapi://germplasm/{germplasmDbId} | Single germplasm record with attributes and parents — mirrors brapi_get_germplasm. |
brapi://filters/{endpoint} | Filter catalog for one endpoint — mirrors brapi_describe_filters. |
brapi://variable/{observationVariableDbId} | Single observation-variable record (trait, scale, method, ontology). |
Prompts
| Prompt | Description |
|---|---|
brapi_eda_study | EDA playbook for one study — orient, variables, coverage, missing data, outliers, pedigree, then a structured report. Args: studyDbId, optional alias. |
brapi_meta_analysis | Cross-study meta-analysis for a germplasm × trait combination — resolve trait, discover studies, harmonize scales, summarize within and across studies. Args: germplasmDbIds (CSV), traitName, optional alias. |
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 session- Auth is a tagged union:
none/bearer/api_key/sgn(Breedbase/tokenexchange) /oauth2(client-credentials) - Typed errors:
auth_token_exchange_failed,auth_no_access_token - Returns 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 profile- Typed error:
unknown_alias - Returns the same orientation envelope shape as
brapi_connect
brapi_describe_filters tool
endpointrequired — one ofstudies,germplasm,observations,variables,images,variants,locations- Each entry carries
name,type(string/integer/number/boolean/date/string[]/integer[]),description, and an example value - Typed 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, plusextraFilterspassthrough loadLimitcaps in-context rows; beyond it the full result set materializes as a canvas dataframe (query withbrapi_dataframe_query)distributionscoverprogramName,studyType,seasons,locationName,commonCropName- Typed 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 inline- Companion 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 study - Typed errors:
unknown_alias,study_not_found
brapi_find_germplasm tool
- Filters:
names,germplasmDbIds,germplasmPUIs,accessionNumbers,crops,synonyms,collections,genus,species, plusextraFilters textis 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,countryOfOriginCode- Typed 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 coverage - Typed 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 reached - Traversal stats:
depthReached,rootCount,leafCount,cycleCount,deadEndCount loadLimitbounds 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, plusextraFilters textranks 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 dataframe- Typed errors:
unknown_alias,all_filters_dropped
brapi_find_observations tool
- Filters:
studies,germplasm,variables,observationUnits,observations,seasons,programs,trials,observationLevels,timestampFrom/timestampTo, plusextraFilters loadLimitcaps in-context rows; beyond it the full result set materializes as a canvas dataframedistributionscoverobservationVariableName,studyName,germplasmName,observationLevel,season- Typed errors:
unknown_alias,all_filters_dropped
brapi_find_images tool
- Filters:
images,observationUnits,observations,studies,imageFileNames,mimeTypes,descriptiveOntologyTerms, plusextraFilters - Metadata only — fetch bytes via
brapi_get_image loadLimitcaps in-context rows; beyond it the full result set materializes as a canvas dataframe- Typed errors:
unknown_alias,all_filters_dropped
brapi_get_image tool
- 1–5
imageDbIdsper call - Prefers
/images/{id}/imagecontent; falls back to the metadataimageURL—sourceon each payload names which path served it - Per-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 whole - Typed 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, plusextraFilters - Optional 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 dataframe- Typed errors:
unknown_alias,all_filters_dropped
brapi_find_variants tool
- Filters:
variantSets,variants,references,referenceName+start/end(1-based inclusive/exclusive genomic region), plusextraFilters loadLimitcaps in-context rows; beyond it the full result set materializes as a canvas dataframedistributionscovervariantType,referenceName,variantSetDbId- Typed errors:
unknown_alias,all_filters_dropped
brapi_find_genotype_calls tool
- Requires at least one of
variantSetDbId,variantSetDbIds,germplasmDbIds,callSetDbIds, orvariantDbIds— unfiltered pulls are rejected - Upstream pull bounded by
BRAPI_GENOTYPE_CALLS_MAX_PULL(default 100,000, max 500,000) via the asyncPOST /search/calls→GET /search/calls/{id}pattern loadLimitbounds the inline preview; the full collected set materializes as a dataframe when it exceedsloadLimit- Typed 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 fromregisterAs - 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
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 inline- Typed 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/listotherwise - Idempotent: returns
dropped: false(not an error) for an unknown name - Dataframes 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 unset formatiscsv,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 default- Typed 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 names - Typed 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 entirely- Per-variable aggregates:
n,mean,median,sd(omitted when n < 2 or non-numeric),min/max,studyCount,studyDbIds,seasons - Typed 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 incomplete- Typed 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 writes- Additive only — no observation is ever destroyed
- Requires
BRAPI_ENABLE_WRITES=trueto register; scoped tobrapi:write:observations - Typed 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 covers- Emits a
suggestionwhen a curated tool exists for the same endpoint - Spills 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.pageSize - Typed 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,kindreportssyncorasync- Emits a
suggestionwhen a curated tool covers the same noun - Same spillover behavior as
brapi_raw_get - Typed errors:
unknown_alias,search_endpoint_disabled
brapi://server/info resource
- No parameters — reads the cached capability profile for the
defaultconnection - Typed error:
unknown_alias
brapi://calls resource
- No parameters — raw
/serverinfo+/callsprofile (server identity, crops, supported services) for thedefaultconnection - Typed error:
unknown_alias
brapi://study/{studyDbId} resource
- Same payload as
brapi_get_study, addressed by URI on the default connection - Typed errors:
unknown_alias,study_not_found
brapi://germplasm/{germplasmDbId} resource
- Same payload as
brapi_get_germplasm, addressed by URI on the default connection - Typed 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 endpoint - Typed 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_variables - Typed errors:
unknown_alias,variable_not_found
brapi_eda_study prompt
- Arguments:
studyDbIdrequired;aliasoptional - Six-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 germplasm - Ends 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:
Shortened here. Read the whole README on GitHub.
Advanced
- Delivery
- brapi-mcp-server MCP server → your ahel gateway (mcp.ahel.ai) → every connected AI client.
- Catalog kind
- mcp-server
- Gateway key
io-github-cyanheads-brapi-mcp-server- Source
- github.com/cyanheads/brapi-mcp-server
- Hosted endpoint
https://brapi.caseyjhand.com/mcp