arxiv-mcp-server

arXiv makale araması ve tam metin okuma

Dokümantasyon

@cyanheads/arxiv-mcp-server

Search arXiv, fetch paper metadata, and read full-text content via MCP. STDIO or Streamable HTTP.

4 Tools • 2 Resources

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework

Public Hosted Server: https://arxiv.caseyjhand.com/mcp


Overview

arXiv papers, metadata, and full text from the arXiv API and its OAI-PMH metadata feed. Search papers by query, category, and submission date; fetch structured metadata by ID; and read full paper text with automatic fallback across HTML and PDF renders. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.

Tools

ToolDescription
arxiv_search_papersSearch arXiv papers by query with field prefixes, category, and date filters
arxiv_get_metadataFetch metadata for one or more papers by arXiv ID
arxiv_read_paperRead full paper text via HTML, ar5iv, or PDF-extracted fallback
arxiv_list_categoriesList the arXiv category taxonomy, optionally filtered by group

Resources

ResourceDescription
arxiv://paper/{paperId}Paper metadata by arXiv ID
arxiv://categoriesFull arXiv category taxonomy

Capability reference

arxiv_search_papers tool

  • Query supports field prefixes ti:, au:, abs:, cat:, co:, jr:, all: and AND / OR / ANDNOT, up to 1000 characters; category takes a code (cs.CL) or a whole archive (astro-ph, cs, math)
  • Query grammar follows arXiv's: space-separated terms are joined with OR (write AND to require every term), operators are uppercase only, and all three share one precedence grouped to the right (a AND b OR c is a AND (b OR c)). Quote a multi-word topic ("graph neural networks" AND cat:cs.LG) and put alternatives in parentheses before a filter; arXiv ignores common words such as of and the outside a quoted phrase
  • Sorts by sort_by (relevance / submitted / updated) and sort_order, with submitted_from / submitted_to as inclusive UTC YYYY-MM-DD bounds; up to 50 results per call (a larger max_results returns 50)
  • arXiv serves only the first 10,000 results of a query; a start of 10,000 fails start_beyond_result_limit. Use date windows to go further
  • Output echoes the effective query, total match count, and page offset; empty pages carry recovery guidance instead of an error. A total that is a floor (the local mirror counting to 50,000) carries totalFoundIsLowerBound: true

arxiv_get_metadata tool

  • Takes up to 100 IDs and looks up the first 10 distinct ones; the rest come back in remaining_ids for the next call's paper_ids. An ID repeated in any accepted spelling is looked up once, while different versions of one paper stay separate
  • Accepts versioned (2401.12345v2), unversioned, and pre-2007 (hep-th/9901001) IDs, with an optional arXiv: prefix or an arxiv.org abs / pdf / html URL
  • Returns found papers plus a typed not_found[] (not_in_arxiv / version_not_in_mirror / version_not_served / live_lookup_failed / invalid_id) for the rest; version_not_served is a superseded pre-2007 version arXiv's API answers HTTP 500 for, and its detail names the latest version. With the local mirror, live_lookup_failed marks an ID the mirror lacked whose live lookup failed; the papers the mirror served still come back
  • Fails invalid_id when no ID is well-formed, no_match when every ID misses, version_unavailable when a version is reachable only on the live API, and version_not_served when every ID is a version arXiv will not serve

arxiv_read_paper tool

  • Tries arXiv HTML, then ar5iv, then PDF text extraction; the source field reports which answered. PDF text is plain, so math, tables, and headings flatten
  • ar5iv keeps one render per paper, so an ar5iv body may be another revision than paper_id and carries revision_unconfirmed: true, unless the read named no version and arXiv's API reported v1 as the paper's only version; pdf_url is exactly the paper_id revision
  • max_characters defaults to 100,000 (pass null for the whole paper); page with start. The first page carries an outline of section headings with offsets
  • paper_id accepts the same ID spellings as arxiv_get_metadata
  • Fails invalid_id, no_match (arXiv has no such paper, or no such version of it), content_unavailable (no render, no PDF), pdf_extraction_failed (the PDF has no text layer or does not parse), version_unavailable (version-pinned ID needs the live API), or version_not_served (a superseded pre-2007 version arXiv will not serve; names the latest version and this version's PDF)

arxiv_list_categories tool

  • ~155 categories across 8 top-level groups (cs, econ, eess, math, physics, q-bio, q-fin, stat)
  • Optional group filter to narrow results
  • Static data — always succeeds

arxiv://paper/{paperId} resource

  • paperId accepts the same ID spellings as arxiv_get_metadata; percent-encode a legacy ID's slash (arxiv://paper/hep-th%2F9901001)
  • Fails empty_id, invalid_id, no_match, version_unavailable, or version_not_served

arxiv://categories resource

  • Returns { categories: [...] }, a flat array with code / name / group per entry
  • No parameters; cacheable for 24h

Features

Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.

arXiv-specific:

  • Read-only, no authentication required
  • Request queue enforcing arXiv's 3-second crawl delay; rate-limit responses fail fast with a cooldown
  • Paper text falls back from arXiv HTML to ar5iv to PDF extraction
  • Full arXiv category taxonomy embedded as static data
  • Optional local OAI-PMH metadata mirror (SQLite + FTS5) that removes rate-limit exposure for search and metadata. See Optional: local mirror

Agent-friendly output:

  • arxiv_read_paper names its source; arxiv_search_papers echoes the effective query
  • arxiv_get_metadata returns found papers with a typed not_found[] reason per miss
  • Typed source and not_found[].reason enums let callers branch on data

Getting started

Public Hosted Instance

A public instance is available at https://arxiv.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:

{
  "mcpServers": {
    "arxiv-mcp-server": {
      "type": "streamable-http",
      "url": "https://arxiv.caseyjhand.com/mcp"
    }
  }
}

Self-Hosted / Local

Add the following to your MCP client configuration file.

{
  "mcpServers": {
    "arxiv-mcp-server": {
      "type": "stdio",
      "command": "bunx",
      "args": ["@cyanheads/arxiv-mcp-server@latest"]
    }
  }
}

Or with npx (no Bun required):

{
  "mcpServers": {
    "arxiv-mcp-server": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@cyanheads/arxiv-mcp-server@latest"]
    }
  }
}

