Skip to main content
Glama
cyanheads

crossref-mcp-server

by cyanheads

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework

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


Overview

Scholarly metadata from the Crossref REST API. Resolve DOIs to full metadata records, search across ~155 million scholarly works by free text or structured filters, fetch outgoing reference lists, and look up journals, funders, and publishers. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.

Tools

Tool

Description

crossref_get_work

Resolve a DOI to its full Crossref metadata record: title, authors, editors, affiliations, abstract, journal with volume/issue/pages, ISSNs and ISBNs, publication date, license, full-text links, funder acknowledgements, post-publication updates, and related identifiers

crossref_search_works

Search the Crossref works index by free text and/or structured filters, with field-scoped query parameters, sort, field selection, and offset or cursor-based paging

crossref_get_references

Return the outgoing reference list for a DOI — the works cited by this paper, with citation strings and resolved DOIs where available

crossref_search_journals

Find Crossref journal records by ISSN or title query; optionally retrieve a page of the journal's most recent works

crossref_search_funders

Find funders in the Crossref Funder Registry by name, registry ID, or funder DOI; optionally retrieve a page of funded works

crossref_get_member

Resolve a Crossref member ID to its publisher record — name, owned DOI prefixes, DOI counts, and per-category metadata deposit coverage

crossref_get_prefix

Resolve a DOI prefix (e.g. 10.1038) to its owning publisher, chaining into crossref_get_member

Related MCP server: CrossRef MCP Server

Capability reference

