@cyanheads/worms-marine-taxa-mcp-server
Resolve WoRMS marine species names; fetch classifications, synonyms, distributions, traits, and IDs via MCP. STDIO or Streamable HTTP.
8 Tools
Overview
Marine taxonomy from the World Register of Marine Species (WoRMS). Resolve scientific or common names to AphiaIDs, follow accepted names, and pull classifications, synonyms, distributions, traits, and external identifiers from the keyless WoRMS REST API.
Every AphiaID selector accepts a positive safe integer, a decimal string, or a canonical WoRMS LSID such as urn:lsid:marinespecies.org:taxname:137205; returned AphiaIDs remain numbers. For other LSIDs use worms_crosswalk_id with externalId and type: "lsid", or resolve a scientific name with worms_resolve_taxon.
Tools
Capability reference
worms_resolve_taxon tool
- A nonempty
names list (no total-input ceiling, at most 50 per upstream request) or one vernacular, never both. fuzzy: true opts into TAXAMATCH matching; marineOnly and extantOnly default to true for names. Vernacular search matches a whole recorded common name, or with vernacularMatch: "contains" every recorded name containing the text (grouper finds no exact match but many by contains); it applies neither filter
- Each result keeps its
inputIndex (duplicates included), every match with status, validAphiaId, and validName, and a guidance line on a miss; continue with nextInputOffset and nextMatchOffset
- Vernacular results page with the upstream
offset (1-based, steps of 50) and the local resultOffset
worms_get_taxon tool
- A nonempty
aphiaIds list with no total-input ceiling, at most 50 fetched per request; continue with nextInputOffset using the unchanged list. Unknown IDs stay in position as found: false; every submitted element is validated before fetching
includeVernaculars: true (one ID only) adds common names, paged with vernacularOffset; a failed lookup returns vernacularError beside the record
includeSources: true (one ID only, not with includeVernaculars) adds the taxon's sources, such as its original description and basis of record, with reference, page, url, link, fulltext, and doi verbatim. Optional sourceUse: "original description" filters whole use values ignoring case before sourceOffset and the byte budget; blanks omit the filter, populated values stay untrimmed. Continue nextSourceOffset with unchanged options to repeat the full primary record and reconstruct every selected row in order
- Successful source results report
sourceTotalCount for the complete selected bibliography and unfilteredSourceCount only with an effective filter. Notices distinguish no matching use, an empty bibliography, and a past-end offset. A failed lookup returns sourcesError beside the record without counts; sourceUse is inactive without includeSources
worms_get_classification tool
- One
aphiaId; ranks is the root-to-tip lineage, with found: false when WoRMS has none
includeChildren: true adds one 50-row page of direct children (offset, marineOnly, extantOnly); a failed children lookup returns childrenError beside the lineage
- The submitted AphiaID keeps its own recorded lineage. A
notice names its linked accepted AphiaID, or the preferred AphiaID for an accepted alternative representation; a failed status lookup returns statusError beside the lineage
worms_get_synonyms tool
- One
aphiaId, accepted or not: synonyms are listed for its accepted taxon (validAphiaId), 50 per upstream offset
found and guidance separate a missing taxon from a name with no known accepted ID
worms_get_distributions tool
- One
aphiaId; optional recordStatus (valid, doubtful, inaccurate), establishmentMeans, invasiveness, and occurrence filters combine with AND. recordStatus ignores case and surrounding whitespace and echoes its lowercase code; the others match case-insensitive whole values without trimming populated strings, with observed values in worms_list_reference topic distribution_terms. Blank filters are omitted
- Reports
unfilteredCount and filteredCount; rows carry the locality, its MarineRegions locationId, and status fields in source order
- WoRMS attaches records to individual name usages, so by default the tool reads that AphiaID's own records.
includeSynonyms: true combines the accepted taxon's records with those of its synonyms, 10 synonyms per call, each row tagged sourceAphiaId; synonymUnion reports the window, the nextSynonymOffset to continue from, and any failedSynonyms
- A
notice names the linked accepted AphiaID, distinguishing an accepted alternative representation from its preferred AphiaID; without includeSynonyms, a failed status lookup returns statusError beside the rows
worms_get_traits tool
- One
aphiaId; includeInherited (default true) adds attributes recorded on higher taxa. On an accepted taxon WoRMS also returns attributes recorded on its synonyms whatever includeInherited is set to; inheritedFrom names the source taxon in both cases
- A
notice names the linked accepted AphiaID, distinguishing an accepted alternative representation from its preferred AphiaID; a failed status lookup returns statusError beside the attributes
- Every node keeps
nodeIndex, parentIndex, and depth from the full tree, so units and qualifiers stay attached to their values across pages; values and references are verbatim
measurementType (case-insensitive whole value, such as Body size) keeps each matching node with its subtree and ancestors, indices unchanged; totalCount counts that set and unfilteredCount the whole tree
worms_crosswalk_id tool
- Exactly one of
aphiaId (forward) or externalId with type (reverse). Forward reads one type or all nine: algaebase, bold, dyntaxa, fishbase, iucn, lsid, ncbi, tsn, gisd
- Forward returns
ids per type, typeTotals (0 means none recorded), and failedTypes for systems that errored; reverse returns found and the full record. IDs stay opaque strings, leading zeros included
worms_list_reference tool
topic: external_id_types, statuses, environment_flags, and distribution_terms are served offline; ranks and trait_types are read live
statuses covers the 28 statuses the WoRMS manual and REST API document, each with a one-line meaning and a group (accepted, unaccepted, uncertain, technical). A record's status can be the specific reason itself, such as junior subjective synonym, so test the group rather than status === "unaccepted"; the guide is non-exhaustive
distribution_terms lists the establishmentMeans, invasiveness, and occurrence values observed in WoRMS distributions, for the whole-value filters of worms_get_distributions; the lists are non-exhaustive
- Pages with
resultOffset; trait definitions keep their tree indices across pages
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.
WoRMS-specific:
- Exact and TAXAMATCH fuzzy name matching with marine and extant filters; every match and record carries its accepted-name links
- One shared request queue: one upstream request at a time, 500 ms between starts, a 45-second tool deadline, and an 8 MiB response ceiling
- Per-call retrieval with no cache or bulk harvesting, as the WoRMS webservice guidance asks
- Every response carries WoRMS attribution (doi:10.14284/170); credit the WoRMS Editorial Board and keep per-record citations when reusing results (terms)
Agent-friendly output:
- Byte-bounded pages: reads return whole items within a 24,000-byte response and name the next offset (
nextResultOffset, nextInputOffset, upstream page.nextOffset); a full 50-row upstream page reports hasMore: null rather than guessing
- Unknown stays unknown: null and absent fields pass through, a null environment flag means not recorded, and empty evidence never claims biological absence
- Partial failure: optional lookups (
vernacularError, sourcesError, childrenError, statusError), synonym-union failedSynonyms, and crosswalk failedTypes keep the data that did arrive, with typed reasons and recovery hints
Getting started
Connect to the hosted instance, no install:
Or run it locally:
For other clients, add this to the MCP config file:
No API key or account is needed: the WoRMS REST API is public. For Docker or a source build, see Running the server.
Configuration
The server has no variables of its own; these are the common framework settings.
See .env.example for the common framework overrides.
Running the server
Local development
Requires Bun v1.4.0 or higher (or Node.js v24+).
Docker
The image defaults to HTTP transport, stateless session mode, and logs to /var/log/worms-marine-taxa-mcp-server. To run it over stdio from an MCP client, use "command": "docker" with "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/worms-marine-taxa-mcp-server:latest"]. To build locally, docker build -t worms-marine-taxa-mcp-server .; OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches; optional lookups report classified failures beside the primary data instead of discarding it
- Register new tools in the
createApp() arrays in src/index.ts
- Internal imports are relative ESM paths with
.js extensions
- Wrap 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:
License
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.