@cyanheads/brapi-mcp-server

MCP serverEverything else

Collaborative BrAPI v2.1 MCP workspace, studies, germplasm, genotypes across Breedbase, T3, more.

Available today. Use it from your connected AI after setup.

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

ToolDescription
brapi_connectAuthenticate to a BrAPI v2 server, register the connection under an alias, and return the full orientation envelope in one call.
brapi_server_infoRe-fetch the orientation envelope for a registered alias, optionally forcing a capability refresh.
brapi_describe_filtersList valid filter names for a BrAPI endpoint — companion lookup for extraFilters on any find_* tool.
brapi_find_studiesFind studies by crop, trial type, season, location, or program, with distributions and dataframe spillover.
brapi_get_studyFetch a study with program/trial/location resolved and companion counts (observations, units, variables).
brapi_find_germplasmFind germplasm by name, synonym, accession, PUI, crop, or free text, with distributions and dataframe spillover.
brapi_get_germplasmFetch a germplasm with attributes, direct parents, and companion counts (studies, parents, descendants).
brapi_walk_pedigreeBFS-walk ancestry or descendancy as a deduplicated DAG with cycle detection and depth limits.
brapi_find_variablesFind observation variables by name, trait class, ontology term, or free text, ranked via OntologyResolver.
brapi_find_observationsPull observation records by study, germplasm, variable, season, or unit, with dataframe spillover.
brapi_find_imagesFilter image metadata by unit, observation, study, ontology term, or MIME type. Bytes via brapi_get_image.
brapi_get_imageFetch image bytes for up to 5 imageDbIds inline as type: image content blocks.
brapi_find_locationsFind research stations by country, type, abbreviation, or bounding box.
brapi_find_variantsFind variant records by variant set, reference, or genomic region.
brapi_find_genotype_callsPull genotype calls via async-search polling, bounded by an upstream pull ceiling.
brapi_dataframe_describeList dataframes (or describe one) with column schema, row counts, and originating-source provenance.
brapi_dataframe_queryRun read-only SQL across in-memory dataframes (DuckDB-backed).
brapi_dataframe_dropOpt-in. Drop a dataframe by name. Idempotent.
brapi_dataframe_exportOpt-in, stdio-only. Export a dataframe to disk as CSV, Parquet, or JSON.
brapi_build_phenotype_matrixBuild a germplasm × trait matrix from one or more studies, materialized as a canvas dataframe.
brapi_germplasm_performancePer-variable performance aggregates (n, mean, median, sd, min, max) for a single germplasm across its studies.
brapi_export_genotype_matrixExport genotype calls for a variant set as a germplasm × variant matrix, plus VCF-lite / PLINK serialization.
brapi_submit_observationsOpt-in. Two-phase observation write — preview validates, apply confirms and writes.
brapi_raw_getPassthrough to any BrAPI GET /{path} endpoint not covered by a curated tool.
brapi_raw_searchPassthrough 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.

ResourceDescription
brapi://server/infoOrientation envelope for the default connection — mirrors brapi_server_info.
brapi://callsRaw 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

PromptDescription
brapi_eda_studyEDA playbook for one study — orient, variables, coverage, missing data, outliers, pedigree, then a structured report. Args: studyDbId, optional alias.
brapi_meta_analysisCross-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

  • baseUrl and auth are optional — when omitted, resolved from BRAPI_<ALIAS>_* env vars, then the built-in registry, then BRAPI_DEFAULT_*, so credentials never enter the LLM context
  • alias (default default, pattern ^[a-zA-Z0-9_-]+$) registers multiple concurrent connections in one session
  • Auth is a tagged union: none / bearer / api_key / sgn (Breedbase /token exchange) / 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

  • alias optional (defaults to the connection registered under default); forceRefresh (default false) bypasses the cached capability profile
  • Typed error: unknown_alias
  • Returns the same orientation envelope shape as brapi_connect

brapi_describe_filters tool

  • endpoint required — one of studies, 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 carries availableEndpoints as 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, plus extraFilters passthrough
  • loadLimit caps in-context rows; beyond it the full result set materializes as a canvas dataframe (query with brapi_dataframe_query)
  • distributions cover programName, 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

  • studyDbId required; resolves program, trial, and location FKs 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, plus extraFilters
  • text is a client-side substring match against germplasmName, accessionNumber, defaultDisplayName, and registered synonyms — combine with a server-side filter to narrow the upstream pull first
  • loadLimit caps in-context rows; beyond it the full result set materializes as a canvas dataframe
  • distributions cover commonCropName, genus, species, collection, countryOfOriginCode
  • Typed errors: unknown_alias, all_filters_dropped

brapi_get_germplasm tool

  • germplasmDbId required; 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; direction is ancestors (default), descendants, or both; maxDepth 1–10 (default 3)
  • Deduplicates nodes and breaks cycles; a 1,000-node safety cap sets truncated when reached
  • Traversal stats: depthReached, rootCount, leafCount, cycleCount, deadEndCount
  • loadLimit bounds the inline nodes/edges preview; 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, plus extraFilters
  • text ranks the full upstream union via OntologyResolver (PUI / name / synonym / trait-class match) and fills the in-context window with matches first, unmatched rows for context — unlike brapi_find_germplasm.text, unmatched rows aren't dropped
  • ontologyCandidates in the response carries the ranked matches with their match source
  • loadLimit caps 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, plus extraFilters
  • loadLimit caps in-context rows; beyond it the full result set materializes as a canvas dataframe
  • distributions cover observationVariableName, studyName, germplasmName, observationLevel, season
  • Typed errors: unknown_alias, all_filters_dropped

