mcpField
Z
zenodo-mcp-server

zenodo-mcp-server

Remote
github.com/cyanheadsSearch1Updated today
Repository

About zenodo-mcp-server

zenodo-mcp-server lets researchers and data users search Zenodo deposits and inspect datasets, software, and publications. It resolves identifiers, traces versions, and supports file and archive previews.

Key features

  1. 01Search with research metadata filters
  2. 02Resolve DOI, concept DOI, and URLs
  3. 03Inspect record metadata and citations
  4. 04Trace deposit version histories
  5. 05List files and read text excerpts

Use cases

  1. 01Find research records by funder or license
  2. 02Resolve a DOI to its Zenodo record
  3. 03Compare versions of a deposit
  4. 04Preview text files or archive members

MCP support

Exposes six tools for MCP clients to search Zenodo, resolve records, browse versions and files, and read text excerpts. Supports STDIO and Streamable HTTP.

Configuration

Remote servers need no local install. Pick your client, then copy the config or install in one click.

Endpointhttps://zenodo.caseyjhand.com/mcp
Copy install command
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

Run commands in a terminal; paste JSON into the client’s MCP config and restart. Servers that need sign-in will prompt on first connect.

Packages
npm@cyanheads/zenodo-mcp-server0.2.0

Tools

6
ParameterTypeRequiredDescription
pageintegerNoResult page, starting at 1. page × size may not exceed 10,000.
sizeintegerNoResults per page (1–25).
sortenumNoSort order: bestmatch, newest, oldest, mostviewed, mostdownloaded, updated-desc, updated-asc. Case, spaces, hyphens, and underscores are ignored when matching (MostViewed reads as mostviewed). Defaults to bestmatch with a query, newest without.bestmatch | newest | oldest | mostviewed | mostdownloaded | updated-desc | updated-asc
awardstringNoOne grant: an award id <funder-ror>::<number> (00k4n6c32::101135562), a bare grant number, or a CORDIS award DOI (10.3030/101135562). Find ids with zenodo_lookup_vocabulary (vocabulary: awards).
querystringNoKeyword query. Terms are OR-ed unless joined with AND; "quoted phrases" match exactly; field syntax such as metadata.title:"…" or doi:"10.5281/zenodo.N" works. Escape a literal / as \/. A bare DOI or record URL is rejected; open it with zenodo_get_record instead. Omit to browse with filters only.
funderstringNoOne funder: a ROR id (01cwqze88), a ROR URL, or a Crossref Funder DOI (10.13039/100000002). Resolve a funder name with zenodo_lookup_vocabulary (vocabulary: funders).
licensestringNoLicense id, e.g. cc-by-4.0, cc0-1.0, mit (lowercased). Find ids with zenodo_lookup_vocabulary (vocabulary: licenses).
communitystringNoOne community: its slug, UUID, or zenodo.org/communities/<slug> URL. Find slugs with zenodo_lookup_vocabulary (vocabulary: communities).
file_typearrayNoFile extensions, OR-ed (a single string is accepted): csv, zip, pdf, … Lowercased with any leading dot stripped.
all_versionsbooleanNoInclude superseded versions; by default only the latest version is searched.
published_tostringNoLatest publication date, YYYY, YYYY-MM, or YYYY-MM-DD; a partial date ends at its last day.
access_statusenumNoAccess status: open, restricted, embargoed, or metadata-only. Case, spaces, hyphens, and underscores are ignored when matching (Metadata Only reads as metadata-only).open | restricted | embargoed | metadata-only
creator_orcidstringNoCreator ORCID iD, e.g. 0000-0002-1825-0097; an https://orcid.org/ prefix is stripped and a trailing x uppercased.
resource_typearrayNoResource type ids, OR-ed (a single string is accepted): dataset, software, publication, publication-article, image-photo, … Any case; a subtype may also be written in the <type>::<id> form zenodo_lookup_vocabulary shows as its search value (publication::publication-article). A top-level type includes its subtypes. Look ids up with zenodo_lookup_vocabulary (vocabulary: resource_types).
published_fromstringNoEarliest publication date, YYYY, YYYY-MM, or YYYY-MM-DD; a partial date starts at its first day.

Details

From the project README

@cyanheads/zenodo-mcp-server

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.

6 Tools

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework

Public Hosted Server: https://zenodo.caseyjhand.com/mcp


Overview

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.

Tools
ToolDescription
zenodo_search_recordsSearch deposits by keyword plus type, community, funder, grant, ORCID, file type, license, access, and date filters, with facet counts
zenodo_get_recordResolve one deposit from a record id, DOI, concept DOI, or URL to its full metadata, first 25 files, and an optional citation
zenodo_list_versionsList every version of a deposit's version series, newest first
zenodo_list_filesPage a deposit's file manifest, or list the members of one of its .zip files
zenodo_read_fileRead a byte-capped text excerpt of one file or .zip member
zenodo_lookup_vocabularyResolve community, funder, grant, license, and resource-type names to the ids search filters take

Capability reference

