Search fossil occurrences, taxon ranges, diversity through deep time, and the geologic time scale.
Search fossil occurrences, resolve taxon fossil ranges, plot diversity through deep time, and look up the geologic time scale via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://paleobiology.caseyjhand.com/mcp
Fossil biodiversity over the Paleobiology Database (PBDB), spanning roughly 540 million years. Resolve taxon fossil ranges, search fossil occurrences and collections by taxon, geologic time, and location, and plot diversity through deep time from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
paleobiology_search_occurrences | Search fossil occurrences by taxon, geologic time, geography, and depositional environment. Every row carries both modern and paleo coordinates; broad results spill to a DataCanvas for SQL. |
paleobiology_get_taxon | Resolve a taxon by name or taxon_no to its accepted name, rank, classification, and FAD/LAD range — the name-resolution gateway. |
paleobiology_get_diversity | Compute a diversity / origination / extinction curve for a clade across geologic time. |
paleobiology_list_intervals | Look up the geologic time scale — named intervals ↔ absolute Ma boundaries. |
paleobiology_search_collections | Find fossil collections (localities) by area, geologic time, formation, and lithology. |
paleobiology_dataframe_query | Run a read-only SQL SELECT over occurrence sets staged on a DataCanvas. |
paleobiology_dataframe_describe | List the tables and columns staged on a DataCanvas. |
paleobiology_dataframe_drop | Drop a single staged table to free memory before its TTL expires. Opt-in. |
| Resource | Description |
|---|---|
paleobiology://occurrence/{occurrence_no} | One fossil occurrence with full detail — modern + paleo coordinates, classification, strata, locality. |
paleobiology://taxon/{taxon_no} | One taxon record with its fossil range and classification. |
All resource data is also reachable via tools — the resources mirror a single-record read of paleobiology_search_occurrences / paleobiology_get_taxon for clients that surface resources. Tool-only clients lose nothing.
paleobiology_search_occurrences toolbase_name (a clade and all its descendants) or taxon_name (exact) filters the taxon; base_id filters the same clade by its resolved PBDB taxon_no instead of a name — exactly one of base_name/base_id, never bothinterval or a max_ma/min_ma range (min_ma strictly less than max_ma), plus an optional lng/lat bounding box (lngmin/lngmax both or neither; a lone latmin/latmax is valid) and environment (marine, terrestrial, freshwater); collection_no scopes to one locality. At least one filter is requiredlimit (max 500, default 100) and offset page against PBDB's true match count; the response names the exact offset for the next pagecanvas_id and table_name return only when the page spills; reusing a canvas_id replaces that canvas's occurrence table rather than accumulatingmissing_filter, conflicting_taxon_filter, incomplete_bbox, inverted_ma_range — all rejected at the tool boundary before the upstream requestpaleobiology_get_taxon toolname or taxon_no (exactly one required) to accepted name, rank, higher classification, immediate parent, occurrence count, and FAD/LAD range in Mataxon_no is the base_id accepted by paleobiology_search_occurrences, paleobiology_get_diversity, and paleobiology_search_collectionsshow_children pages immediate child taxa, up to 200 per call; children_truncated and children_offset say whether and where to continuetaxon_not_found, missing_selectorpaleobiology_get_diversity toolbase_name or base_id (exactly one required), bounded by a named interval or max_ma/min_ma range (min_ma strictly less than max_ma)count enum: genera (default), species, families; resolution enum: period (default), epoch, agemissing_filter, conflicting_taxon_filter, inverted_ma_rangepaleobiology_list_intervals toolname substring, a min_ma/max_ma overlap window, and/or a level (eon, era, period, epoch, age); no filters browses the full scalesource field (bundled_ics / pbdb_upstream) plus snapshot_version say which answeredlevel, Ma boundaries, parent_no, and — when resolved upstream — the originating scale nameinterval_not_found (name matched nothing anywhere), interval_lookup_unavailable (retryable — PBDB unreachable for a non-bundled name)paleobiology_search_collections toolbase_name/base_id (mutually exclusive), a named interval or max_ma/min_ma range, a lng/lat bounding box, a formation or lithology name, and/or environment; at least one filter is requiredn_occs)limit (max 500, default 100) and offset page results; the response discloses when localities remaincollection_no into paleobiology_search_occurrences to see the fauna found at that localitymissing_filter, conflicting_taxon_filter, incomplete_bbox, inverted_ma_rangepaleobiology_dataframe_query toolSELECT against occurrence sets staged on a DataCanvas by paleobiology_search_occurrences; writes and file-reading functions are rejectedtable_name a spilled search returned; the classification column is JSON — roll up by rank with json_extract_string(classification, '$.family') (also $.phylum, $.class, $.order, $.genus)truncated: true marks a trimmed resultcanvas_disabled when CANVAS_PROVIDER_TYPE is not duckdbpaleobiology_dataframe_describe toolpaleobiology_dataframe_query to discover identifierscanvas_disabled when CANVAS_PROVIDER_TYPE is not duckdbpaleobiology_dataframe_drop toolcanvas_id + table_name to free memory before its TTL expires; dropping a nonexistent table returns dropped: false, not an errorPALEOBIOLOGY_DATAFRAME_DROP_ENABLED=true, absent from tools/list otherwisecanvas_disabled when CANVAS_PROVIDER_TYPE is not duckdbpaleobiology://occurrence/{occurrence_no} resourceoccurrence_no is a bare positive integer (regex-validated), from paleobiology_search_occurrences outputattribution fieldoccurrence_not_foundpaleobiology://taxon/{taxon_no} resourcetaxon_no is a bare positive integer (regex-validated), from paleobiology_get_taxon or an occurrence's accepted_nopaleobiology_get_taxon's shape exactly — accepted name, rank, classification, parent, FAD/LAD range — plus a CC BY 4.0 attribution fieldtaxon_not_foundBuilt 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.
PBDB-specific:
vocab=pbdb so readable field names come straight from upstream instead of hand-mapped terse codespaleobiology_list_intervals resolves the international scale's named intervals ↔ absolute Ma boundaries with no network call, falling back to a PBDB lookup for sub-stage and regional namesclassification JSON column)MCP_AUTH_MODE defaults to none)Agent-friendly output:
reference_no, every PBDB-backed tool and resource carries the CC BY attribution, sparse upstream fields (paleo-coords, formation, late_interval) are omitted rather than zeroed, and diversity counts are flagged as sampledA public instance is available at https://paleobiology.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"paleobiology-mcp-server": {
"type": "streamable-http",
"url": "https://paleobiology.caseyjhand.com/mcp"
}
}
}
Add one of the following to your MCP client configuration file. PBDB is keyless — no API key required.
With bunx:
{
"mcpServers": {
"paleobiology-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/paleobiology-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"paleobiology-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/paleobiology-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"paleobiology-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/paleobiology-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/mcp
To enable SQL over large occurrence sets, set CANVAS_PROVIDER_TYPE=duckdb (the @duckdb/node-api peer dep ships in dependencies). Without it, paleobiology_search_occurrences still returns its inline preview; the paleobiology_dataframe_* tools fail with a clear "canvas disabled" message.
git clone https://github.com/cyanheads/paleobiology-mcp-server.git
cd paleobiology-mcp-server
bun install
cp .env.example .env
# edit .env to override defaults — all vars are optional
All variables are optional — the server runs with no configuration against the public PBDB API.
| Variable | Description | Default |
|---|---|---|
PBDB_BASE_URL | Paleobiology Database API base. Override for a mirror/proxy or pinned API version. | https://paleobiodb.org/data1.2 |
PBDB_TIMEOUT_MS | Per-request timeout in milliseconds. Diversity queries over large clades can be slow. | 30000 |
PBDB_MAX_OCCURRENCES | Hard cap on rows pulled per occurrence/collection call. | 1000 |
CANVAS_PROVIDER_TYPE | Set to duckdb to enable the DataCanvas spill path and paleobiology_dataframe_* tools. | none |
PALEOBIOLOGY_DATAFRAME_DROP_ENABLED | Register paleobiology_dataframe_drop. Absent from tools/list when unset. | false |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_SESSION_MODE | HTTP session posture: stateless, stateful, or auto. The server declares stateless in src/index.ts — it holds no per-session state — and this variable overrides that declaration. | stateless |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
OTEL_ENABLED | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | false |
See .env.example for the full list of optional overrides.
Build and run:
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
docker build -t paleobiology-mcp-server .
docker run --rm -p 3010:3010 paleobiology-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/paleobiology-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools/resources and inits services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). |
src/mcp-server/resources | Resource definitions (*.resource.ts). |
src/services/pbdb | Paleobiology Database HTTP client, normalization, and domain types. |
src/services/intervals | In-memory index over the bundled ICS geologic time-scale snapshot. |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagesrc/mcp-server/*/definitions/index.ts0,0)Data is from the Paleobiology Database, licensed CC BY 4.0 — credit it in downstream use.
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y @cyanheads/paleobiology-mcp-serverMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-cyanheads-paleobiology-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/paleobiology-mcp-server"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceio.github.cyanheads/paleobiology-mcp-server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.