crossref_get_work tool

  • DOI validated against the 10.NNNN/suffix regex before the upstream call, accepted either bare or wrapped in its resolver (https://doi.org/…, https://dx.doi.org/…, doi:…) and unwrapped before the lookup

  • Returns title, authors with affiliations, abstract (when deposited), container/journal, publication date, work type, ISSN, license URLs, full-text link URLs, and funder acknowledgements

  • Citation locators volume, issue, page, and articleNumber under the same names and on the same rendered line as crossref_search_works, plus isbn — each as deposited, omitted when absent

  • editors in the author entry shape, returned whole and never counted in authorCount

  • Author list paged by offset/limit (default 25, max 500); authorCount reports the full deposited total and a nextOffset continues when authors remain — every other field is returned in full on every page

  • updatedBy lists the corrections, retractions, expressions of concern, and new versions Crossref records against the work — notice DOI, type, source (publisher or retraction-watch, with its record ID), and date, entry for entry — and updateTo the works this record is a notice for; when updatedBy is present, notice names each update type with its sources. An absent updatedBy does not mean the work was never updated

  • relations groups related identifiers (preprint ↔ published version, versions, reviews, supplements) by relation type, identifier type, and asserting party, returned whole; only Crossref-registered DOIs among them resolve through this tool

  • A funder or affiliation asserted only through the ROR registry (no name deposited) carries ror in place of name, never as a blank entry

  • Publication date is the first of published, published-print, published-online, and issued (this tool only) that names a value; a date component Crossref records as unknown is omitted, along with everything less precise below it

  • Outgoing references are reported as a count (referencesCount) — entries come from crossref_get_references; incoming citation count (isReferencedByCount) is included, but citing works are not exposed by Crossref — use OpenAlex for citation graphs


crossref_search_works tool

  • Free-text query plus a structured filter object using Crossref's hyphenated keys (from-pub-date, type, funder, issn, has-abstract, license.url, etc.)

  • Field-scoped parameters queryTitle, queryAuthor, queryContainerTitle, and queryBibliographic combine with query and with each other

  • Sort by relevance, score, is-referenced-by-count, published, deposited, or other listed fields — the publication-date sorts page by offset only, since Crossref refuses them with a cursor

  • fields narrows the payload to any of the 17 select names the summary projects — including the citation locators volume, issue, page, article-number, and ISSN — and refuses any other name; DOI is always returned, and crossref_get_work carries the rest of the record

  • A blank or whitespace-only query term or filter value, and an empty sort or order, is read as omitted; when that leaves nothing to search by, the notice says the page is an unfiltered listing

  • authorLimit caps authors per work (default 25, max 500); authorCount reports the full deposited total — chain a cut work's DOI into crossref_get_work for the rest

  • Offset paging is capped at ~10K; cursor="*" starts deep paging via chained nextCursor tokens — cursor and offset cannot be combined

  • A cursor walk ends on the page that omits nextCursor — an empty page never carries one; every empty page's notice names which of the three causes applies


crossref_get_references tool

  • Takes the citing work's DOI bare or resolver-wrapped, the same forms crossref_get_work accepts

  • Each reference carries its deposited citation string and, when Crossref has resolved it, a DOI for crossref_get_work

  • Citation strings have formatting markup stripped and character references decoded; a bracketed span that isn't a recognized tag (a cited URL, a Miller index, a DOI fragment) is left exactly as deposited

  • Paged by offset/limit (default 100, max 500); referenceCount is the full deposited total and nextOffset continues when more remain

  • Coverage varies by publisher — pre-2000 works and non-participating publishers often have no indexed references

  • Single-hop only; incoming citations are not available through Crossref — use OpenAlex for citation graphs


crossref_search_journals tool

  • include_works: true also returns a page of the journal's most recent works — newest published first by works_offset, newest registered first on a works_cursor walk; requires an unambiguous journal — a title query matching more than one returns ambiguous_journal, naming candidates and ISSNs

  • Returns journal title, publisher, ISSN-L, subject areas, and total DOI count

  • Title-query results page by offset (ceiling offset + rows ≤ 100,000); the works list pages separately by works_offset (ceiling ≤ 10,000) — a page that stalls at either ceiling, or an offset past the end of either list, carries a notice naming it

  • works_cursor="*" pages the works list with no ceiling via chained nextWorksCursor tokens; a cursor walk runs by Crossref registration date, newest first (Crossref does not walk a publication-date sort by cursor), and cannot combine with works_offset > 0 (works_cursor_offset_conflict)

  • A matched journal with no ISSN registered has no addressable works list — include_works is skipped with a notice rather than returning an empty list


crossref_search_funders tool

  • Accepts a name query, a bare registry ID (100000001), or a full funder DOI (10.13039/100000001, optionally behind a doi:/https://doi.org/ prefix)

  • include_works: true also returns a page of funded works; requires an unambiguous funder — a name query matching more than one returns ambiguous_funder, naming candidates and registry IDs

  • Returns funder name, registry ID, country, and alternate names

  • Name-query results page by offset (ceiling ≤ 100,000); the funded-works list pages separately by works_offset (ceiling ≤ 10,000) or, with no ceiling, works_cursor="*" chaining nextWorksCursor — offset pages run newest published first, a cursor walk newest registered first, and the two cannot combine (works_offset > 0); an offset past the end of either list carries a notice naming it

  • The funded-works list also counts works funded by the funder's registry descendants, which a crossref_search_works filter on {"funder": "10.13039/<id>"} does not

  • A deprecated registry entry answers to its successor's name while counting only its own works — the response's notice names the superseding ID via replacedBy; the replacement is never followed automatically


crossref_get_member tool

  • Members are the organizations that register DOIs — this answers "what does this publisher publish, and how completely do they deposit metadata?"

  • Returns primary name, alternate imprint names, owned DOI prefixes, DOI counts (total/current/backfile), a per-work-type breakdown, and per-category metadata deposit coverage (references, abstracts, ORCIDs, funders, licenses, and more) as current/backfile fractions

  • Pair with crossref_get_prefix to resolve a DOI prefix to the member ID first


crossref_get_prefix tool

  • Accepts the registrant prefix of a DOI (e.g. 10.1038, no /suffix)

  • Returns the publisher name and numeric member ID — the ID chains directly into crossref_get_member for the full record

  • The Crossref prefix record is thin by design (owner name and member link only); richer publisher data lives on the member record

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.

Crossref-specific:

  • Polite-pool User-Agent header injected on every request — priority access via CROSSREF_MAILTO, keyless otherwise; no API token required

  • Retry with exponential backoff on 429 (honoring Retry-After), 5xx, HTTP 408/504, and network failures; a malformed response body and a request that hits CROSSREF_TIMEOUT_MS are not retried

  • Cursor-based deep paging on the works search and on both works sub-resources, for result sets beyond the offset cap

  • Crossref's rejections of a request come back as declared reasons — unknown_filter (with the hyphenated key when Crossref lists one), invalid_parameter, sort_cursor_conflict, invalid_cursor — carrying the rejected inputs and a recovery hint; a malformed issn filter value is refused before the request, and no upstream error relays Crossref's raw response body

  • Text normalization on every human-readable value: HTML character references decoded and whitespace collapsed; citation strings additionally have formatting markup stripped, so titles and abstracts read as plain text instead of raw JATS XML; a MathML formula keeps its structure — the TeX annotation where the deposit carries one, otherwise a linear form such as x_i, A^{−1}, √(m), (a+b)/c

Agent-friendly output:

  • Provenance — identifiers, URLs, and dates are returned byte-exact while human-readable text is normalized, so a caller can trust doi, issn, and date fields without re-verification

  • Graceful partial failure — an empty page is a success, not an error: an offset past the end of a list, or a query nothing matched, carries a notice saying which, and so does the page that ends a crossref_search_works cursor walk; a journal or funder works walk ends on the page that omits nextWorksCursor

  • Discriminated output contracts — the nextCursor, nextOffset, nextWorksOffset, and nextWorksCursor continuation fields are withheld once a list is exhausted, and a page that stops at an offset ceiling says so in its notice; a cursor token can still ride the last partial page, and the empty page after it carries none

  • Ambiguity handled explicitly — ambiguous_journal and ambiguous_funder list every candidate and its identifier in the error data rather than silently resolving to the first match

Getting started

Public Hosted Instance

A public instance is available at https://crossref.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:

{
  "mcpServers": {
    "crossref-mcp-server": {
      "type": "streamable-http",
      "url": "https://crossref.caseyjhand.com/mcp"
    }
  }
}

Self-Hosted / Local

Add the following to your MCP client configuration file. CROSSREF_MAILTO is optional but recommended — without it the server uses Crossref's anonymous pool with stricter rate limits.

{
  "mcpServers": {
    "crossref-mcp-server": {
      "type": "stdio",
      "command": "bunx",
      "args": ["@cyanheads/crossref-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "MCP_LOG_LEVEL": "info",
        "CROSSREF_MAILTO": "your-email@example.com"
      }
    }
  }
}

Or with npx (no Bun required):

{
  "mcpServers": {
    "crossref-mcp-server": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@cyanheads/crossref-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "MCP_LOG_LEVEL": "info",
        "CROSSREF_MAILTO": "your-email@example.com"
      }
    }
  }
}

