Metadata-Version: 2.5
Name: arxiv-mcp-lean
Version: 0.7.0
Summary: A Model Context Protocol server for arXiv paper search and retrieval
Project-URL: Homepage, https://github.com/Ray0907/arXiv-mcp
Project-URL: Repository, https://github.com/Ray0907/arXiv-mcp
Author: Ray Tien
License: MIT
License-File: LICENSE
Keywords: ai,arxiv,claude,mcp,papers,research
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Requires-Dist: httpx>=0.28.0
Requires-Dist: mcp>=2.0.0
Requires-Dist: openai-mcp-extensions>=0.1.0
Requires-Dist: pydantic>=2.10.0
Description-Content-Type: text/markdown

# arXiv MCP Server

A Model Context Protocol (MCP) server that provides arXiv paper search and retrieval capabilities. This server enables LLMs to search for academic papers on arXiv and get titles, abstracts, authors, and content from the official arXiv API.

## Features

- Search papers by query, author, category, and date
- Advanced search with specific field filters
- Compact search: optional abstract character caps without changing full-result defaults
- Get detailed paper metadata, batch lookups and metadata-only BibTeX citations
- Read outlines, named sections or fixed-size chunks with a strict character cap
- Retrieve full paper content through server-backed MCP resource links without embedding large papers in tool results
- Browse recent papers by category with stateless inclusive UTC date windows
- Discover references and citing papers through Semantic Scholar
- List all arXiv categories
- Pagination support for search results

## Available Tools

### `search`
Search arXiv for papers matching a query.

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `query` | string | Yes | Search query (e.g., 'LLM', 'transformer') |
| `category` | string | No | Filter by category (e.g., 'cs.AI', 'cs.LG') |
| `author` | string | No | Filter by author name |
| `sort_by` | string | No | Sort order: 'relevance', 'date_desc', 'date_asc', 'updated_desc', 'updated_asc' |
| `page` | int | No | Page number (default: 1) |
| `page_size` | int | No | Results per page, max 50 (default: 25) |

### `search_advanced`
Advanced search with specific field filters.

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `title` | string | No | Search in paper titles |
| `abstract` | string | No | Search in abstracts |
| `author` | string | No | Search by author name |
| `category` | string | No | Filter by category |
| `id_arxiv` | string | No | Restrict to one arXiv ID; combines with the other filters |
| `date_from` | string | No | Start of submission date range (YYYY-MM-DD) |
| `date_to` | string | No | End of submission date range (YYYY-MM-DD) |
| `sort_by` | string | No | Same options as `search` |
| `page` | int | No | Page number |
| `page_size` | int | No | Results per page |

`search` and `search_advanced` also accept `abstract_max_chars`: omit it for full abstracts (the default), set `0` for empty abstract text, or a positive integer to truncate each abstract to that many characters. The same option works on `get_recent`. All other paper fields and pagination stay intact; cached metadata and `get_paper` always retain full abstracts. Defaults remain 25 search results and 10 recent papers.

### `get_paper`
Get detailed information about a specific arXiv paper. Optional `primary_category`, `comment`,
`journal_ref`, `doi` and integer `version` come directly from the Atom feed and are omitted when absent.

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `id_or_url` | string | Yes | arXiv ID (e.g., '2301.00001') or full URL |

### `get_papers`
Look up 1–50 IDs or arXiv URLs in a single `id_list` API request. Returns `papers` in request
order and `missing_ids` for IDs absent from the response (including unavailable/withdrawn papers).
A withdrawn paper with metadata is still returned. Invalid IDs or an out-of-range batch size fail
locally. Exact duplicates are collapsed; legacy pinned IDs use latest metadata, as in `get_paper`.

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `ids` | list of strings | Yes | 1–50 arXiv IDs or URLs |

### `export_bibtex`
Export API-derived UTF-8 BibTeX as `{bibtex, missing_ids}`; no citation text is invented.
Accepts `ids` (1–50 IDs/URLs), deduplicates versions and looks up the latest metadata using
`get_papers`. Stable cite keys are `arxiv:<versionless ID>` (e.g. `arxiv:hep-th/9901001`).
Entries use `@misc`, arXiv `eprint`/`archivePrefix`/`primaryClass`, and DOI when supplied.
The complete, unparsed `journal_ref` goes in `note` rather than guessing journal/volume/pages.
BibTeX special characters are escaped as literal text (including raw LaTeX); Unicode names
and titles are retained, for UTF-8-capable bibliography tools such as Biber.

### `get_content`
Validate the paper ID locally (no HTTP request), then return an `arxiv://` MCP resource link. Clients read the link through `resources/read`; only that read fetches the text through Jina Reader: the arXiv HTML rendering first, then the canonical PDF when no usable HTML exists. Paper text is never embedded in the tool result. Upstream failures, including a missing PDF (HTTP 404), surface as readable resource-read errors rather than tool-validation errors. A server-backed custom URI is used because MCP does not require every client to fetch external `https://` resource links directly.

