crossref-mcp-server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@crossref-mcp-serverresolve DOI 10.1038/nature12373"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Tools
Seven tools for working with Crossref data — DOI resolution, full-text search across all scholarly works, outgoing reference lists, and journal, funder, and publisher lookup:
Tool | Description |
| Resolve a DOI to its full Crossref metadata record: title, authors, affiliations, abstract (when deposited), journal, publication date, type, license, full-text links, funder acknowledgements, and outgoing reference count. The author list pages by |
| Search the Crossref works index by free text and/or structured filters. Supports sort, field selection, a per-work author cap, and cursor-based deep paging. |
| Return the outgoing reference list for a DOI — the works cited by this paper, with deposited citation strings and resolved DOIs where available |
| Find Crossref journal records by ISSN or title query; optionally retrieve a page of the journal's most recent works by publication date. Both lists page by offset. |
| Find funders registered in the Crossref Funder Registry by name, bare registry ID, or funder DOI; optionally retrieve a page of funded works. Both lists page by offset. |
| Resolve a Crossref member ID to its publisher record — name, owned DOI prefixes, DOI counts, per-work-type breakdown, and per-category metadata deposit coverage |
| Resolve a DOI prefix (e.g. |
crossref_get_work
Resolve a DOI to its canonical Crossref record.
DOI validated against
10.NNNN/suffixregex before the upstream callReturns title, authors with affiliations, abstract (when deposited), container/journal, publication date, work type, ISSN, license URLs, full-text link URLs, and funder acknowledgements
The author list is paged with
offsetandlimit(default 25, max 500).authorCountis the full deposited total; when authors remain, the response carries anextOffsetto pass back asoffset. Ordinary records fit in a single page — large-collaboration papers deposit thousands of authors, enough to fill a client's context from one record. Only the author list is paged; every other field comes back in full on every page.A funder or an affiliation the publisher asserted through the ROR registry rather than by name carries that identifier as
ror, and noname. The entry still names an organization instead of coming back as an award number with nothing attached to it.A date component Crossref records as unknown — it deposits
nullin place of the number — is omitted rather than reported, so a record with no registered year comes back with no publication date.The publication date is the first of
published,published-print,published-online, andissuedthat names one. The three search tools read the same order withoutissued, which is this tool's alone. A source deposited holding only unknown components names nothing and is passed over; one naming a coarser date than the source behind it still answers, since the four are different facts rather than one fact at four precisions.Outgoing references are reported as a count; the entries themselves come from
crossref_get_referencesIncoming citation count (
is-referenced-by-count) is included; citing works are not — Crossref does not expose that data. Use OpenAlex for citation graphs.
crossref_search_works
Search across ~155M Crossref-registered works.
Free-text
queryplus a structuredfilterobject using Crossref's hyphen-separated key syntax:from-pub-date,until-pub-date,type,funder,issn,member,has-abstract,has-references,has-full-text,directory(useDOAJto restrict to open-access content)Field-specific query parameters scope matching beyond the generic
query:queryTitle,queryAuthor,queryContainerTitle(journal/book name), andqueryBibliographic(whole-citation match to resolve a known reference to its DOI) — all combine with each other and withquerySort by
relevance,is-referenced-by-count,published,deposited, orscorefieldsparameter narrows response payload — useful for large result sets. Names are case-sensitive;DOIis always returned whether or not it is listed, so every result stays resolvable bycrossref_get_work.Each work returns at most
authorLimitauthors (default 25, max 500), withauthorCountreporting that work's full deposited total. A single page of large-collaboration papers can carry tens of thousands of author entries; pass a cut work's DOI tocrossref_get_workto page its whole author list, or raiseauthorLimitto widen the cap here.Offset paging up to ~10K results; deep paging requires
cursor=*on the first call, then pass the returnednextCursortoken. Cursor and offset cannot be combined.A cursor walk ends on the page that omits
nextCursor. Crossref keeps minting a token past the end of a list, so the token is withheld on an empty page rather than relayed — the rule theworks_cursorwalks below follow too. Here that page also carries anoticesaying the walk is complete, becauseworksis this tool's whole payload and an empty page nothing is said about renders as blank text.
crossref_get_references
Fetch the outgoing reference list for a DOI.
Each reference includes its deposited citation string and, where Crossref has resolved it, a DOI for follow-up lookup
Citation strings come back with markup removed — inline emphasis (
<i>,<em>,<small>,<span>), scripts (<sub>,<sup>,<inf>), block boundaries (<p>,<br>,<refersplit />), MathML and TeX formula wrappers, and a whole JATS<mixed-citation>deposited into a free-text field. A bracket comes out only when it is a well-formed tag whose element name is on a closed list, so an angle-bracket span that is not one — a cited URL, a Miller index, a DOI fragment, a bracketed phrase — is returned exactly as deposited. A link (<a>,<ext-link>,<uri>) is decided against its own text: its tags come out where the text already carries what thehrefholds, and stay where thehrefaddresses something the text does not name.Paged with
offsetandlimit(default 100, max 500).referenceCountis the full deposited total; when more remain, the response carries anextOffsetto pass back asoffset. Most works fit in a single page — bibliography records can carry tens of thousands of references.Coverage varies by publisher — pre-2000 literature and non-participating publishers may have no reference list
Single-hop only; agents that need N-hop traversal chain calls explicitly
crossref_search_journals
Find journal records by ISSN or title.
include_works: truealso returns a page of the journal's most recent works by publication dateReturns journal title, publisher, ISSN-L, subject areas, and total DOI count
Title-query results page with
offset;journalsTotalreports the full match count andnextOffsetcarries the input for the following page. The journal works list pages separately withworks_offsetandnextWorksOffset.The two lists have different ceilings: title search allows
offset + rowsup to 100,000, the works list only 10,000. A page that stops at either ceiling carries anoticesaying so — a missing continuation offset would otherwise read as the end of the list.The journal works list also pages by cursor, which has no ceiling: pass
works_cursor="*"and chain thenextWorksCursortoken from each response to read the whole list. A cursor walk starts at the newest work and cannot resume from an offset, and the two cannot be combined —works_cursorwith a nonzeroworks_offsetreturnsworks_cursor_offset_conflict. Each token runs about 1500 characters on both result surfaces, a cost per page rather than per record, so a long walk is cheaper at a highrows.include_worksneeds an unambiguous journal. A title query matching more than one — measured by the upstream match count, not by how many fit on the requested page — returnsambiguous_journal, naming the page's candidates and their ISSNs in the message and incandidateson the error data, alongside the full match count. Pass one back asissn, or narrow the query when the journal you want is not among them.The works list is addressable by ISSN alone, so a matched journal with none registered has no works list to request.
include_worksis then skipped and the response carries anoticesaying so — an absentrecentWorkswould otherwise read as a journal with no works, andtotalDoisis the journal's own DOI count rather than an answer about the lookup. There is no alternative identifier to retry with; usecrossref_search_workswithqueryContainerTitleinstead.
crossref_search_funders
Find funders in the Crossref Funder Registry.
Accepts a name query, a bare registry ID (
100000001), or a full funder DOI (10.13039/100000001, optionally behind adoi:orhttps://doi.org/prefix)include_works: truealso returns a page of works funded by the matched funderReturns funder name, registry ID, country, and alternate names
Name-query results page with
offset;fundersTotalreports the full match count andnextOffsetcarries the input for the following page. The funded works list pages separately withworks_offsetandnextWorksOffset.The two lists have different ceilings: name search allows
offset + rowsup to 100,000, the works list only 10,000. A page that stops at either ceiling carries anoticesaying so — a missing continuation offset would otherwise read as the end of the list.The funded works list also pages by cursor, which has no ceiling: pass
works_cursor="*"and chain thenextWorksCursortoken from each response to read the whole list. A cursor walk starts at the newest work and cannot resume from an offset, and the two cannot be combined —works_cursorwith a nonzeroworks_offsetreturnsworks_cursor_offset_conflict. Each token runs about 1500 characters on both result surfaces, a cost per page rather than per record, so a long walk is cheaper at a highrows. This list counts works funded by the funder's registry descendants, which acrossref_search_worksfilter on{"funder": "10.13039/<id>"}does not.include_worksneeds an unambiguous funder. A name query matching more than one — measured by the upstream match count, not by how many fit on the requested page — returnsambiguous_funderrather than resolving one silently, naming the page's candidates and their registry IDs in the message and incandidateson the error data, alongside the full match count. Pass one back asfunder_doi, or narrow the query when the funder you want is not among them.The Funder Registry supersedes entries, and a deprecated one answers to the same name and abbreviation as its successor while carrying only the works registered against the old ID — so resolving to it hands back an undercount as the answer. A superseded record carries
replacedBywith the superseding registry ID (and the current record carriesreplaces), and the response carries anoticenaming the successor on both thefunder_doiandquerypaths. The replacement is never followed automatically: re-run withfunder_doiset to that ID to get the current entry.
crossref_get_member
Resolve a Crossref member ID to its publisher/organization record.
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_prefixto resolve a DOI prefix to the member ID first
crossref_get_prefix
Resolve a DOI prefix to its owning publisher.
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_memberfor the full recordThe Crossref prefix record is thin by design (owner name and member link only); richer publisher data lives on the member record
Related MCP server: @cyanheads/openalex-mcp-server
Features
Built on @cyanheads/mcp-ts-core:
Declarative tool definitions — single file per tool, framework handles registration and validation
Unified error handling across all tools
Pluggable auth (
none,jwt,oauth)Swappable storage backends:
in-memory,filesystem,Supabase,Cloudflare KV/R2/D1Structured logging with optional OpenTelemetry tracing
STDIO and Streamable HTTP transports
Crossref-specific:
Polite-pool
User-Agentheader injected on every request — priority access granted viaCROSSREF_MAILTOemail address, no API token requiredRetry with exponential backoff on 429 (honoring
Retry-After), 5xx, HTTP 408/504, and network failures. Two failures are not retried: a malformed response body, which an identical request re-serializes, and a request that hitsCROSSREF_TIMEOUT_MS, where every attempt costs the full deadlineUpstream failures arrive classified and with recovery guidance on both result surfaces: rate limit, service unavailable, timeout, and malformed response each say what to do next in
content[]as well as instructuredContentCursor-based deep paging on the works search and on both works sub-resources, for result sets beyond the offset cap
Filter key validation: Crossref uses hyphens (
has-abstract,has-references,from-pub-date); the server enforces correct syntax and surfaces API validation errors with actionable recovery hintsText normalization on every human-readable value returned: character references decoded against the full HTML5 named set (2,125 names) plus the decimal and hex forms, whitespace collapsed to single spaces. Decoding is a single pass, so escaped text stays escaped —
&lt;reads as the literal<, never as<— and a reference has to end in a semicolon, so the bare&inR&Dor in a URL's query string comes back as deposited. The fields publishers deposit as JATS XML — work titles, subtitles, container titles, and abstracts — and the citation strings in a reference list additionally have markup stripped, so an italicized species name reachescontent[]as text instead of an<i>tag and a newline that splits the Markdown heading. Both run one rule: a bracket comes out only when it is a well-formed tag — one inside a MathML or structured-citation region, or one the element-name classification recognizes — so a cited URL, a Miller index, a DOI fragment, and a bracketed phrase come back as deposited. A JATS<alternatives>wrapper holds one object encoded several ways, and its first text-bearing child is the one returned, so a formula deposited as both TeX and MathML renders once instead of twice, and it stands as its own token in the sentence the way a MathML formula does, whichever encoding it kept. A link (<a>,<ext-link>,<uri>) is the one element decided against its own text rather than by name, because that is where the address it carries may or may not already be: the tags come out where the text holds what thehrefholds — verbatim or less its scheme — and stay where thehrefaddresses something the text does not name, so a trial registration deposited as both text andhrefreaches the reader as plain text, while a link showing only a site's homepage over anhrefthat points at one record keeps its tag rather than losing the record. Removing a tag leaves the separator its class calls for: scripts and formula wrappers leave none, because their content continues the token around them (CO<sub>2</sub>readsCO2, notCO 2, and a MathML formula reads as one expression); inline emphasis leaves a space only where the text would otherwise run together, between two word characters or where a sentence ends and the next word begins; block boundaries always leave one. The two surfaces differ in one thing — an element name neither recognizes is structure in a JATS field and is removed, and is presumed content in a citation string and stays. A bracket the rule keeps then has to survive the client's Markdown renderer, which would otherwise consume a kept link down to the text it wraps and drop the address behind it, resolve an escaped character reference, and pair a deposited*with the emphasisformat()writes around a journal title — so every normalized value is escaped on its way intocontent[], and only where a reader would take a character for markup:R&Dand[18F]FDGare untouched while<and<ext-link …>are not. A marker that opens a Markdown block — the19.an abstract begins on, a leading-— is escaped only on the one line that renders a deposited value at column zero, since anywhere else it is inert. Identifiers and machine-format values (DOIs, URLs, ISSNs, prefixes, dates, work types) are returned byte-exact on both surfaces, since they are what a reader copies
Getting started
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/mcpPrerequisites
Bun v1.3.14 or higher (or Node.js v24+).
An email address for
CROSSREF_MAILTOis optional but recommended — Crossref's polite pool grants priority access to clients that identify themselves. No account or token is required.
Installation
Clone the repository:
git clone https://github.com/cyanheads/crossref-mcp-server.gitNavigate into the directory:
cd crossref-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# edit .env and optionally set CROSSREF_MAILTO for polite-pool accessConfiguration
All configuration is validated at startup via Zod schemas in src/config/server-config.ts.
Variable | Description | Default |
| Email address embedded in the polite-pool | — |
| Crossref API base URL. Override for testing against a local proxy. |
|
| Per-request timeout in milliseconds. Also the worst-case wait against an unresponsive upstream — a request that hits the deadline is not retried. |
|
| Transport: |
|
| Port for the HTTP server. |
|
| Auth mode: |
|
| Log level (RFC 5424). |
|
| Directory for log files (Node.js only). |
|
| Enable OpenTelemetry instrumentation. |
|
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:httpRun 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 |
|
|
| Server-specific environment variable parsing and validation with Zod. |
| Tool definitions ( |
| CrossrefService — HTTP client, polite-pool header, retry, pagination helpers. |
| Unit and integration tests mirroring |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storageRegister new tools via the barrel in
src/mcp-server/tools/definitions/index.tsWrap 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 and pull requests are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
Apache-2.0 — see LICENSE for details.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityDmaintenanceEnables AI assistants to search academic literature and retrieve detailed metadata for papers, journals, and funding organizations via the Crossref database. It supports DOI-based lookups and keyword searches to facilitate programmatic access to scholarly research data.7
- Alicense-qualityAmaintenanceAccess the OpenAlex academic research catalog - 270M+ publications through MCP. Supports STDIO and Streamable HTTP.47411Apache 2.0
- AlicenseAqualityCmaintenanceEnables retrieval of academic literature metadata via DOI or search using the Crossref REST API.2MIT
- Alicense-qualityCmaintenanceSearches and retrieves scholarly metadata from the CrossRef REST API, covering over 150 million records across all disciplines, without requiring an API key.MIT
Related MCP Connectors
Query FDA data on drugs, food, devices, and recalls via openFDA. STDIO or Streamable HTTP.
bioRxiv + medRxiv preprint server API
Query SEC EDGAR filings, XBRL financials, and company data through MCP. STDIO & Streamable HTTP.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/cyanheads/crossref-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server