GBIF Biodiversity 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., "@GBIF Biodiversity MCP Serverfind occurrences of Danaus plexippus in Mexico"
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://gbif-biodiversity.caseyjhand.com/mcp
Overview
Species taxonomy, occurrence records, datasets, and publishers from the Global Biodiversity Information Facility (GBIF). Match names to the backbone taxonomy, search and aggregate 3.9B+ occurrence records with Darwin Core filters, and trace records back to their datasets and publishers. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
Tools
Tool | Description |
| Match a scientific name to the GBIF backbone, returning its taxon key, confidence, and classification |
| Match up to 50 scientific names in one call, one result per name in input order |
| Fetch a backbone taxon by key: classification, authorship, synonymy, vernacular name, counts |
| Search or browse the backbone by name fragment, rank, or a kingdom, family, or genus name |
| Return a taxon's parent chain, root-first from kingdom to its immediate parent |
| List a taxon's direct children, such as the genera in a family or the species in a genus |
| Search 3.9B+ occurrence records by taxon, place, date, basis of record, presence/absence, and IUCN category |
| Count occurrences matching a filter without fetching records |
| Fetch one full Darwin Core occurrence record by key |
| Aggregate occurrence counts by country, year, dataset, taxon, status, and other dimensions |
| Search datasets by keyword, type, publishing country, or publishing or hosting organization |
| Fetch full dataset metadata by UUID: citation, contacts, license, DOI, coverage |
| Search GBIF-registered publishing organizations by name or country |
Resources
Resource | Description |
| Taxon record from the GBIF backbone: classification, authorship, synonymy status, vernacular name |
| Dataset metadata: title, description, citation, license, contacts, coverage |
Both resources are also reachable via gbif_get_species and gbif_get_dataset; many MCP clients are tool-only and never surface resources.
Related MCP server: MCP Registry Search
Capability reference
gbif_match_species tool
One scientific
name(common names go togbif_search_species), with optionalstrict, akingdomto disambiguate, and an expectedrankReturns
taxonKey, the accepted taxon to pass to the occurrence tools, plusmatchedTaxonKeywhen a synonym was resolved,matchType(EXACT,FUZZY,HIGHERRANK),confidence0–100 (review below 80), and the classification with a key at each rankmatchTypeNONEfails asno_match; a blankkingdomfails asinvalid_filter
gbif_bulk_match_species tool
1–50 scientific
namesper call, withstrict; results come back in input orderEach result carries
nameandmatchType, and on a matchtaxonKey,matchedTaxonKey(synonyms), andconfidence; an unmatched name isNONE, a failed lookup isERRORwitherrorand, when classified,reason, and neither fails the batch
gbif_get_species tool
One backbone
taxonKey; a key not in the backbone fails asnot_found, one GBIF cannot parse (a fraction, or past the 32-bit range) asinvalid_filterReturns the classification with rank keys,
authorship,vernacularName,numDescendants,numOccurrences,publishedIn, andparentKey; ataxonomicStatusofSYNONYMcomes withacceptedKeyandaccepted
gbif_search_species tool
qname fragment (scientific and vernacular),rank,isExtinct, and a checklistdatasetKey(omit for the backbone); up to 1,000 per page withoffsetTaxa carry
key,rank,taxonomicStatus, classification names,numOccurrences, andnumDescendants; enrichment reportstotalCount,endOfRecords, and thetaxonScopeappliedkingdom,family, andgenustake names, matched exactly as GBIF capitalizes them; the narrowest one scopes, andkingdombeside a narrower one disambiguates it. An unknown name fails asunresolved_taxon_scope, a genus outside the named family asconflicting_taxon_scope, and a scope paired with a non-backbonedatasetKeymatches nothing. A blank filter or a malformeddatasetKeyfails asinvalid_filter
gbif_get_species_classification tool
One
taxonKey; returnsclassificationroot-first from kingdom to the immediate parent, each entry withkey,rank,name, andscientificNameThe queried taxon is not included (use
gbif_get_species); a root taxon returns an empty chain with a notice, an unknown key fails asnot_found, and one GBIF cannot parse asinvalid_filter
gbif_get_species_children tool
One
taxonKey, up to 1,000 children per page withoffset; no filters. An unknown key fails asnot_found, one GBIF cannot parse asinvalid_filterEach child carries
key,canonicalName,rank,taxonomicStatus,vernacularName,numOccurrences, andnumDescendants; GBIF reports no total, soendOfRecords(withtruncatedwhile more remain) is the continuation signal
gbif_search_occurrences tool
Filters:
taxonKey(preferred;scientificNamedoes not match synonyms),country,publishingCountry,stateProvince, adecimalLatitude/decimalLongitudebox or WKTgeometry,year,month,basisOfRecord,hasCoordinate,isInCluster,coordinateUncertaintyInMeters,datasetKey,occurrenceStatus,iucnRedListCategory; up to 300 per pageRecords carry
key,taxonKey,taxonomicStatus, coordinates,eventDateandeventTime,basisOfRecord,occurrenceStatus,iucnRedListCategory,datasetKey, andissues; enrichment reportstotalCount,endOfRecords, and theoccurrenceStatusappliedoffset + limitcaps at 100,001 and GBIF has no cursor; a deeper request fails aspagination_cap_exceeded, and a larger match is reached by partitioning on aDATASET_KEYfacet fromgbif_occurrence_facets. A rejected filter value fails asinvalid_filter
gbif_count_occurrences tool
Filters:
taxonKey,country,publishingCountry,stateProvince,isGeoreferenced,datasetKey,year,occurrenceStatus,iucnRedListCategoryReturns
count, with theoccurrenceStatusapplied in the enrichment; a count above 100,001 carries a notice naming the partition route, and a rejected filter value fails asinvalid_filter
gbif_get_occurrence tool
One
occurrenceKeyfrom a search result; an unknown key fails asnot_found, one GBIF cannot parse asinvalid_filterThe full Darwin Core record:
occurrenceStatus(ABSENTis a survey that found nothing, not a sighting), coordinates, GADM levels 0–3 undergadm, dates witheventTime,taxonomicStatus,iucnRedListCategory, institution, collection, and catalog codes,recordedByandidentifiedBy,media,identifiers, andissues
gbif_occurrence_facets tool
facetis one ofCOUNTRY,STATE_PROVINCE,YEAR,MONTH,BASIS_OF_RECORD,DATASET_KEY,PUBLISHING_COUNTRY,KINGDOM_KEY,PHYLUM_KEY,CLASS_KEY,ORDER_KEY,FAMILY_KEY,GENUS_KEY,SPECIES_KEY,OCCURRENCE_STATUS,IUCN_RED_LIST_CATEGORY; up to 100 values per page (facetLimit, default 10), paged withfacetOffsetScope filters:
taxonKey,country,publishingCountry,stateProvince,year,geometry,basisOfRecord,datasetKey,occurrenceStatus,iucnRedListCategory. ReturnstotalOccurrencesandcountsranked by count; a full page setstruncatedin the enrichment, and a rejected scope value fails asinvalid_filterDATASET_KEYbuckets sum to the scope's full total, so it is the facet for partitioning a search too large to page;YEAR,MONTH,STATE_PROVINCE, andSPECIES_KEYdrop records that lack the field. Passfacet: OCCURRENCE_STATUSwithoccurrenceStatus: ANYfor the presence/absence split
gbif_search_datasets tool
Filters:
q,type(OCCURRENCE,CHECKLIST,METADATA,SAMPLING_EVENT),publishingCountry,publishingOrg,hostingOrg; up to 1,000 per pageReturns
key,title,type,license,doi,recordCount, and a 300-characterdescriptionpreview, withdescriptionTruncatedflagging a cut (gbif_get_datasethas the full text)publishingOrgmatches the organization whose data it is and is the usual chain fromgbif_search_publishers;hostingOrgmatches the organization whose installation serves it, and the two together intersect. Both take the lowercase UUID form; anything else, like any rejected filter value, fails asinvalid_filter
gbif_get_dataset tool
One
datasetKeyUUID andcontactLimit(default 10, max 100;0drops contact detail but keepscontactsTotal); a malformed key fails asinvalid_filter, an unknown one asnot_foundReturns
description,citationText,license,doi,recordCount,numConstituents,contactswithcontactsTotalandcontactsReturned,temporalCoverages, andgeographicCoverages
gbif_search_publishers tool
qname fragment andcountry(alpha-2 or alpha-3, any case); up to 1,000 per pageReturns organization
key,title,country, andcity; the key chains intogbif_search_datasetsaspublishingOrgA blank
q, an emptycountry, or a country GBIF cannot parse fails asinvalid_filter
gbif://species/{taxonKey} resource
Taxon record as
application/json: classification names,authorship,vernacularName,taxonomicStatuswithacceptedKeyandaccepted,numDescendantstaxonKeycomes fromgbif_match_speciesorgbif_search_species; an unknown key fails asnot_found, a non-numeric or unparseable one asinvalid_filter
gbif://dataset/{datasetKey} resource
The
gbif_get_datasetrecord asapplication/json, with contacts fixed at 10datasetKeycomes fromgbif_search_datasetsor an occurrence record; an unknown key fails asnot_found, a malformed one asinvalid_filter
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.
GBIF-specific:
GBIF API v1 species, occurrence, dataset, and organization endpoints, keyless, with an identifying
User-Agenton every requestSynonym-safe keys:
gbif_match_speciesresolves a synonym to its accepted taxon, and the occurrence tools filter on thattaxonKeySightings by default: the search, count, and facet tools default
occurrenceStatustoPRESENT, excluding absence records. DatasetrecordCountspans every status, so it runs higher than agbif_count_occurrencestotal for the same keyStrict country codes: on the occurrence tools and
gbif_search_datasets,country(where a record was observed) andpublishingCountry(the publisher's country) accept only uppercase ISO 3166-1 alpha-2, because other forms silently match nothing there.stateProvincematches verbatim and case-sensitively, so take values from aSTATE_PROVINCEfacetBlank filters rejected: GBIF reads an empty parameter as no filter, so an optional filter sent blank or whitespace-only fails as
invalid_filterinstead of widening the query. Omit a field to leave it off
Agent-friendly output:
Scope echo: enrichment reports
totalCount,endOfRecords, theoccurrenceStatusapplied, and the resolvedtaxonScope, with notices on empty results, over-cap totals, and astateProvincethat matched nothingGraceful partial failure:
gbif_bulk_match_speciesreturns aNONEorERRORrow per name instead of failing the batchTyped errors:
invalid_filter,not_found,no_match,unresolved_taxon_scope,conflicting_taxon_scope, andpagination_cap_exceeded, each with a recovery hintSparse fields omitted rather than null-filled:
extinctappears only on taxa GBIF explicitly flags
Getting started
Public Hosted Instance
A public instance is available at https://gbif-biodiversity.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"gbif-biodiversity-mcp-server": {
"type": "streamable-http",
"url": "https://gbif-biodiversity.caseyjhand.com/mcp"
}
}
}Self-Hosted / Local
Add the following to your MCP client configuration file.
{
"mcpServers": {
"gbif-biodiversity-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/gbif-biodiversity-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}Or with npx (no Bun required):
{
"mcpServers": {
"gbif-biodiversity-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/gbif-biodiversity-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}Or with Docker:
{
"mcpServers": {
"gbif-biodiversity-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/gbif-biodiversity-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/mcpPrerequisites
Bun v1.4.0 or higher (or Node.js v24+).
No credentials: the GBIF endpoints this server calls are public.
Installation
Clone the repository:
git clone https://github.com/cyanheads/gbif-biodiversity-mcp-server.gitNavigate into the directory:
cd gbif-biodiversity-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# optionally set GBIF_USER_AGENT to a contact URL or emailConfiguration
Variable | Description | Default |
| GBIF API base URL. |
|
| Per-request HTTP timeout, in ms. |
|
|
| server name, version, and repository URL |
| Transport: |
|
| HTTP server port. |
|
| HTTP session mode: |
|
| Authentication: |
|
| Log level ( |
|
| Log each failed tool call's arguments and result, redacted by key name and capped at |
|
| Directory for log files (Node.js only). |
|
| Storage backend: |
|
| Enable OpenTelemetry. |
|
See .env.example for the full list of optional overrides.
Running the server
Local development
Build and run the production version:
# One-time build bun run rebuild # Run the built server bun run start:http # or bun run start:stdioRun checks and tests:
bun run devcheck # Lints, formats, type-checks, and more bun run test # Runs the test suite
Docker
docker build -t gbif-biodiversity-mcp-server .
docker run --rm -p 3010:3010 gbif-biodiversity-mcp-serverThe Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/gbif-biodiversity-mcp-server. OpenTelemetry peer dependencies are installed by default; build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
Directory | Purpose |
| Tool definitions ( |
| Resource definitions. Species and dataset resources. |
| GBIF API service layer: HTTP client with retry, timeouts, and error mapping, plus raw response types. |
| Server-specific environment variable parsing and validation with Zod. |
| Unit and integration tests, mirroring the |
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 storageRegister new tools and resources in the
createApp()arrays insrc/index.tsWrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.
This server cannot be deployed
Maintenance
Related MCP Connectors
Search ReliefWeb humanitarian reports, disasters, jobs, training, and country profiles via MCP.
GBIF MCP — wraps the Global Biodiversity Information Facility API v1 (free, no auth)
Query U.S. Census Bureau data, variables, and geography via MCP.
Search events, conference weeks, cities, venues and artist schedules via remote MCP.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables searching Figshare research articles, datasets, papers, and posters through the MCP protocol.13 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables semantic and full-text search over the official Model Context Protocol registry to discover MCP servers for integration.10Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables searching OpenAleph entities and documents using natural language queries through the MCP protocol.15 npm1MIT
- AlicenseNot gradedqualityAmaintenanceGeocode, reverse geocode, and run Overpass spatial queries on OpenStreetMap data via MCP.304 npm6Apache 2.0