Full text is cached per process by paper ID, keeping versions distinct: up to 32 papers, expiring after 1 hour for unversioned IDs or 24 hours for pinned `vN` IDs. Abstract/PDF URL aliases share the same cached text. HTML is used only when it looks like a real paper (at least 2,000 characters and two headings); stubs, error pages and failures fall back to the PDF. The source chosen for an ID is remembered for the process (up to 1,024 IDs, least recently read dropped first), so a refetch after cache expiry never changes the text under earlier `section:N` or `offset` values; if that source later fails, the read returns an error instead of switching. Availability and extraction quality still depend on the upstream services, especially for withdrawn papers.

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `id_or_url` | string | Yes | Valid arXiv ID or arxiv.org abstract/PDF URL; Jina reads the arXiv HTML rendering, or the canonical PDF if that is unusable, not the abstract page |

### `read_paper`
Read a bounded text view using the same full-text cache as `get_content`. By default, return
Markdown headings (with `section:N` selectors), chunk availability and the API abstract.
Select a heading by case-insensitive name or unique partial match; ambiguous names raise an
error rather than silently returning the wrong section. A section includes its subsections.
ATX (`#`) and Setext headings are recognized; fenced code is ignored.

Headings come from the arXiv HTML rendering when it exists, which usually gives a fuller outline than PDF extraction (papers without HTML fall back to the PDF, which can have very few headings). `chunk:N` always provides a fallback: 1-based,
fixed 10,000-character slices of the complete original text, including any preamble. No headings
are guessed from unmarked text. The outline lists the available chunk range.

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `id_or_url` | string | Yes | Valid arXiv ID or abstract/PDF URL |
| `section` | string | No | Heading name, `section:N` or `chunk:N`; omitted returns outline plus abstract |
| `max_chars` | int | No | Character cap including truncation marker (default 12000, minimum 256) |
| `offset` | int | No | Zero-based character offset within the selected view (default 0) |

A truncated response says how to continue: keep `id_or_url`, `section`, and `max_chars` unchanged
and use the indicated `offset`. This works for long sections, chunks and outlines. Character
counts are Unicode characters, not bytes or tokens. The minimum cap leaves room for an actionable
marker. The outline needs API metadata as well as full text; section/chunk reads only need the full text.

### `get_recent`
Get recent papers from a specific arXiv category.

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `category` | string | No | Category code (default: 'cs.AI') |
| `count` | int | No | Maximum papers, clamped to 1–50 (default: 10) |
| `since` | string | No | Inclusive submission date, strict `YYYY-MM-DD`, through today in UTC; future dates rejected |
| `abstract_max_chars` | int | No | Abstract character cap; `0` returns empty text, omitted/`null` keeps full abstracts |

The response includes `count` (returned papers), `total_results` (all matches in the window), and `has_more` (the count limit omitted matches). `since` is stateless: there is no saved watch or cursor. For example, `get_recent(category="cs.LG", since="2024-02-29", count=5)` returns at most five newest matches, not every paper since that date. To continue, use `search_advanced(category="cs.LG", date_from="2024-02-29", sort_by="date_desc", page=2, page_size=5)`; keep the same `date_to` UTC day if continuing across midnight. Dates include the start day's `00:00` and end day's `23:59` arXiv API boundaries.