Or with Docker:

{
  "mcpServers": {
    "arxiv-mcp-server": {
      "type": "stdio",
      "command": "docker",
      "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/arxiv-mcp-server:latest"]
    }
  }
}

For Streamable HTTP, set the transport and start the server:

MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp

Prerequisites

Installation

  1. Clone the repository:
git clone https://github.com/cyanheads/arxiv-mcp-server.git
  1. Navigate into the directory:
cd arxiv-mcp-server
  1. Install dependencies:
bun install

Configuration

All configuration is optional — the server works out of the box with sensible defaults.

VariableDescriptionDefault
ARXIV_API_BASE_URLarXiv API base URL.https://export.arxiv.org/api
ARXIV_REQUEST_DELAY_MSMinimum delay between arXiv API requests (ms).3000
ARXIV_CONTENT_TIMEOUT_MSTimeout for paper body fetches — HTML renders and PDF downloads (ms).30000
ARXIV_API_TIMEOUT_MSTimeout for API search/metadata requests (ms). With the mirror enabled, also the longest an unversioned arxiv_read_paper waits on its metadata lookup, queue included, before a mirror holding the paper answers.15000
ARXIV_READ_TIMEOUT_MSTime budget for one arxiv_read_paper call — its metadata lookup, every content request and retry, and PDF text extraction (ms). Keep it under your MCP client's request timeout (60 s by default).50000
ARXIV_MIRROR_ENABLEDEnable the local OAI-PMH metadata mirror.false
ARXIV_MIRROR_PATHSQLite path for the mirror../data/arxiv-mirror.db
ARXIV_MIRROR_REFRESH_CRONUTC cron expression for in-process daily refresh (HTTP mode only).unset
ARXIV_MIRROR_FALLBACK_LIVEFall through to the live API on a local ID-lookup miss.true
ARXIV_MIRROR_RECENT_DAYS_LIVEPositive values send sort_by=submitted, descending queries to the live API; 0 disables.2
ARXIV_MIRROR_OAI_BASE_URLarXiv OAI-PMH endpoint base URL.https://oaipmh.arxiv.org/oai
ARXIV_MIRROR_OAI_REQUEST_DELAY_MSMinimum delay between OAI-PMH requests (ms).3000
ARXIV_MIRROR_REFRESH_TIMEOUT_MSTime budget for one scheduled refresh (ms).7200000
MCP_TRANSPORT_TYPETransport: stdio or http.stdio
MCP_HTTP_PORTPort for HTTP server.3010
MCP_SESSION_MODEauto, stateful, or stateless.stateless
MCP_AUTH_MODEAuth mode: none, jwt, or oauth.none
MCP_LOG_LEVELLog level (RFC 5424).info
OTEL_ENABLEDEnable OpenTelemetry instrumentation (spans, metrics, completion logs).false

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:http
    # or
    bun run start:stdio
    
  • Run checks and tests:

    bun run devcheck   # Lint, format, typecheck, security audit
    bun run test       # Vitest test suite
    

Optional: local mirror

For self-hosted deployments behind one egress IP, an optional SQLite + FTS5 mirror of arXiv's OAI-PMH metadata serves arxiv_search_papers and arxiv_get_metadata locally, so concurrent users stop queueing behind arXiv's 3-second crawl delay; arxiv_read_paper still fetches paper text live. Harvest it once with bun run mirror:init (~4.4h), then set ARXIV_MIRROR_ENABLED=true. Running it from an npm install, scheduling refreshes, schema upgrades, and where mirror search differs from the live API are covered in docs/mirror.md.

Docker

docker build -t arxiv-mcp-server .
docker run --rm -p 3010:3010 arxiv-mcp-server

The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/arxiv-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.

Project structure

DirectoryPurpose
src/index.tscreateApp() entry point — registers tools/resources and starts the optional mirror-refresh scheduler.
src/configServer-specific environment variable parsing and validation with Zod.
src/mcp-server/tools/definitionsTool definitions (*.tool.ts).
src/mcp-server/resources/definitionsResource definitions (*.resource.ts).
src/services/arxivArxivService — live arXiv API client (search, metadata, HTML), and PDF text extraction in a worker thread (pdf-text.ts).
src/services/arxiv/mirrorOptional OAI-PMH mirror — harvester, SQLite + FTS5 store, query translator, runner.
skills/arxiv-guideAgent skill published to agents that install the server, and served over MCP: citation-to-ID resolution, search gates, version and section-by-section reading workflows.
scripts/arxiv-mirror-*.tsMirror lifecycle scripts (init, refresh, verify); scripts/tsconfig.json resolves their @/ imports to src/, or to dist/ in an install.
tests/Unit and integration tests.
docs/Design document, local mirror guide, and directory structure.

Development guide

See CLAUDE.md for development guidelines and architectural rules. The short version:

  • Handlers throw, framework catches — no try/catch in tool logic
  • Use ctx.log for request-scoped logging, ctx.state for tenant-scoped storage
  • arXiv API returns HTTP 200 for everything — including rate limits — so check content-type and body before parsing
  • Validate raw arXiv responses → normalize to domain types → return the output schema; never fabricate missing fields

Contributing

Issues are welcome. Run checks before submitting:

bun run devcheck
bun run test

License

Apache-2.0 — see LICENSE for details.