brapi_find_images tool

  • Filters: images, observationUnits, observations, studies, imageFileNames, mimeTypes, descriptiveOntologyTerms, plus extraFilters
  • Metadata only — fetch bytes via brapi_get_image
  • loadLimit caps 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 imageDbIds per call
  • Prefers /images/{id}/imagecontent; falls back to the metadata imageURLsource on each payload names which path served it
  • 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
  • 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, plus extraFilters
  • 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 reports coordinateAxisOrder: "swapped"
  • loadLimit caps 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), plus extraFilters
  • loadLimit caps in-context rows; beyond it the full result set materializes as a canvas dataframe
  • distributions cover variantType, referenceName, variantSetDbId
  • Typed errors: unknown_alias, all_filters_dropped

brapi_find_genotype_calls tool

  • Requires at least one of variantSetDbId, variantSetDbIds, germplasmDbIds, callSetDbIds, or variantDbIds — unfiltered pulls are rejected
  • Upstream pull bounded by BRAPI_GENOTYPE_CALLS_MAX_PULL (default 100,000, max 500,000) via the async POST /search/callsGET /search/calls/{id} pattern
  • loadLimit bounds the inline preview; the full collected set materializes as a dataframe when it exceeds loadLimit
  • Typed errors: unknown_alias, no_filters, search_endpoint_disabled (dialect marks this server's search route as known-dead)

brapi_dataframe_describe tool

  • dataframe optional — 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-registered df_* dataframes, not user-derived ones from registerAs
  • 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

  • sql must be a single SELECT — 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 separately
  • LIMIT/OFFSET is the paging idiom; projection and aggregation (COUNT, GROUP BY, AVG) summarize without materializing every row
  • registerAs (letters/digits/underscore, ≤63 chars) persists the result as a new dataframe; preview (≤1000) and rowLimit bound what's returned inline
  • Typed error: sql_rejected (carries the granular gate reason on data.gateReason)
  • Response enrichment: truncated, shown, cap, notice

brapi_dataframe_drop tool

  • Opt-in via BRAPI_CANVAS_DROP_ENABLED=true — omitted from tools/list otherwise
  • 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 from tools/list under HTTP transport or when unset
  • format is csv, parquet, or json; optional columns (thin projection) or sql (full SELECT, mutually exclusive with columns) materializes a temporary derived table first
  • filename rejects 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

  • studies required (≥1) — study-anchored to avoid full-table scans; optional variables/germplasm subsets
  • shape: wide (one row per germplasm, one column per variable) or long (one row per observation); aggregate: mean (default), median, first, or all (forces long form even when shape:"wide")
  • Wide-matrix column names are SQL-safe identifiers derived from observationVariableDbId; variableLegend maps them back to display names
  • Typed errors: unknown_alias, all_filters_dropped, no_observation_path (neither /observations nor /observationunits returned data)
  • Response enrichment: truncated, shown, cap, notice

brapi_germplasm_performance tool

  • germplasmDbId required; discovers the germplasm's studies automatically (capped at 200) unless an explicit studyDbIds set 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

  • variantSetDbId required; format is matrix-json (dataframe only), vcf-lite (VCF-subset text in vcf, plus dataframe), or plink (.ped/.map text, plus dataframe)
  • maxCalls/maxColumns can only lower the deployment ceilings (BRAPI_GENOTYPE_CALLS_MAX_PULL, BRAPI_GENOTYPE_MATRIX_MAX_COLUMNS), never raise them
  • variantColumnLegend maps SQL-safe column names back to original variant IDs; truncated names which ceiling fired when the matrix is incomplete
  • Typed errors: unknown_alias, no_filters, search_endpoint_disabled

brapi_submit_observations tool

  • studyDbId required; 1–5,000 observation rows; observationDbId presence on a row routes it to PUT, absence to POST
  • mode: "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 probe
  • force: true skips the confirmation round — only for out-of-band-authorized writes
  • Additive only — no observation is ever destroyed
  • Requires BRAPI_ENABLE_WRITES=true to register; scoped to brapi: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) + optional params; last-resort escape hatch for endpoints no curated tool covers
  • Emits a suggestion when a curated tool exists for the same endpoint
  • 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
  • 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) + body posted verbatim to POST /search/{noun}; async polling resolved transparently, kind reports sync or async
  • Emits a suggestion when 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 default connection
  • Typed error: unknown_alias

brapi://calls resource

  • No parameters — raw /serverinfo + /calls profile (server identity, crops, supported services) for the default connection
  • 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 to brapi_find_variables
  • Typed errors: unknown_alias, variable_not_found

brapi_eda_study prompt

  • Arguments: studyDbId required; alias optional
  • 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) and traitName required; alias optional (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