
Brapi Mcp Server
io.github.cyanheadsv0.8.0更新于 Sep 29, 2026
Collaborative BrAPI v2.1 MCP workspace — studies, germplasm, genotypes across Breedbase, T3, more.
安装
在 SourceWeft 中
- 打开 控制台中的 Brapi Mcp Server,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Web executable,通过 Streamable HTTP。 远程服务在工作区中配置后即可从网页运行时运行。
其他 MCP 客户端
把它添加到你客户端的 mcpServers 配置中。
{
"mcpServers": {
"brapi-mcp-server": {
"type": "http",
"url": "https://brapi.caseyjhand.com/mcp"
}
}
}README
@cyanheads/brapi-mcp-server
A collaborative BrAPI v2.1 workspace for multi-agent research via MCP. Search studies, germplasm, genotypes, & more - across Breedbase, T3, Sweetpotatobase, & any BrAPI v2-compliant server.
Public Hosted Server: https://brapi.caseyjhand.com/mcp
Overview
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.
Tools
Resources
Every resource reads the default connection and mirrors a tool; tool-only clients and multi-server workflows use the tools.
Prompts
Capability reference
brapi_connect tool
baseUrlandauthare optional: omitted values come fromBRAPI_<ALIAS>_*env vars, the built-in aliases, thenBRAPI_DEFAULT_*(see Per-alias credentials);alias(defaultdefault, pattern^[a-zA-Z0-9_-]+$) keeps several servers registered at once;auth.modeisnone,bearer,api_key,sgn(Breedbase/tokenexchange), oroauth2(client credentials)- Returns the orientation envelope:
serveridentity,authsummary,capabilities(supported,notableGaps), activedialect,contentcounts,attributionfor built-in servers, andnextToolSuggestionsnaming the entry-point finders this server can serve - 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
brapi_server_info tool
aliasoptional;forceRefresh(defaultfalse) refetches the capability profile instead of reading the cache- Returns the same orientation envelope as
brapi_connect
brapi_describe_filters tool
endpointis one ofstudies,germplasm,observations,variables,images,variants,locations;unknown_endpointcarriesavailableEndpoints- Each filter has
name,type,description, andexample; the catalog follows the v2.1 spec, and individual servers may honor a subset
brapi_find_studies tool
- Filters:
crop,trialTypes,seasons,locations,programs,trials,studyNames,active distributionsoverprogramName,studyType,seasons,locationName,commonCropName
brapi_get_study tool
studyDbIdrequired; resolvesprogram,trial, andlocationinline;study_not_foundwhen the upstream has no such study- 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
brapi_find_germplasm tool
- Filters:
names,germplasmDbIds,germplasmPUIs,accessionNumbers,crops,synonyms,collections,genus,species;textis 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 distributionsovercommonCropName,genus,species,collection,countryOfOriginCode
brapi_get_germplasm tool
germplasmDbIdrequired; returnsattributesand directparents;germplasm_not_foundwhen the upstream has no such germplasm- Companion counts
studyCount,directParentCount,directDescendantCount
brapi_walk_pedigree tool
- 1–20 root
germplasmDbIds;directionisancestors(default),descendants, orboth;maxDepth1–10 (default 3); the walk stops at 1,000 nodes and setstruncated - Deduplicated
nodesandedgeswithdepthReached,rootCount,leafCount,cycleCount,deadEndCount; pastloadLimit, both sets spill tonodesDataframeandedgesDataframe
brapi_find_variables tool
- Filters:
variables,variableNames,variablePUIs,traitClasses,ontologies,studies,methods,scales,crop textranks the full result set and moves matches to the top without dropping the rest;ontologyCandidateslists the ranked matches, each withsource(puiMatch,nameMatch,synonymMatch,traitClassMatch)distributionsoverontologyDbId,traitClass,scaleName
brapi_find_observations tool
- Filters:
studies,germplasm,variables,observationUnits,observations,seasons,programs,trials,observationLevels,timestampFrom/timestampTo distributionsoverobservationVariableName,studyName,germplasmName,observationLevel,season
brapi_find_images tool
- Filters:
images,observationUnits,observations,studies,imageFileNames,mimeTypes,descriptiveOntologyTerms; returns metadata only, with bytes viabrapi_get_image distributionsovermimeType,studyName,observationUnitName,descriptiveOntologyTerms
brapi_get_image tool
- 1–5
imageDbIdsper call, up to 20 MB each;images_unsupportedwhen the server doesn't advertise/images - Each image's
sourceisimagecontentor theimageURLfallback; failed fetches land in per-imageerrors[]and suspect payloads (a non-image MIME type) inwarnings[], so a partial batch still returns
brapi_find_locations tool
- Filters:
locations,locationNames,countryCodes(ISO 3166-1 alpha-3),countryNames(English names resolved to alpha-3),locationTypes,abbreviations; optionalbboxneeds all four ofminLat,maxLat,minLon,maxLonand applies after the fetch distributionsovercountryCode,locationType;coordinateAxisOrder: "swapped"reports a server that stores coordinates as[lat, lon]
brapi_find_variants tool
- Filters:
variantSets,variants,references, and a genomic region ofreferenceName+start(inclusive) /end(exclusive), 1-based distributionsovervariantType,referenceName,variantSetDbId
brapi_find_genotype_calls tool
- Needs at least one of
variantSetDbId,variantSetDbIds,germplasmDbIds,callSetDbIds,variantDbIds(no_filtersotherwise); optionalcallFormat(VCF,FLAPJACK,DARTSEQ,JSON) distributionsovercallSetName,variantName,variantSetDbId, plus the server'scallFormatting;search_endpoint_disabledwhen the active dialect marksPOST /search/callsas dead- The upstream pull stops at
BRAPI_GENOTYPE_CALLS_MAX_PULL(default 100,000) and setstruncated;loadLimitbounds only the inline preview
brapi_dataframe_describe tool
dataframeoptional: omit to list every dataframe, or name one for columns, row count, and provenance (originating tool,baseUrl, query, expiry), which only auto-registereddf_*tables carry- Listing without a name fails with
list_all_disabled_on_shared_httpon an HTTP deployment where every caller shares thedefaulttenant
brapi_dataframe_query tool
sqlis a singleSELECT; writes, DDL,COPY,PRAGMA,ATTACH, file reads, and system-catalog reads fail assql_rejected, with the specific reason indata.gateReason- Returns
rowCount, typedcolumns, androwsbounded bypreview(≤1,000),rowLimit, andBRAPI_CANVAS_MAX_ROWS;truncated,shown,cap, andnoticedisclose the cut registerAs(identifier, ≤63 characters) saves the full result as a new dataframe for chaining
brapi_dataframe_drop tool
dataframerequired; returnsdropped: false, not an error, for an unknown name- Registered only when
BRAPI_CANVAS_DROP_ENABLED=true
brapi_dataframe_export tool
formatiscsv,parquet, orjson; optionalcolumnsorsql(mutually exclusive) andfilename(no path separators or..; omit for a timestamp-suffixed default); returns the absolutepath,sizeBytes, androwCount- Typed errors:
export_dir_unset,dataframe_not_found,invalid_filename,mutually_exclusive_projection - Registered only over stdio with
BRAPI_EXPORT_DIRset
brapi_build_phenotype_matrix tool
studiesrequired (≥1), optionalvariables/germplasmsubsets;shapewide(default) orlong;aggregatemean(default),median,first, orall(always long form);loadLimitcaps observations per study- Returns the matrix as a dataframe plus
observationCount,germplasmCount,variableCount, andvariableLegendmapping SQL-safe column names to variable names;truncated/capflag a study that hitloadLimit;no_observation_pathwhen neither/observationsnor/observationunitsreturns data
brapi_germplasm_performance tool
germplasmDbIdrequired; discovers its studies (up to 200) unlessstudyDbIdsis supplied; optionalvariablessubset;germplasm_not_foundwhen the upstream has no such germplasmperVariablerows carryn,mean,median,sd(omitted when n < 2 or non-numeric),min,max,studyCount,studyDbIds, andseasons
brapi_export_genotype_matrix tool
variantSetDbIdrequired (no_filtersotherwise), optionalgermplasmDbIds;formatismatrix-json,vcf-lite(addsvcftext), orplink(addsped/maptext), and every format registers the germplasm × variant dataframevariantColumnLegendmaps SQL-safe column names back to variant IDs;search_endpoint_disabledwhen the active dialect marksPOST /search/callsas deadmaxCalls/maxColumnscan lowerBRAPI_GENOTYPE_CALLS_MAX_PULL/BRAPI_GENOTYPE_MATRIX_MAX_COLUMNSbut never raise them;truncatedmeans a ceiling fired, andwarningsnames which
brapi_submit_observations tool
studyDbIdplus 1–5,000observations; a row withobservationDbIdupdates viaPUT, one without creates viaPOST, and nothing is deletedmode: "preview"(default) returnsvalid,invalid,routingcounts, andperRowWarningswithout writing;mode: "apply"asks the caller to confirm (force: trueskips it), writes, and returnsposted,updated, andstudyObservationCount; failures areobservations_unsupported,study_not_found,post_unsupported,put_unsupported, anduser_declined- Registered only when
BRAPI_ENABLE_WRITES=true; requires thebrapi:write:observationsscope
brapi_raw_get tool
pathis a relative route such as/samples(a full URL fails ascross_origin_path); optionalparamsandloadLimit- Returns the raw envelope (
url,metadata,result) plus asuggestionwhen a curated tool covers the endpoint; list results pastloadLimitspill to a dataframe unlessparams.page/params.pageSizedrive paging
brapi_raw_search tool
noun(e.g.observations,calls,germplasm) and abodyposted verbatim toPOST /search/{noun}; async searches are polled to completion- Returns
kind(syncorasync),searchResultsDbId,result, and asuggestion; spills likebrapi_raw_getunlessbody.page/body.pageSizeis set;search_endpoint_disabledwhen the active dialect marks the route as dead
brapi://server/info resource
- Orientation envelope for the
defaultconnection asapplication/json - Same payload as
brapi_server_infocalled with no arguments
brapi://calls resource
- Capability profile for the
defaultconnection: supported services with their HTTP methods and versions, plus crops - Reflects what
/serverinfo+/callsreturned at the last load
brapi://study/{studyDbId} resource
- Same payload as
brapi_get_studyon thedefaultconnection study_not_foundwhen the upstream has no such study
brapi://germplasm/{germplasmDbId} resource
- Same payload as
brapi_get_germplasmon thedefaultconnection germplasm_not_foundwhen the upstream has no such germplasm
brapi://filters/{endpoint} resource
- Same payload as
brapi_describe_filters;unknown_endpointfor an endpoint outside the catalog - Listing
brapi://filtersreturns one resource per endpoint
brapi://variable/{observationVariableDbId} resource
- The
/variables/{id}record (trait, scale, method, ontology) on thedefaultconnection variable_not_foundwhen the upstream has no such variable
brapi_eda_study prompt
- Arguments:
studyDbIdrequired;aliasoptional - 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
brapi_meta_analysis prompt
- Arguments:
germplasmDbIds(comma-separated) andtraitNamerequired;aliasoptional, run once per alias for multi-server analyses - 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
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:
- Shared finder contract: the seven filter finders (studies, germplasm, variables, observations, images, locations, variants) take
alias,loadLimit, andextraFilters(keys frombrapi_describe_filters), and returnresults,hasMore,distributions, and an optionaldataframehandle - Dataframe spillover: past
loadLimit, finders page the rest of the result (up to 50 pages and 50,000 rows) into a DuckDBdf_<uuid>table and return its handle - 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 toPOST /search/{noun}when aGETwould narrow a multi-value filter;BRAPI_<ALIAS>_DIALECTpins one - 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
- Capability-aware calls: each connection's
/serverinfo+/callsprofile is cached and checked before a tool calls an endpoint; a per-connection concurrency cap and exponential-backoff retries cover 429/5xx
Agent-friendly output:
- Typed failures: every connection-scoped tool and resource fails with
unknown_aliasuntilbrapi_connectregisters the alias, and a filter finder orbrapi_build_phenotype_matrixfails withall_filters_droppedinstead of widening to an unfiltered pull when the dialect drops every filter supplied - Query echo on every finder:
totalCount,returnedCount, the exactappliedFilterssent upstream, arefinementHinton broad results, an empty-resultnotice, andwarnings - Graceful partial failure:
brapi_get_imagereturns per-imageerrors[]andwarnings[]rows instead of failing the batch - Discriminated outputs:
brapi_submit_observationsreturns amode-discriminated result (preview/apply),brapi_raw_searchreportskind, andbrapi_get_imagereports each image'ssource
Working with dataframes
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.
Dataframe names are capability tokens, not row-level ACLs: anyone holding a name in the same session or tenant bucket (see 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.
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:
Self-Hosted / Local
Add the following to your MCP client configuration file.
Or with npx (no Bun required):
Or with Docker:
For Streamable HTTP, set the transport and start the server:
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).
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
@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.
Installation
- Clone the repository:
- Navigate into the directory:
- Install dependencies:
- Configure environment:
Configuration
Every variable is optional.
See .env.example for the full list of optional overrides.
Per-alias credentials
brapi_connect fills baseUrl and auth from env vars when the agent omits them, in this order:
- Agent input, within the pairing rules below.
- Per-alias env vars:
BRAPI_<ALIAS>_*, uppercased with hyphens as underscores (my-server→BRAPI_MY_SERVER_*). - Built-in aliases: see Built-in aliases.
BRAPI_DEFAULT_BASE_URL, for an alias with no URL or credentials of its own.
Env credentials go only to the server configured with them:
- An alias's credentials pair with its
BRAPI_<ALIAS>_BASE_URL, else its enabled built-in URL. A callerbaseUrlthat points elsewhere fails withauth_base_url_mismatch. Credentials with no URL of their own, including those left behind by a built-in disabled viaBRAPI_BUILTIN_ALIASES_DISABLED, fail withalias_base_url_unsetand are never sent toBRAPI_DEFAULT_BASE_URL. BRAPI_DEFAULT_*credentials attach only when the resolved URL isBRAPI_DEFAULT_BASE_URL; an alias pointed anywhere else connects without auth unless it has credentials of its own. With noBRAPI_DEFAULT_BASE_URLset, default credentials fail withalias_base_url_unset.
URLs compare after normalizing host case, default ports, and trailing slashes. Caller-supplied auth is never mixed with env credentials.
Each alias carries one credential family, and the auth mode follows from which fields are set. Mixing families within an alias raises a ValidationError.
BRAPI_<ALIAS>_DIALECT pins the dialect adapter (spec, brapi-test, breedbase, cassavabase, bms) when detection picks the wrong one; auto or unset detects it.
The agent then calls brapi_connect({ alias: 'bti-cassava' }) with no baseUrl, no auth, and no secrets in the prompt.
Built-in aliases
These public BrAPI v2 endpoints connect with no configuration. Their orientation envelope carries license, citation, and homepage in an attribution block (Creative Commons Attribution).
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).
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.
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.
Running the server
Local development
-
Build and run the production version:
-
Run checks and tests:
Docker
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.
Deployment shapes
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:
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.
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.
Project structure
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor logging,ctx.statefor tenant-scoped storage — noconsole, no direct persistence access - Add new tools to the matching group in
src/mcp-server/tools/definitions/index.ts;src/index.tscomposes the groups behind their feature flags - Wrap upstream calls: validate raw → normalize → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
License
Apache-2.0 — see LICENSE for details.
来源:README.md,提交 f079d60
工具
0版本历史
3- v0.8.0最新Sep 24, 2026
- v0.7.13Sep 19, 2026
- v0.7.12Sep 16, 2026