mcp-arxiv-research-server
<p align="center"><img src="assets/hero.svg" alt="mcp-arxiv-research-server: a paper, its citation graph, a tool call and the BibTeX it produces (animated)" width="100%"></p>
<p align="center">
<img src="https://img.shields.io/badge/MCP-server-B31B1B?style=flat-square&labelColor=1a1a1a" alt="MCP">
<img src="https://img.shields.io/badge/TypeScript-Node%2020%2B-B31B1B?style=flat-square&logo=typescript&logoColor=white&labelColor=1a1a1a" alt="TypeScript">
<img src="https://img.shields.io/badge/arXiv%20%C2%B7%20Semantic%20Scholar-B31B1B?style=flat-square&labelColor=1a1a1a" alt="arXiv and Semantic Scholar">
<img src="https://img.shields.io/badge/BibTeX-105%2F105%20compile-B31B1B?style=flat-square&labelColor=1a1a1a" alt="BibTeX compiles">
<img src="https://img.shields.io/badge/tests-18%20passing-B31B1B?style=flat-square&labelColor=1a1a1a" alt="tests">
<img src="https://img.shields.io/badge/license-MIT-B31B1B?style=flat-square&labelColor=1a1a1a" alt="MIT">
</p>
<h3 align="center"><i>A research desk for Claude: search arXiv, see who cites a paper,<br>export BibTeX that compiles, and keep a reading list between conversations.</i></h3>
<p align="center"><b>Eng. Jaafar Salim</b><br><sup>University of Technology, Iraq</sup></p>
---
> **Abstract.** Large language models are good at reading papers and bad at remembering which papers exist. This [Model Context Protocol](https://modelcontextprotocol.io) server gives Claude seven tools over the arXiv API and the Semantic Scholar Graph API. It returns search results in about **one fifth of the tokens** of the raw arXiv feed, obeys arXiv's *one request every three seconds* rule and caches answers, which cut a 16-call research session from **36.1 s to 21.1 s**, and exports BibTeX that compiled **105 of 105** real arXiv records with **zero** LaTeX errors, where copying the fields as they are produced two errors and silently lower-cased acronyms in 28 titles. Every number below is reproducible offline from recorded API responses.
---
## 1 · Introduction
Ask a model for "five papers on X" and some of them will not exist. The fix is not a better prompt; it is a tool that can only return what the library returned. With this server, Claude cites by arXiv id, and every id came from arXiv itself.
| Tool | What Claude gets | Source |
|---|---|---|
| `search_arxiv` | ranked results: id, title, year, authors, category, journal, start of abstract. Paging, sorting, category filter, arXiv field syntax (`ti:`, `au:`, `cat:`) | arXiv |
| `get_paper` | full metadata and abstract for up to 20 papers, from any id spelling (`1706.03762v5`, `arXiv:…`, abs or pdf URLs, `hep-th/9901001`) | arXiv |
| `find_citations` | citation count, influential citations, citing papers per year, the most-cited or newest citing papers | Semantic Scholar |
| `export_bibtex` | a `.bib` for up to 200 papers: stable keys, escaped specials, math kept, acronyms protected | arXiv |
| `library_add` · `library_list` · `library_remove` | a reading list with tags and notes, saved in a JSON file, also exportable as BibTeX | local |
All read tools carry `readOnlyHint`; `library_remove` carries `destructiveHint`, so a client can ask before using it.
## 2 · Installation
```bash
git clone https://github.com/jafarSalim/mcp-arxiv-research-server.git
cd mcp-arxiv-research-server
npm install # also compiles to dist/
```
**Claude Desktop** (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"arxiv": {
"command": "node",
"args": ["/absolute/path/to/mcp-arxiv-research-server/dist/src/index.js"],
"env": { "S2_API_KEY": "optional, raises the Semantic Scholar rate limit" }
}
}
}
```
**Claude Code:** `claude mcp add arxiv -- node /absolute/path/to/mcp-arxiv-research-server/dist/src/index.js`
Then, for example: *"Find recent arXiv papers on deep-learning channel estimation for OFDM, read the three most relevant abstracts, tell me who cites the best one, and give me BibTeX for all three."*
| Variable | Default | Meaning |
|---|---|---|
| `ARXIV_INTERVAL_MS` | `3000` | gap between arXiv requests ([arXiv's terms](https://info.arxiv.org/help/api/tou.html) ask for one every 3 s) |
| `S2_API_KEY` | none | Semantic Scholar key; without it the gap is 3 s, with it 1 s |
| `ARXIV_CACHE_DIR` | `~/.arxiv-research/cache` | on-disk cache; `off` keeps it in memory only |
| `ARXIV_CACHE_TTL_HOURS` | `24` | how long an answer is reused; `0` turns caching off |
| `ARXIV_LIBRARY` | `~/.arxiv-research/library.json` | the reading list |
## 3 · Method
```mermaid
%%{init: {"theme":"base","themeVariables":{"actorBkg":"#ffffff","actorBorder":"#B31B1B","actorTextColor":"#1a1a1a","signalColor":"#1a1a1a","signalTextColor":"#1a1a1a","noteBkgColor":"#f6f1e6","noteTextColor":"#1a1a1a","noteBorderColor":"#B31B1B","fontFamily":"Georgia, serif"}}}%%
sequenceDiagram
participant C as Claude
participant S as this server
participant K as cache (disk)
participant A as export.arxiv.org
C->>S: search_arxiv("channel estimation", category eess.SP)
S->>K: same URL in the last 24 h?
K-->>S: no
Note over S,A: wait until 3 s after the previous arXiv request
S->>A: GET /api/query?search_query=(all:channel AND all:estimation) AND cat:eess.SP
A-->>S: Atom XML (about 2.5 KB per paper)
S->>K: store
S-->>C: compact list, about 120 tokens per paper
C->>S: same question later
S->>K: hit
S-->>C: answer, no request sent
```
**Politeness.** Requests to each host are queued one at a time with a minimum gap (3 s for arXiv, 1 s or 3 s for Semantic Scholar). `429` and `5xx` answers are retried with back-off, honouring `Retry-After`.
**Parsing.** arXiv's Atom feed is parsed with every field kept as text, so a paper whose title is literally `0` ([2104.12255](https://arxiv.org/abs/2104.12255)) stays `"0"` instead of becoming a number. arXiv's own error feeds (for example *incorrect id format for abc*) become readable error messages.
**BibTeX.** One `@misc` entry per paper in the form arXiv recommends (`eprint`, `archivePrefix`, `primaryClass`), plus `howpublished` and `note` for classic BibTeX styles that ignore `eprint`:
```bibtex
@misc{gould2006frequency,
title = {Frequency of Hot Jupiters and Very Hot Jupiters from the {OGLE-III} Transit Surveys Toward the Galactic Bulge and Carina},
author = {Andrew Gould and Susan Dorsher and B. Scott Gaudi and Andrzej Udalski},
year = {2006},
month = jan,
eprint = {astro-ph/0601001},
archivePrefix = {arXiv},
primaryClass = {astro-ph},
url = {https://arxiv.org/abs/astro-ph/0601001},
howpublished = {arXiv:astro-ph/0601001 [astro-ph]},
note = {Published in ActaAstron.56:1-50,2006}
}
```
| Problem in real titles | What the exporter does |
|---|---|
| `&`, `%`, `_`, `#`, `$` outside math | escaped: `\&`, `\%`, … |
| `$O(n^2)$` inside a title | left exactly as written |
| Greek letters (`ω`), which pdfLaTeX cannot typeset from UTF-8 | `$\omega$`, or `\omega` inside math |
| acronyms and CamelCase (`OGLE-III`, `DNA`, `WebView`), which BibTeX styles lower-case | wrapped in braces |
| two papers by the same first author, same year, same first word | keys `vaswani2017attention`, `vaswani2017attentiona` |
| accents in keys (`Müller-Łukasz`) | folded to `mullerlukasz` |
## 4 · Experiments
**Data.** The arXiv service is not reachable from the machine these measurements ran on, so every experiment replays **real recorded responses**: 105 arXiv papers recorded by the [arxiv.py](https://github.com/lukasschwab/arxiv.py) project in April 2026 (a 100-result search for *testing*, its pages, and single-id lookups), and the Semantic Scholar record of Turing's *Computing Machinery and Intelligence* (1950) with 1,000 of its citing papers, recorded by the [semanticscholar](https://github.com/danielnsilva/semanticscholar) project. Both are MIT-licensed; their licences are in [`test/fixtures`](test/fixtures). A small [replay server](test/replay.ts) plays them back on localhost, and the server under test talks to it over real HTTP, launched as a child process and driven by the official MCP TypeScript client.
The recordings had stored arXiv's UTF-8 as Latin-1 (`Wübben` saved as `Wübben`, because arXiv sends no charset). The replay server repairs that; the server itself always decodes UTF-8.
### 4.1 · Tokens the model has to read
<p align="center"><img src="results/fig1_tokens.png" alt="Figure 1" width="88%"></p>
<p align="center"><sup><b>Figure 1.</b> Tokens for the same search results, as the raw arXiv Atom feed and as returned by <code>search_arxiv</code> at three abstract lengths.</sup></p>
| Results | Raw Atom XML | `search_arxiv`, full abstracts | **default (300 characters)** | titles only |
|---:|---:|---:|---:|---:|
| 10 | 5,976 | 2,985 | **1,238** (4.8× fewer) | 687 |
| 25 | 13,822 | 7,014 | **2,793** | 1,407 |
| 50 | 27,534 | 14,123 | **5,316** | 2,585 |
| 100 | 55,079 | 27,737 | **10,673** (5.2× fewer) | 5,233 |
Even with complete abstracts the compact format is half the size, because the XML repeats namespaces, links and tags for every paper. The usual flow (scan 10 results, then `get_paper` on the two or three that matter) costs about 1,200 + 3 × 350 tokens instead of 6,000.
What the model actually sees (first three of ten):
```text
214,881 arXiv results for "testing" (showing 1–10).
1. [2202.12139v1] Testing Deep Learning Models: A First Comparative Study of Multiple Testing Techniques (2022)
Mohit Kumar Ahuja, Arnaud Gotlieb, Helge Spieker · cs.SE · Artificial Intelligence in Software Testing @ 2022 IEEE …
Deep Learning (DL) has revolutionized the capabilities of vision-based systems (VBS) in critical applications such as …
2. [2405.13786v1] Towards Explainable Test Case Prioritisation with Learning-to-Rank Models (2024)
Aurora Ramírez, Mario Berrios, José Raúl Romero et al. (4 authors) · cs.SE · Proc. 2023 IEEE International Conference …
Test case prioritisation (TCP) is a critical task in regression testing to ensure quality as software evolves. …
3. [2005.14124v2] Active Fuzzing for Testing and Securing Cyber-Physical Systems (2020)
Yuqi Chen, Bohan Xuan, Christopher M. Poskitt et al. (5 authors) · cs.SE · In Proc. ACM SIGSOFT International …
```
<sup>Lines shortened here for width. Token counts use the open Llama 4 tokenizer as a stand-in, since Claude's tokenizer is not public; the ratios are what matter.</sup>
### 4.2 · A research session, with and without the cache
A scripted session of **16 tool calls** of the kind a conversation produces: two result pages, three abstracts, a repeated question, BibTeX for three papers, citations of one paper (twice, sorted two ways), two reading-list additions, a 15-result search, a second BibTeX export, and the reading list as BibTeX. arXiv's 3-second gap was enforced in both runs.
<p align="center"><img src="results/fig2_session.png" alt="Figure 2" width="95%"></p>
<p align="center"><sup><b>Figure 2.</b> Timeline of the 16 calls. Every arXiv request must wait three seconds after the previous one; cached answers skip both the request and the wait.</sup></p>
| | Wall time | arXiv requests | Semantic Scholar requests |
|---|---:|---:|---:|
| No cache | 36.1 s | 13 | 4 |
| **With cache** | **21.1 s** | **8** | **2** |
Five arXiv requests are saved: the repeated search, two papers that were looked up and then added to (or looked up again from) the reading list, and two BibTeX exports for papers already fetched; the second citation query saves both Semantic Scholar requests. Against the real API the saving is larger, because each skipped request also skips the network round trip. The cache is on disk, so it also survives restarting Claude.
### 4.3 · Does the BibTeX compile?
All 105 recorded papers were exported through the `export_bibtex` tool and compiled with pdfLaTeX and BibTeX (TeX Live 2023) in two styles, `plain` and `plainnat` with natbib, by [`scripts/compile_bib.sh`](scripts/compile_bib.sh). The baseline writes the same fields as they are, which is what a quick script, or a model writing BibTeX from the metadata, usually does.
| | Entries | BibTeX errors | BibTeX warnings | **LaTeX errors** | Titles with acronyms lower-cased |
|---|---:|---:|---:|---:|---:|
| Fields copied as they are | 105 | 0 | 0 | **2** (`&` in a title, a Greek `ω`) | 28 of 105 |
| **`export_bibtex`** | 105 | 0 | 0 | **0** | **0** |
Both styles gave the same counts. The two LaTeX errors stop a strict build; the lower-cased acronyms are worse, because nothing reports them:
<p align="center"><img src="results/fig3_bibtex.png" alt="Figure 3" width="100%"></p>
<p align="center"><sup><b>Figure 3.</b> The same three entries typeset with the <code>plain</code> style. (a) <i>ogle-iii</i>, <i>dna</i>, and the ω of <i>ωTest</i> dropped. (b) as published.</sup></p>
### 4.4 · Citations
`find_citations("10.1093/mind/lix.236.433")` on the recorded Semantic Scholar data returns:
```text
Computing Machinery and Intelligence (1950)
Cited by 10,059 papers (530 "influential" according to Semantic Scholar), cites 14 papers.
The analysis below uses the 1,000 citing papers returned in one request, not all 10,059.
Citing papers per year (sample, last 8 years present): 2022: 353, 2023: 499, 2024: 148
Most-cited citing papers:
1. LLaMA: Open and Efficient Foundation Language Models (2023) — Hugo Touvron, Thibaut Lavril et al. (14 authors) · cited 5,708× · arXiv:2302.13971
2. A Survey of Large Language Models (2023) — Wayne Xin Zhao, Kun Zhou et al. (22 authors) · cited 1,114× · arXiv:2303.18223
3. What if the devil is my guardian angel: ChatGPT as a case study of using chatbots in education (2023) — A. Tlili, Boulus Shehata et al. (7 authors) · cited 409×
…
```
<p align="center"><img src="results/fig4_citations.png" alt="Figure 4" width="100%"></p>
<p align="center"><sup><b>Figure 4.</b> The 1,000-paper sample of works citing Turing (1950): the most-cited of them, and their years. The sample is the most recent part of the list, which is why it starts in 2022.</sup></p>
The tool says plainly when it is working from a sample, so Claude does not present 1,000 as the total. Citing papers that are on arXiv carry their id, so the next step can be `get_paper` on them.
### 4.5 · What the recorded search covers
```mermaid
%%{init: {"theme":"base","themeVariables":{"pie1":"#B31B1B","pie2":"#1a1a1a","pie3":"#8c8c8c","pie4":"#d9a3a3","pie5":"#6b5a45","pie6":"#e9e4da","pieStrokeColor":"#ffffff","pieSectionTextColor":"#ffffff","pieTitleTextColor":"#1a1a1a","pieOuterStrokeColor":"#1a1a1a","fontFamily":"Georgia, serif"}}}%%
pie showData
title Primary category of the 100 results for "testing"
"cs.SE software engineering" : 64
"cs.LG machine learning" : 4
"cs.IT information theory" : 4
"math.ST statistics" : 4
"cs.DS data structures" : 3
"18 other categories" : 21
```
Two thirds are software engineering; the other third is spread over 22 categories, from statistics to information theory. A one-word query is ambiguous; `category: "cs.SE"` or `ti:` narrows it, and the tool description tells Claude so.
## 5 · Tests
```bash
npm test # 18 tests: 11 unit, 7 end-to-end over stdio with the official MCP client
```
```mermaid
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#ffffff","primaryBorderColor":"#B31B1B","primaryTextColor":"#1a1a1a","lineColor":"#1a1a1a","fontFamily":"Georgia, serif"}}}%%
flowchart LR
F["recorded responses<br/>arxiv.py · semanticscholar<br/>MIT"] --> R["replay server<br/>localhost HTTP"]
R <--> S["this server<br/>child process"]
S <-- "stdio JSON-RPC" --> C["official MCP client<br/>@modelcontextprotocol/sdk"]
C --> T["18 assertions<br/>tools · paging · ids · errors<br/>503 retry · cache · library"]
```
They cover every id spelling, all 105 recorded entries parsing, the paper titled `0`, arXiv error feeds, LaTeX escaping and case protection, unique keys and balanced braces across all 105 entries, the per-host request gap and `503` retry (with a fake clock), the disk cache surviving a new process, the reading list surviving a reload, and an unknown paper giving a useful message instead of a stack trace.
```bash
npm run bench # → results/bench.json, results/outputs/, results/bibtex/all.bib (about 1 minute)
./scripts/compile_bib.sh # → results/bibtex/compile.txt and the PDFs
python3 scripts/charts.py # → results/fig*.png (needs: pip install tiktoken matplotlib pillow)
```
## 6 · Limitations
* **Recorded, not live, in this repository's measurements.** The live code path is the same one the tests use, with different base URLs, but the numbers above come from April 2026 recordings. Real latency adds network time on top.
* **arXiv only** for search and BibTeX. Papers that never appeared on arXiv are reachable through `find_citations` (by DOI) but not through `get_paper`.
* Case protection covers acronyms and CamelCase, not ordinary proper nouns: *Android* and *Markov* still come out lower-case in `plain`. Add braces by hand for those, or use a style that keeps title case.
* `find_citations` reads one page of up to 1,000 citing papers. For heavily cited papers it is a sample, and it says so.
* Without an API key, Semantic Scholar shares a public rate limit; under load it may answer `429` even after retries.
## 7 · Layout
```text
mcp-arxiv-research-server/
├── src/
│ ├── server.ts the seven tools, configuration, error messages written for the model
│ ├── arxiv.ts id parsing, query building, Atom parsing
│ ├── s2.ts Semantic Scholar ids, URLs, citation summary
│ ├── bibtex.ts keys, LaTeX escaping, math, Greek, case protection
│ ├── http.ts per-host queue, minimum gap, retries, disk cache
│ ├── library.ts the reading list
│ ├── format.ts what the model reads
│ └── index.ts stdio entry point
├── test/ unit + end-to-end tests, replay server, recorded fixtures (with their licences)
├── bench/run.ts the experiments in section 4
└── scripts/ compile_bib.sh, charts.py
```
---
<p align="center"><i>Every citation Claude gives you should exist. This makes sure it does.</i><br>
<b>Eng. Jaafar Salim</b> · <a href="https://github.com/jafarSalim">github.com/jafarSalim</a> · if it saves you a hallucinated reference, a ⭐ helps others find it.</p>
TDQS
Scored across 7 tools
Most tools target distinct actions: search discovery, metadata retrieval by id, citation lookup, BibTeX export, and library CRUD. The only real overlap is BibTeX output, which both library_list and export_bibtex can produce, though the descriptions frame them differently (reading-list dump vs. .bib export for arbitrary paper sets).
Two conventions coexist: verb_noun for arXiv operations (search_arxiv, get_paper, find_citations, export_bibtex) and noun_verb for the local library (library_list, library_add, library_remove). Readable and arguably intentional grouping, but not a single predictable pattern.
Seven tools is well-scoped for an arXiv research workflow: discovery, retrieval, citation analysis, export, and a three-operation local reading list. No tool feels redundant or tacked on.
Core research lifecycle is covered end-to-end: search, fetch metadata, check citations, export BibTeX, and save/remove from a reading list, with library_add acting as an upsert for tags/notes. Minor gaps: no way to update or inspect a single saved entry without listing, and find_citations only covers citing papers, not the paper's own references.