Or with Docker:

{
  "mcpServers": {
    "crossref-mcp-server": {
      "type": "stdio",
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "MCP_TRANSPORT_TYPE=stdio",
        "-e", "CROSSREF_MAILTO=your-email@example.com",
        "ghcr.io/cyanheads/crossref-mcp-server:latest"
      ]
    }
  }
}

For Streamable HTTP, set the transport and start the server:

MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 CROSSREF_MAILTO=your-email@example.com bun run start:http
# Server listens at http://localhost:3010/mcp

Prerequisites

  • Bun v1.4.0 or higher (or Node.js v24+).

  • An email address for CROSSREF_MAILTO is optional but recommended — Crossref's polite pool grants priority access to clients that identify themselves. No account or token is required.

Installation

  1. Clone the repository:

git clone https://github.com/cyanheads/crossref-mcp-server.git
  1. Navigate into the directory:

cd crossref-mcp-server
  1. Install dependencies:

bun install
  1. Configure environment:

cp .env.example .env
# edit .env and optionally set CROSSREF_MAILTO for polite-pool access

Configuration

All configuration is validated at startup via Zod schemas in src/config/server-config.ts.

Variable

Description

Default

CROSSREF_MAILTO

Email address embedded in the polite-pool User-Agent header. Optional — server starts without it but logs a warning and uses the anonymous pool with stricter rate limits.

—

CROSSREF_BASE_URL

Crossref API base URL. Override for testing against a local proxy.

https://api.crossref.org

CROSSREF_TIMEOUT_MS

Per-request timeout in milliseconds. Also the worst-case wait against an unresponsive upstream — a request that hits the deadline is not retried.

10000

MCP_TRANSPORT_TYPE

Transport: stdio or http.

stdio

MCP_SESSION_MODE

HTTP session mode: auto, stateful, or stateless. This server needs no multi-round input, so src/index.ts declares stateless; Docker and .env.example pin the same value, and setting this variable overrides the declaration.

stateless (declared in src/)

MCP_HTTP_PORT

Port for the HTTP server.

3010

MCP_AUTH_MODE

Auth mode: none, jwt, or oauth.

none

MCP_LOG_LEVEL

Log level (RFC 5424).

info

LOGS_DIR

Directory for log files (Node.js only).

<project-root>/logs

OTEL_ENABLED

Enable OpenTelemetry instrumentation.

false

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

Running the server

Local development

  • 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

Project structure

Directory

Purpose

src/index.ts

createApp() entry point — registers tools and inits services.

src/config

Server-specific environment variable parsing and validation with Zod.

src/mcp-server/tools

Tool definitions (*.tool.ts).

src/services/crossref

CrossrefService — HTTP client, polite-pool header, retry, pagination helpers.

tests/

Unit and integration tests mirroring src/.

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 request-scoped logging, ctx.state for tenant-scoped storage

  • Register new tools via the barrel 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 (abstracts, reference lists, and affiliations are frequently absent in Crossref records)

Contributing

Issues are welcome. Run checks and tests before submitting:

bun run devcheck
bun run test

License

Apache-2.0 — see LICENSE for details.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Searches and retrieves scholarly metadata from the CrossRef REST API, covering over 150 million records across all disciplines, without requiring an API key.
    23 PyPI
    MIT
  • F
    license
    A
    quality
    B
    maintenance
    Enables searching and retrieving scholarly metadata from Crossref's public REST API, including works, journals, funders, and members, without requiring an API key.
    10
    1
    -
  • A
    license
    A
    quality
    C
    maintenance
    Enables scholarly metadata lookups from the Crossref REST API, including works, members, journals, funders, types, licenses, and prefixes, as tools for LLM clients.
    18
    25 PyPI
    MIT