zenodo_search_records tool
  • Keyword query (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_to
  • Up to 25 hits per page; only the first 10,000 matches are reachable (result_window_exceeded past that); latest versions only unless all_versions is true
  • sort: bestmatch, newest, oldest, mostviewed, mostdownloaded, updated-desc, updated-asc (default bestmatch with a query, newest without)
  • Each hit carries record and concept ids and DOIs, type, version, creators, license ids, access, file totals, and unique views and downloads; facets count resource types, access statuses, file types, subjects, and years over the full match set
  • A bare DOI or record URL as query fails as query_is_identifier; a community or funder Zenodo doesn't know fails as unknown_community / unknown_funder
  • A query 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 instead

zenodo_get_record tool
  • id 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 resolved
  • Returns the description as plain text (up to 4,000 characters), creators and contributors with ORCIDs and ROR affiliations, rights, access and embargo, funding, related identifiers, communities, version position with latest_recid, usage counts, and the first 25 files
  • Every capped field reports its total, and each cut except the file list (paged with zenodo_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 nature
  • A miss returns found: false with miss_kind (not_found, deleted, restricted, not_on_zenodo) and guidance; a deleted record adds its removal tombstone

zenodo_list_versions tool
  • Takes the same id forms as zenodo_get_record; a concept DOI and any version's DOI list the same series
  • Newest first, up to 25 per page; reports total_versions, latest_recid, and the concept record id and DOI
  • Each version carries its record id, DOI, version label, publication date, index, is_latest, file totals, and unique views and downloads
  • Misses return found: false with the same miss_kind, guidance, and tombstone as zenodo_get_record

zenodo_list_files tool
  • Manifest entries carry key, size, MIME type, MD5, download URL, previewable, and listable (a .zip); up to 200 per page via offset / limit
  • archive_key lists the members of one .zip; Zenodo lists at most 1,000 files and directories per archive, flagged by upstream_truncated
  • key_contains filters keys or member paths case-insensitively before paging
  • Restricted and embargoed files return the access status and embargo date with no entries; unknown and deleted records fail as record_not_found / record_deleted

zenodo_read_file tool
  • key from zenodo_list_files, plus archive_member to read inside a .zip without downloading it
  • max_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 only
  • status is text, not_text, restricted, or empty; excerpts end on a line or character boundary
  • Extensionless files typed application/octet-stream (LICENSE, Makefile) are returned only when their content is UTF-8 text
  • Every result carries the deposit's rights and the download_url

zenodo_lookup_vocabulary tool
  • vocabulary: communities, funders, awards, licenses, or resource_types; query by name, acronym, or keyword, or omit it to browse
  • Each entry's filter_param and filter_value name the zenodo_search_records filter and the id to pass it
  • funder scopes awards to one funder, as a ROR id, ROR URL, or Crossref Funder DOI
  • Up to 25 entries per page; only the first 10,000 entries of a list are reachable, reported as reachable beside total

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.

Zenodo-specific:

  • Accepts a record id, Zenodo DOI, concept DOI, external DOI, or zenodo.org / doi.org URL in every tool that takes id; URLs are parsed locally and never fetched
  • Search filters compose into Zenodo's query, so facet counts agree with total; community and funder values are checked before the search runs
  • Separate request pacers for Zenodo's search and general rate-limit buckets, backed by its X-RateLimit-* headers, plus a process-local cache (records 5 min, searches 60 s, vocabularies 1 h)
  • Byte-range file reads and .zip member reads, with binary detection before any text is returned

Agent-friendly output:

  • Misses as data: zenodo_get_record and zenodo_list_versions return found: false with a typed miss_kind, next-step guidance, and a deleted record's tombstone
  • Depositor-supplied text is labeled untrusted: descriptions render as quoted blocks, file content in a code fence, and HTML descriptions are converted to plain text
  • Search responses echo the effectiveQuery sent to Zenodo and the appliedSort, and every paged tool reports totals with a next page or offset
  • Fields Zenodo omits stay absent rather than defaulting to 0, '', or false

Getting started

Public Hosted Instance

A 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.

Self-Hosted / Local

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
Prerequisites
Installation
  1. Clone the repository:
git clone https://github.com/cyanheads/zenodo-mcp-server.git
  1. Navigate into the directory:
cd zenodo-mcp-server
  1. Install dependencies:
bun install
  1. Configure environment:
cp .env.example .env
# optionally set ZENODO_ACCESS_TOKEN

Configuration

VariableDescriptionDefault
ZENODO_ACCESS_TOKENZenodo personal access token, created with no scopes. Raises Zenodo's rate limit; tool behavior and page sizes are unchanged.none
MCP_TRANSPORT_TYPETransport: stdio or http.stdio
MCP_HTTP_PORTHTTP server port.3010
MCP_SESSION_MODEHTTP session mode: stateless, stateful, or auto.stateless
MCP_AUTH_MODEAuthentication: none, jwt, or oauth.none
MCP_LOG_LEVELLog level (debug, info, warning, error, etc.).info
LOGS_DIRDirectory for log files (Node.js only).<app-root>/logs
STORAGE_PROVIDER_TYPEStorage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1.in-memory
OTEL_ENABLEDEnable OpenTelemetry.false

See .env.example for the full list of optional overrides.

Rate limits

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.

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:stdio
    
  • Run checks and tests:

    bun run devcheck  # Lints, formats, type-checks, and more
    bun run test      # Runs the test suite
    
Docker
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.

Project structure

DirectoryPurpose
src/index.tscreateApp() entry point: server instructions, tool registration, service setup and teardown.
src/configServer-specific environment variable parsing and validation with Zod.
src/mcp-server/toolsTool definitions (*.tool.ts) plus shared render, schema, and record-miss helpers.
src/services/zenodoZenodo 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.mdTool surface design, verified upstream behavior, and decisions log.

Development guide

See CLAUDE.md for development guidelines and architectural rules. The short version:

  • Handlers throw, framework catches — no try/catch in tool logic
  • Use ctx.log for logging; Zenodo responses are cached process-wide in the service, not in ctx.state
  • Register new tools in src/mcp-server/tools/definitions/index.ts
  • 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:

bun run devcheck
bun run test

License

This project is licensed under the Apache 2.0 License. See the LICENSE file for details.

Comments

Markdown supported

New to MCP?

From what the protocol is to installing your first server, these answers cover most first questions.

An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.