DOI Citation Verifier
<p align="center">
<img src="icon.png" alt="doi-mcp logo" width="128" height="128">
</p>
<h1 align="center">DOI Citation Verifier MCP Server</h1>
<p align="center">
A Model Context Protocol (MCP) server that <strong>prevents citation hallucination</strong> by verifying academic citations against <strong>8 authoritative databases</strong>. Only papers with a DOI are ever verified or returned, so every citation it gives an AI assistant comes with a working <code>https://doi.org/</code> link.
</p>
## š Quick Install
```bash
npx -y github:tfscharff/doi-mcp
```
Or add to your Claude Desktop config:
```json
{
"mcpServers": {
"doi-mcp": {
"command": "npx",
"args": ["-y", "github:tfscharff/doi-mcp"]
}
}
}
```
To pin a release, append its tag: `github:tfscharff/doi-mcp#v7.0.0`. See [Releases](#releases).
## The Problem This Solves
Large language models sometimes "hallucinate" academic citations - citing papers that don't exist, misattributing real titles to wrong authors, or mixing up publication details. This MCP server eliminates that problem by:
1. **8-database verification**: Checks citations across CrossRef, OpenAlex, PubMed, zbMATH, ERIC, HAL, INSPIRE-HEP, and Semantic Scholar
2. **Parallel search**: Queries all databases simultaneously
3. **Comprehensive coverage**: 600+ million publications across all disciplines including STEM, humanities, social sciences, and education
4. **DOI-only results**: A paper counts only if a database returns a DOI for it. Every verified citation, possible match, and search result includes a DOI.
## Features
- **8 Database Search**: CrossRef, OpenAlex, PubMed, zbMATH, ERIC, HAL, INSPIRE-HEP, Semantic Scholar
- **Verify Citations**: Check if a paper with specific details actually exists, one at a time or in batches
- **Find Verified Papers**: Search for real papers on a topic and get only DOI-backed results
- **Strict matching**: A candidate must match the citation's title or an author; a matching year alone never verifies anything
- **Parallel Processing**: All database queries run simultaneously
- **Caching**: Repeated successful queries return instantly (5-minute TTL, 100 entries)
- **Source Selection**: Search all databases or target specific sources
- **Zero Configuration**: No API keys required (an optional Semantic Scholar key avoids its shared rate limit)
- **Failures are reported, not hidden**: When a database errors or rate-limits, responses name it and say why, so a failed lookup is never mistaken for "no such paper"
- **Fully Tested**: 100% line coverage across scoring, caching, HTTP, all 8 database adapters, the tools, and the MCP server itself
## How It Works
When an AI assistant is asked about research or for citations:
1. **Without this MCP**: The assistant might cite "According to Smith et al. (2023) in Nature..." referencing a paper that doesn't exist
2. **With this MCP**: The assistant uses `verifyCitation` first, which searches across 8 databases in parallel and returns:
- Verified match with a DOI ā Can be cited
- No DOI-backed match found ā Cannot cite; must search for real papers instead
If a DOI is supplied, it is resolved directly at doi.org first; the database search is the fallback.
### Scoring
Each candidate returned by the databases is scored against the citation:
| Match | Points |
|---|---|
| Title contains the first 30 characters of the cited title (case-insensitive) | +3 |
| Exact year | +3 (±1 year: +1) |
| Each cited author whose last name appears in the candidate's authors | +2 |
A candidate is only considered if it has a DOI **and** matches on title or at least one author. A score of 3+ verifies the citation; 5+ is reported as high confidence.
### Limitations
- **Papers without a DOI can't be verified.** Some real papers, such as conference proceedings that never received a DOI, will come back `verified: false`. This is deliberate: the server only vouches for citations it can link to.
- **Speed depends on the slowest database.** Most lookups take one to a few seconds; title/author searches wait for all 8 databases.
- **Semantic Scholar rate-limits** requests without an API key: they share one global anonymous quota that is often exhausted. Set `SEMANTIC_SCHOLAR_API_KEY` (see [Installation](#installation)) for a dedicated quota. Without one, a 429 is retried once after a second; if that is also limited, its `RATE_LIMITED` error is reported and the other databases are still used.
- **DBLP was removed in 7.0.0.** dblp.org serves a bot-check page to API clients instead of JSON. Computer-science papers with DOIs are still found through CrossRef and OpenAlex.
- **Each database is asked for 3Ć `limit` results** and trimmed back to `limit` after dropping records without a DOI, so a few DOI-less top hits don't empty a source.
## Tools
All tools are read-only (`readOnlyHint: true`, `destructiveHint: false`, `idempotentHint: true`).
### verifyCitation
**Primary anti-hallucination tool** - Verifies a citation exists before it can be mentioned.
**Input:**
- `title` (string, optional): Paper title (partial matches accepted)
- `authors` (array, optional): Author names (last names sufficient)
- `year` (number, optional): Publication year
- `doi` (string, optional): DOI if known
- `journal` (string, optional): Journal name
**Returns JSON with:**
- `verified`: true/false
- If verified: `confidence` (`high`/`medium`), `source`, `doi`, `doiUrl`, `title`, `authors`, `year`, `journal`, `abstractFromArticle`, `message`
- If not verified: a warning `message`, plus `possibleMatches` (each with a DOI) when there were near misses, and `errors` (`database`, `code`, `message`) for any database that failed, since it may have held the match
**Example successful verification:**
```json
{
"verified": true,
"confidence": "high",
"source": "CrossRef",
"doi": "10.1038/nature14539",
"doiUrl": "https://doi.org/10.1038/nature14539",
"title": "Deep learning",
"authors": ["Yann LeCun", "Yoshua Bengio", "Geoffrey Hinton"],
"year": 2015,
"journal": "Nature",
"message": "ā Citation verified with high confidence"
}
```
### batchVerifyCitations
Verify several citations in one call. Each citation takes the same fields as `verifyCitation`, plus an optional `id` to match results back to inputs.
**Input:**
- `citations` (array): `{ id?, title?, authors?, year?, doi?, journal? }` objects
**Returns:** `summary` (`total`, `verified`, `failed`) and a `results` array, one entry per citation, each with `id`, `verified`, and either `paper` (including `doiUrl`) or a `message`. Unverified entries also carry `errors` for any database that failed.
### findVerifiedPapers
Search for real papers on a topic and return only papers with DOIs.
**Input:**
- `query` (string): Search query (topic, keywords, author names)
- `source` (string, optional): Which database to search - "all" (default), "crossref", "openalex", "pubmed", "zbmath", "eric", "hal", "inspirehep", or "semanticscholar"
- `limit` (number, optional): Number of results per source (1-20, default: 5)
- `yearFrom` (number, optional): Minimum publication year
- `yearTo` (number, optional): Maximum publication year
**Returns:** Up to `limit` papers, deduplicated by DOI, each with `source`, `title`, `authors`, `year`, `journal`, `doi`, and `doiUrl`, plus `errors` for any database that failed.
With no papers, it returns `count: 0`, `sourcesQueried`, `errors`, and a `message` that separates "nothing with a DOI matched" from "these databases failed". If every database queried failed, the result is also flagged `isError`:
```json
{
"count": 0,
"message": "Every database queried failed, so no papers could be checked: Semantic Scholar (HTTP 429: rate limited (...)).",
"sourcesQueried": ["Semantic Scholar"],
"errors": [{ "database": "Semantic Scholar", "code": "RATE_LIMITED", "message": "HTTP 429: rate limited (...)" }]
}
```
**Example:**
```javascript
// Search all 8 databases
findVerifiedPapers({ query: "CRISPR gene editing", limit: 5 })
// Search only PubMed for biomedical papers
findVerifiedPapers({ query: "cancer immunotherapy", source: "pubmed", limit: 10 })
// Search zbMATH for mathematics papers
findVerifiedPapers({ query: "algebraic topology", source: "zbmath" })
// Search ERIC for education research
findVerifiedPapers({ query: "active learning pedagogy", source: "eric" })
// Search HAL for French/European humanities research
findVerifiedPapers({ query: "phenomenology Husserl", source: "hal" })
// Search INSPIRE-HEP for high-energy physics papers
findVerifiedPapers({ query: "Higgs boson", source: "inspirehep" })
```
### Resources and prompt
- `citation://databases` (JSON): the 8 databases with coverage and API URLs
- `citation://guidelines` (Markdown): when and how to use each tool
- `citation-verification-rules` (prompt): rules that tell the assistant to verify before citing and to cite only with a DOI
## Installation
Add to your Claude Desktop config file:
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Linux**: `~/.config/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"doi-mcp": {
"command": "npx",
"args": ["-y", "github:tfscharff/doi-mcp"]
}
}
}
```
Restart Claude Desktop and the server will be available.
To give Semantic Scholar a dedicated quota, add a free [API key](https://www.semanticscholar.org/product/api#api-key-form) to the server's environment:
```json
"doi-mcp": {
"command": "npx",
"args": ["-y", "github:tfscharff/doi-mcp"],
"env": { "SEMANTIC_SCHOLAR_API_KEY": "your-key" }
}
```
### Alternative: Global Install
```bash
npm install -g github:tfscharff/doi-mcp
```
Then use this config:
```json
{
"mcpServers": {
"doi-mcp": {
"command": "doi-mcp"
}
}
}
```
### Alternative: Clone Locally
```bash
git clone https://github.com/tfscharff/doi-mcp.git
cd doi-mcp
npm install
npm run build
```
Config for local install:
```json
{
"mcpServers": {
"doi-mcp": {
"command": "node",
"args": ["/absolute/path/to/doi-mcp/dist/index.js"]
}
}
}
```
## Troubleshooting
### Server not connecting
1. Check Node.js is installed: `node --version` (requires v18+)
2. Check Claude Desktop logs:
- **Windows**: `%APPDATA%\Claude\logs\`
- **macOS**: `~/Library/Logs/Claude/`
- **Linux**: `~/.config/Claude/logs/`
### npx command fails
```bash
npm cache clean --force
```
### Testing locally
```bash
npx @modelcontextprotocol/inspector node dist/index.js
```
## Releases
Every change is published as a [GitHub release](https://github.com/tfscharff/doi-mcp/releases) following [Semantic Versioning](https://semver.org/):
- **Major** (5.0.0 ā 6.0.0): a change in what gets verified or returned, or in the tools' inputs and outputs
- **Minor** (5.0.0 ā 5.1.0): new features, such as a new database or tool
- **Patch** (5.0.0 ā 5.0.1): fixes, docs, and maintenance
See [CHANGELOG.md](CHANGELOG.md) for what changed in each version.
## Development
Development requires Node.js 22.12+ (for Vitest 5). The server itself runs on Node 18+.
```bash
npm install # Install dependencies
npm run build # Compile TypeScript to dist/
npm run dev # Watch mode
npm test # Run tests
npm run test:watch # Tests in watch mode
npm run test:coverage # Tests with coverage report
```
Tests mock all network calls; nothing hits the live APIs.
### Releasing
`dist/` is committed so `npx` can run the server straight from GitHub. Don't commit or tag releases by hand; use:
```bash
npm run ship -- "fix(pubmed): handle missing PMIDs" --body-file notes.md
```
The optional notes file becomes the body of the `CHANGELOG.md` entry and the GitHub release notes. On Windows, npm cuts inline arguments off at the first newline, so `ship` refuses `--body` there; use `--body-file`.
`scripts/ship.mjs` checks the header is a [Conventional Commit](https://www.conventionalcommits.org/), runs the tests, rebuilds `dist/`, bumps the version (`feat` ā minor; `fix`, `docs`, `chore`, etc. ā patch; `!` or `BREAKING CHANGE:` ā major), adds a `CHANGELOG.md` entry, then commits, tags `vX.Y.Z`, pushes, and creates the GitHub release. Use `--dry-run` to preview, or `--as X.Y.Z` to force a version. It only runs on `main` with uncommitted changes.
### Architecture
```
src/
āāā index.ts # Entry point (stdio transport)
āāā server.ts # MCP server setup, Zod input schemas
āāā types.ts # Shared interfaces
āāā scoring.ts # Match scoring algorithm
āāā cache.ts # Result cache (5-min TTL)
āāā http.ts # Fetch utilities with timeouts
āāā tools/ # Tool handlers
ā āāā verifyCitation.ts
ā āāā batchVerifyCitations.ts
ā āāā findVerifiedPapers.ts
āāā databases/ # Database adapters
āāā index.ts # Parallel query orchestrator; drops results without a DOI
āāā crossref.ts
āāā openalex.ts
āāā pubmed.ts
āāā ... (8 adapters total)
```
**Adding a new database:**
1. Create `src/databases/newdb.ts` with `config`, `search()`, and `normalize()`
2. Import and add to `src/databases/index.ts`
3. Add it to the database lists in the tool descriptions in `src/server.ts`
4. Add tests in `tests/databases/newdb.test.ts`
## Example Usage
**Before this MCP (citation hallucination):**
```
User: "Tell me about recent AlphaFold research"
Assistant: "According to Johnson et al. (2024) in Science, AlphaFold3 achieved..."
ā This paper doesn't exist
```
**After this MCP (verified citations only):**
```
User: "Tell me about recent AlphaFold research"
Assistant: [Uses findVerifiedPapers tool]
"According to Abramson et al. (2024) in Nature (https://doi.org/10.1038/s41586-024-07487-w),
AlphaFold 3 predicts..."
ā Real paper with a DOI
```
**Verification catches fake citations:**
```
User: "Can you verify this citation: Smith et al. (2024), 'Quantum AI', Nature"
Assistant: [Uses verifyCitation tool - searches all 8 databases in parallel]
"ā I cannot verify this citation - no matching publication with a DOI
was found in any of the 8 databases. This citation may be incorrect."
```
## Database Coverage
All databases are queried in parallel:
### General Databases
- **CrossRef**: 150+ million scholarly publications across all disciplines
- **OpenAlex**: 250+ million scholarly works across all disciplines
- **Semantic Scholar**: 200+ million papers with AI-powered search
### Specialized Databases
- **PubMed**: 35+ million biomedical and life sciences publications
- **zbMATH**: 4+ million mathematics publications
- **ERIC**: 1.7+ million education research publications
- **HAL**: 4.4+ million French/European scholarly documents (2.5M English)
- **INSPIRE-HEP**: 1.7+ million high-energy physics publications
### Total Coverage
**600+ million publications** across all academic disciplines with specialized depth in STEM, computer science, biomedical sciences, mathematics, and education research. Only records that carry a DOI are used.
## License
MIT
## Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
## Related
### API Documentation
- [CrossRef API](https://www.crossref.org/documentation/retrieve-metadata/rest-api/)
- [OpenAlex API](https://docs.openalex.org/)
- [PubMed API](https://www.ncbi.nlm.nih.gov/home/develop/api/)
- [zbMATH API](https://zbmath.org/api/)
- [ERIC API](https://eric.ed.gov/?api=)
- [HAL API](https://api.archives-ouvertes.fr/docs/)
- [INSPIRE-HEP API](https://github.com/inspirehep/rest-api-doc)
- [Semantic Scholar API](https://www.semanticscholar.org/product/api)
### Resources
- [DOI Handbook](https://www.doi.org/the-identifier/resources/handbook)
- [Model Context Protocol](https://modelcontextprotocol.io)
TDQS
Scored across 3 tools
verifyCitation (single) vs batchVerifyCitations (multiple) are clearly distinguished by cardinality, and findVerifiedPapers is discovery rather than verification. Minor overlap in that findVerifiedPapers also returns verified results, but the input intent (search query vs supplied citation) keeps them separable.
All camelCase with a verb-leading noun phrase (verifyCitation, batchVerifyCitations, findVerifiedPapers), which is readable and predictable. The 'batchVerify' compound verb is a slight deviation from the plain verb_noun pattern but is intuitive.
Three tools is lean but justified: each maps to a distinct workflow (single verify, bulk verify, discover-and-verify). Nothing redundant, though the surface is small enough that it sits near the lower bound.
The set covers the core lifecycle: verify one, verify many, and search for verified papers with DOI URLs. It is missing explicit DOI-based lookup or citation formatting/normalization, but for a verification-focused server the main workflows are covered.