Search and resolve Zenodo datasets, software, and publications by DOI; trace versions and funding.
One address for any MCP client. No sign-in needed on this server.
6 read
Grouped by tool name. At most: only reads or looks up information.
Odel charges nothing per call. The publisher may charge for its own service.
Search and resolve Zenodo datasets, software, and publications by DOI; trace versions and funding, list files, and preview text files via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://zenodo.caseyjhand.com/mcp
Datasets, software releases, and publications from Zenodo, CERN's open research repository. Search deposits with filters for funder, grant, community, license, and file type; resolve any Zenodo DOI, concept DOI, or URL to its record; walk a deposit's versions; and list, open, and preview its files, including members of .zip archives.
| Tool | Description |
|---|---|
zenodo_search_records | Search deposits by keyword plus type, community, funder, grant, ORCID, file type, license, access, and date filters, with facet counts |
zenodo_get_record | Resolve one deposit from a record id, DOI, concept DOI, or URL to its full metadata, first 25 files, and an optional citation |
zenodo_list_versions | List every version of a deposit's version series, newest first |
zenodo_list_files | Page a deposit's file manifest, or list the members of one of its .zip files |
zenodo_read_file | Read a byte-capped text excerpt of one file or .zip member |
zenodo_lookup_vocabulary | Resolve community, funder, grant, license, and resource-type names to the ids search filters take |
zenodo_search_records toolquery (terms OR-ed unless joined with AND, quoted phrases, field syntax such as metadata.title:"…") plus resource_type, community, funder, award, creator_orcid, file_type, license, access_status, and published_from / published_toresult_window_exceeded past that); latest versions only unless all_versions is truesort: bestmatch, newest, oldest, mostviewed, mostdownloaded, updated-desc, updated-asc (default bestmatch with a query, newest without)facets count resource types, access statuses, file types, subjects, and years over the full match setquery fails as query_is_identifier; a community or funder Zenodo doesn't know fails as unknown_community / unknown_funderquery that isn't valid search syntax (climate AND AND model, metadata.title: with no value, 'single quotes', an unclosed [a TO b]) fails as query_syntax, naming the offending token, whenever a filter other than community or all_versions is set, because Zenodo would search it as plain text and drop the filters; without such a filter it runs as plain text and the notice says so. Parts Zenodo cannot run fail as query_syntax with or without a filter, unless the query already runs as plain text: a / with no closing / (escape a literal one as \/), a ! with nothing after it (climate!; escape it as climate\!), a range bound ending in a backslash ([a TO b\]]; double-quote a bound that holds a space, ], or }, as in [a TO "b]"]), and a boost, fuzziness, or proximity value Zenodo cannot convert (climate^1.2.3, "x y"~0.5; write ^2, ~0.5, "x y"~2). A " inside a word that never closes (12" vinyl; escape it as 12\") fails the same way without a filter; with one, the phrase is closed at the end of the query and the closed query is checked insteadzenodo_get_record toolid takes a record id, a Zenodo DOI, a concept DOI or concept record id (resolves to the latest version), another DOI registered to a Zenodo record, or a zenodo.org / doi.org URL; input_kind and resolved_from report how it resolvedlatest_recid, usage counts, and the first 25 fileszenodo_list_files) is listed in continuations with a cursor and the record id it is bound to; pass both back to get that field's next window (8,000 characters of text, or the next page of entries)citation_style: bibtex, csl-json, apa, chicago-author-date, harvard-cite-them-right, ieee, modern-language-association, or naturefound: false with miss_kind (not_found, deleted, restricted, not_on_zenodo) and guidance; a deleted record adds its removal tombstonezenodo_list_versions toolid forms as zenodo_get_record; a concept DOI and any version's DOI list the same seriestotal_versions, latest_recid, and the concept record id and DOIindex, is_latest, file totals, and unique views and downloadsfound: false with the same miss_kind, guidance, and tombstone as zenodo_get_recordzenodo_list_files toolpreviewable, and listable (a .zip); up to 200 per page via offset / limitarchive_key lists the members of one .zip; Zenodo lists at most 1,000 files and directories per archive, flagged by upstream_truncatedkey_contains filters keys or member paths case-insensitively before pagingrecord_not_found / record_deletedzenodo_read_file toolkey from zenodo_list_files, plus archive_member to read inside a .zip without downloading itmax_bytes from 256 (default 16,384), at most 65,536 per call (larger values return 65,536); continue a top-level file from next_offset; .zip members read from byte 0 onlystatus is text, not_text, restricted, or empty; excerpts end on a line or character boundaryapplication/octet-stream (LICENSE, Makefile) are returned only when their content is UTF-8 textrights and the download_urlzenodo_lookup_vocabulary toolvocabulary: communities, funders, awards, licenses, or resource_types; query by name, acronym, or keyword, or omit it to browsefilter_param and filter_value name the zenodo_search_records filter and the id to pass itfunder scopes awards to one funder, as a ROR id, ROR URL, or Crossref Funder DOIreachable beside totalBuilt 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.
Zenodo-specific:
id; URLs are parsed locally and never fetchedtotal; community and funder values are checked before the search runsX-RateLimit-* headers, plus a process-local cache (records 5 min, searches 60 s, vocabularies 1 h).zip member reads, with binary detection before any text is returnedAgent-friendly output:
zenodo_get_record and zenodo_list_versions return found: false with a typed miss_kind, next-step guidance, and a deleted record's tombstoneeffectiveQuery sent to Zenodo and the appliedSort, and every paged tool reports totals with a next page or offset0, '', or falseA public instance is available at https://zenodo.caseyjhand.com/mcp — no installation required. It serves Streamable HTTP; each client takes its own config shape.
For Claude Code:
claude mcp add --transport http zenodo-mcp-server https://zenodo.caseyjhand.com/mcp
For Cline (cline_mcp_settings.json):
{
"mcpServers": {
"zenodo-mcp-server": {
"type": "streamableHttp",
"url": "https://zenodo.caseyjhand.com/mcp"
}
}
}
For Cursor (mcp.json):
{
"mcpServers": {
"zenodo-mcp-server": {
"url": "https://zenodo.caseyjhand.com/mcp"
}
}
}
Claude Desktop adds remote servers under Settings → Connectors.
The hosted instance runs without a token, so every caller shares Zenodo's anonymous rate limit. For sustained use, run your own instance with ZENODO_ACCESS_TOKEN set.
Add the following to your MCP client configuration file.
{
"mcpServers": {
"zenodo-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/zenodo-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"zenodo-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/zenodo-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"zenodo-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/zenodo-mcp-server:latest"]
}
}
}
To raise the rate limit, add "ZENODO_ACCESS_TOKEN": "your-token" to env (or -e ZENODO_ACCESS_TOKEN=… for Docker). See Configuration.
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
git clone https://github.com/cyanheads/zenodo-mcp-server.git
cd zenodo-mcp-server
bun install
cp .env.example .env
# optionally set ZENODO_ACCESS_TOKEN
| Variable | Description | Default |
|---|---|---|
ZENODO_ACCESS_TOKEN | Zenodo personal access token, created with no scopes. Raises Zenodo's rate limit; tool behavior and page sizes are unchanged. | none |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.). | info |
LOGS_DIR | Directory for log files (Node.js only). | <app-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry. | false |
See .env.example for the full list of optional overrides.
Zenodo rate-limits anonymous clients per IP address: 60 requests per minute and 2,000 per hour overall, and 30 per minute on record search. The server paces its own requests below those limits: 25 searches per minute, and 55 other requests per minute and 1,900 per hour. Every caller of one server process shares that budget. When it runs out, tools fail with rate_limited and a retryAfter in seconds.
With ZENODO_ACCESS_TOKEN set, Zenodo allows 100 requests per minute and 5,000 per hour, and the server paces other requests at 90 per minute and 4,800 per hour. Search stays at 25 per minute.
The token authenticates as the account that created it, so reads can see what that account can see, including restricted records it owns. On a shared or hosted server, create the token on a dedicated account that owns no restricted records.
Build and run the production version:
# One-time build
bun run rebuild
# Run the built server
bun run start:http
# or
bun run start:stdio
Run checks and tests:
bun run devcheck # Lints, formats, type-checks, and more
bun run test # Runs the test suite
docker build -t zenodo-mcp-server .
docker run --rm -p 3010:3010 zenodo-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/zenodo-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: server instructions, tool registration, service setup and teardown. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts) plus shared render, schema, and record-miss helpers. |
src/services/zenodo | Zenodo service: HTTP boundary and pacers, cache, identifier parsing, query building, normalization, text previews. |
tests/ | Unit, service, tool, and fuzz tests against recorded Zenodo fixtures. |
docs/design.md | Tool surface design, verified upstream behavior, and decisions log. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for logging; Zenodo responses are cached process-wide in the service, not in ctx.statesrc/mcp-server/tools/definitions/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.