research-mcp
# Research MCP
Read-only MCP server for academic research. Lets an LLM agent search papers, inspect metadata, follow citation graphs, generate BibTeX, and read full texts of open-access PDFs — all via FastMCP tools.
## Requirements
- Python 3.13+
- [uv](https://docs.astral.sh/uv/)
- Internet access to the public APIs (no API key required)
## How it works
| API | Role |
| --- | ---- |
| **Semantic Scholar** | Primary search, metadata, TLDRs, citation graph. Tried first. |
| **OpenAlex** | Automatic fallback when Semantic Scholar is rate-limited, plus open-access PDF resolution. |
| **arXiv** | Dedicated preprint search (`source="arxiv"`), arXiv-ID lookups, and direct PDF download. Best for finding specific recent papers by exact name. |
| **DBLP** | Venue proceedings surveys (`venue_proceedings`) and venue-scoped searches. The canonical source for "what does this conference accept". |
| **Crossref** | BibTeX generation (`transform/application/x-bibtex`). |
| **PyMuPDF** | PDF text extraction. |
Semantic Scholar is rate-limited when used without a key, so every request retries with exponential backoff (honoring `Retry-After`) before falling back to OpenAlex. Results always tell the agent which source was used. The arXiv API enforces a ~3 s spacing rule, which the server honors; DBLP resets connections on rapid sequential requests, so venue-stream pages are fetched with a 3 s politeness delay too.
### Caching
All brittle API responses, search results, venue proceedings corpora, and PDFs are cached on disk under `~/.cache/research-mcp/` (`api/` for JSON responses, `pdf/` for downloaded PDFs):
- **Fresh cache hits** are served instantly — repeat queries don't touch the network.
- **Venue corpora** (assembled from DBLP pages) are cached as a whole, so re-surveys of a conference program are instant even if DBLP drops a page.
- **Stale fallback**: if an API is down or rate-limiting, the last cached response is served anyway, so the agent still gets an answer.
- Cache TTLs: 7 days for arXiv and BibTeX (stable data), 24 h for search/metadata and DBLP. Override the location with `RESEARCH_MCP_CACHE_DIR`.
## Configuration
Register the server in your coding agent's MCP configuration. For example, in Opencode's `opencode.json`:
```json
{
"mcp": {
"research-mcp": {
"command": [
"uvx",
"--from",
"git+https://github.com/404Simon/research-mcp",
"research-mcp"
],
"enabled": true,
"type": "local"
}
}
}
```
### Environment variables (all optional)
| Variable | Purpose |
| -------- | ------- |
| `SEMANTIC_SCHOLAR_API_KEY` | Set for guaranteed 1 RPS and higher reliability (falls back to OpenAlex otherwise). |
| `RESEARCH_MCP_CACHE_DIR` | Where API responses and PDFs are cached. Default: `~/.cache/research-mcp/`. |
## Tools
### `search_papers`
Search academic papers by keyword with optional venue and year filters. Returns structured results with title, year, venue, authors, DOI, abstract, citation count, and an open-access PDF link when available.
| Argument | Type | Description |
| ------------ | ----------------- | --------------------------------------------------------------------------- |
| `query` | `string` (req.) | Search terms |
| `venue` | `string` (opt.) | Filter by venue, e.g. `e-Energy`, `VLDB` |
| `year_start` | `int` (opt.) | Earliest publication year |
| `year_end` | `int` (opt.) | Latest publication year |
| `limit` | `int` (opt.) | Max results (default 20) |
| `source` | `string` (opt.) | `auto` (default), `semanticscholar`, `openalex`, or `arxiv` |
`source="arxiv"` searches the arXiv preprint API and is the recommended way to find specific recent papers by exact name, e.g. `query='ti:"carbon intensity" AND abs:forecast'` (arXiv field syntax is passed through verbatim). All results are cached, so repeat searches are instant.
### `venue_proceedings`
Survey a venue's proceedings (what actually gets published there). Use this to check whether a paper idea fits a conference like ACM e-Energy.
| Argument | Type | Description |
| -------- | ----------------- | ------------------------------------------------------------------------ |
| `venue` | `string` (req.) | Venue name (e.g. `e-Energy`) or a DBLP stream key (e.g. `conf/eenergy`) |
| `year` | `int` (opt.) | Only papers from one year (e.g. `2024`) |
| `query` | `string` (opt.) | Topic filter; papers are ranked by title-keyword overlap |
| `limit` | `int` (opt.) | Max results (default 20) |
The full venue corpus is fetched from DBLP (paginated past its 100-hit cap), ranked client-side, and cached as a whole — re-surveys are instant.
```json
{"results": [
{"title": "Reinforcement Learning Approach for Optimal Distributed Energy Management in a Microgrid",
"year": 2018, "venue": "IEEE Transactions on Power Systems",
"authors": ["Elham Foruzan", "Leen-Kiat Soh", "S. Asgarpoor"],
"doi": "10.1109/tpwrs.2018.2823641",
"abstract": "In this paper, a multiagent-based model is used ...",
"citation_count": 297, "open_access_pdf": null,
"source": "openalex"}],
"note": "(Semantic Scholar unavailable — used OpenAlex instead: RequestFailed)",
"count": 1}
```
### `paper_details`
Full metadata for a paper. Accepts a DOI (e.g. `10.1109/tpwrs.2018.2823641`), an **arXiv ID** (e.g. `2408.03506`, `arXiv:2408.03506`, or an `arxiv.org/abs/...` URL), or an OpenAlex ID.
| Argument | Type | Description |
| ------------- | ----------------- | ------------------------------------------------------------------ |
| `doi_or_id` | `string` (req.) | DOI, arXiv ID, or OpenAlex ID |
Returns title, authors, venue, year, abstract, TLDR, citation count, DOI, and open-access PDF:
### `get_citation_graph`
Follow a paper's citations forward or backward for snowball searching.
| Argument | Type | Description |
| ----------- | ----------------- | ------------------------------------------------------------------ |
| `paper_id` | `string` (req.) | DOI, OpenAlex ID (`W...`), or Semantic Scholar paper ID |
| `direction` | `string` (opt.) | `citing` (default, papers that cite this one) or `referenced` (its bibliography) |
| `limit` | `int` (opt.) | Max results (default 20) |
### `get_bibtex`
Generate a BibTeX entry for a DOI. Uses Crossref's native transform; falls back to local generation from OpenAlex metadata (handles arXiv DOIs that Crossref doesn't know).
| Argument | Type | Description |
| -------- | ----------------- | ----------- |
| `doi` | `string` (req.) | DOI |
```text
@article{Foruzan_2018, title={Reinforcement Learning Approach for Optimal Distributed Energy Management in a Microgrid}, volume={33}, ..., author={Foruzan, Elham and Soh, Leen-Kiat and Asgarpoor, Sohrab}, year={2018} }
```
### `read_paper_full_text`
Download an open-access PDF and extract its text with PyMuPDF. Pass a DOI, an **arXiv ID** (e.g. `2408.03506`), or a direct PDF URL. PDFs are cached locally (`~/.cache/research-mcp/pdf/`).
| Argument | Type | Description |
| ---------------- | ----------------- | ---------------------------------------------------------------------- |
| `doi_or_pdf_url` | `string` (req.) | DOI, arXiv ID, or `https://...` PDF URL |
| `max_chars` | `int` (opt.) | Truncate returned text (default 50000) |
| `cache` | `bool` (opt.) | Cache the PDF locally (default `true`) |
Returns the extracted text, page count, source URL, and cached path. If no open-access copy is found, the agent gets a clear message telling it to search for an OA copy or pass a PDF URL directly.
## File structure
```text
src/
main.py # FastMCP server, tool definitions, fallback orchestration
client.py # HTTP client with retry/backoff (429/5xx, Retry-After)
cache.py # Disk cache (~/.cache/research-mcp/) with stale fallback
semanticscholar.py # Semantic Scholar: search, details, citations (optional API key)
openalex.py # OpenAlex: search, details, citation graph, OA-PDF resolution
arxiv.py # arXiv API: search, ID lookups (3 s politeness, 7-day cache)
dblp.py # DBLP: venue proceedings + venue-scoped search (paged corpus cache)
crossref.py # Crossref: BibTeX transform + local fallback generator
pdf.py # PDF download/validation/caching + PyMuPDF text extraction
```
No API key required. Run `uv sync && uv run research-mcp` to start the server over stdio.
TDQS
Scored across 6 tools
Each tool targets a clearly distinct research workflow step: keyword search, metadata lookup by ID, citation graph traversal, venue-based survey, BibTeX generation, and full-text extraction. Even where search_papers supports a venue filter, venue_proceedings is clearly positioned as venue-first exploration, so an agent can reliably pick the right tool.
All names are readable and consistently snake_case, but they mix conventions: search_papers, get_citation_graph, get_bibtex, and read_paper_full_text are verb-led, while paper_details and venue_proceedings are bare noun phrases. This is a mixed pattern rather than a truly consistent verb_noun schema.
Six tools is well within the ideal range for a research assistant server. Each tool covers a distinct capability and none feels redundant or unnecessary for the stated domain of academic paper discovery and reading.
The tool surface covers the core academic research lifecycle: finding papers by keyword or venue, retrieving detailed metadata, exploring citations both forward and backward, reading full text, and generating BibTeX entries. There are no obvious dead ends or missing operations that would prevent an agent from completing a typical literature research workflow.