### `get_references` / `get_citations`
Get papers cited **by** a paper (`get_references`) or papers that **cite** it (`get_citations`),
using the [Semantic Scholar Graph API](https://api.semanticscholar.org/api-docs/graph).

| Argument | Type | Required | Description |
|----------|------|----------|-------------|
| `id_or_url` | string | Yes | arXiv ID or arxiv.org abstract/PDF URL; versions resolve to the same graph paper |
| `limit` | int | No | Maximum edges inspected, 1–50 (default: 10); invalid limits fail locally |

Returns `{id_arxiv, papers, has_more}`. Each compact neighbor has `title`, `id_arxiv` (or `null`
if unavailable), `year` (or `null`), and author names. Unresolved/null edges are skipped, so the
number of returned papers may be smaller than `limit`. `has_more` reports remaining upstream
edges; these tools provide a sample, not full pagination or total citation counts. Increase
`limit` up to 50 for a larger sample. Semantic Scholar coverage can be incomplete or delayed;
HTTP 404 explicitly means absent there, **not** absent from arXiv.

Graph requests use the shared transport deadlines/retries but a separate 1-second per-process
request slot, never arXiv's 3-second slot. Cache up to 128 successful graph responses for 5 minutes,
keyed by versionless ID, direction, and limit. Failures are not cached. Unauthenticated users
share Semantic Scholar's global rate limit: HTTP 429 gives a readable retry-later error after
at most one retry, not a guarantee of access. Transport/body/throttle/retry waits share a
30-second deadline; 5xx errors are readable too.

Optionally set `SEMANTIC_SCHOLAR_API_KEY` in the server process environment (or the MCP server
configuration's `env` object). It is sent only as `x-api-key`, never in URLs or error messages;
graph redirects are not followed to prevent forwarding it to another host. Do not commit keys.

### `list_categories`
List all common arXiv categories with their codes and names.

## Search Behavior

- Results come from the official arXiv API (`export.arxiv.org/api/query`). Requests are spaced 3 seconds
  apart per server process, as the API terms ask.
- The words of a query are ANDed. A query containing a stopword (`is`, `the`, `for`, ...) is sent as an exact
  phrase instead, because the API drops stopwords from ANDed words and would return nothing.
- A query that already uses arXiv field syntax (`ti:`, `au:`, `abs:`, `cat:`, `all:`) is passed through as is.
- Titles and abstracts keep their raw LaTeX (`$\alpha$`). Search by plain words (`alpha divergence`); LaTeX
  markup in a query does not match.
- Versioned IDs (`2301.00001v2`) are looked up as given. The API rejects versioned legacy IDs
  (`hep-th/9901001v1`), so the version is dropped for those.
- Dates in results are ISO timestamps, e.g. `2017-06-12T17:57:34Z`.
- arXiv and Jina transport failures surface as readable errors. GET requests retry once on HTTP
  429, 503 or 406, using `Retry-After` (seconds or HTTP date, capped at 10 seconds; default 1 second).
  Total deadlines include rate-limit and retry waits: 30 seconds for arXiv, 30 seconds for the Jina HTML attempt and 60 seconds for the Jina PDF fallback.
- Per-process FIFO caches keep up to 128 Atom responses and 128 mention queries. Atom responses
  expire after 5 minutes (1 minute for `date_desc` and `updated_desc`); mentions expire after 5 minutes.
  Atom cache keys include all outgoing parameters, including page, page size and sort order.

## Security

Paper text is untrusted external content. A paper (or its metadata) may contain text that looks like
instructions to an AI assistant; treat it as data to read, never as commands to follow. Full text is only
returned through `arxiv://` resources and `read_paper`, and the server itself never executes anything from a paper.

## Installation

### With uvx (no clone)

```bash
uvx arxiv-mcp-lean
```

MCP clients start it for you; use `uvx` as the command, for example:

```json
{
  "mcpServers": {
    "arxiv": {
      "command": "uvx",
      "args": ["arxiv-mcp-lean"]
    }
  }
}
```

The PyPI package is `arxiv-mcp-lean`; the PyPI name `arxiv-mcp` is an unrelated project. To run the
unreleased `main` branch instead: `uvx --from git+https://github.com/Ray0907/arXiv-mcp arxiv-mcp-lean`.

### Using uv from a clone

```bash
# Clone the repository
git clone https://github.com/Ray0907/arXiv-mcp.git
cd arXiv-mcp

# Install with uv
uv sync
```

### Using pip

```bash
# Clone the repository
git clone https://github.com/Ray0907/arXiv-mcp.git
cd arXiv-mcp

# Create virtual environment
python -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install
pip install -e .
```

## Configuration

### Claude Desktop

Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):

```json
{
  "mcpServers": {
    "arxiv": {
      "command": "uv",
      "args": [
        "--directory",
        "/path/to/arXiv-mcp",
        "run",
        "arxiv-mcp"
      ]
    }
  }
}
```

### Claude Code

Add to your Claude Code MCP settings:

```json
{
  "mcpServers": {
    "arxiv": {
      "command": "uv",
      "args": [
        "--directory",
        "/path/to/arXiv-mcp",
        "run",
        "arxiv-mcp"
      ]
    }
  }
}
```

### pi

```bash
pi mcp add arxiv -- uv --directory /path/to/arXiv-mcp run arxiv-mcp
pi mcp list
```

`pi mcp list` should show `arxiv: connected` with the available tools. Tools are exposed through code mode by default; add
`--exposure direct` to `pi mcp add` to expose each tool directly. A project-level server (`-l`) is ignored
until you trust the project in pi.

### Antigravity CLI (agy)

```bash
agy mcp add arxiv -- uv --directory /path/to/arXiv-mcp run arxiv-mcp
agy mcp list
```

`agy mcp list` only shows that the server is enabled; start a session and call a tool to confirm. In
non-interactive mode (`agy -p`), MCP calls are denied until allowed, for example with an `mcp(*)` entry under
`permissions.allow` in `settings.json`.

## Usage Examples

### Search for papers about LLMs

```
Search for recent papers about "large language models"
```

### Find papers by a specific author

```
Search for papers by "Yann LeCun" in the machine learning category
```

### Get paper details

```
Get the details of arXiv paper 2301.00001
```

### Browse recent papers

```
Show me the 10 most recent papers in cs.AI
```

## Development

### Run tests

```bash
uv run pytest
```

### Run the server locally

```bash
uv run arxiv-mcp
```

## Common arXiv Categories

| Code | Name |
|------|------|
| cs.AI | Artificial Intelligence |
| cs.CL | Computation and Language |
| cs.CV | Computer Vision and Pattern Recognition |
| cs.LG | Machine Learning |
| cs.NE | Neural and Evolutionary Computing |
| stat.ML | Machine Learning (Statistics) |

Use `list_categories` tool to get the full list.

## Changelog

See [CHANGELOG.md](CHANGELOG.md).

## License

MIT License - see [LICENSE](LICENSE) for details.
