Semantic Scholar MCP Server
A 14-tool Semantic Scholar MCP server for academic research workflows. Direct access to 200M+ papers from Semantic Scholar — paper search, citation graph traversal, author profiles, and recommendations — from any Model Context Protocol client (e.g., Claude Desktop, Claude Code, Cursor, Cline, Continue, and others).
Every release ships verifiable supply-chain provenance: Sigstore-signed SLSA build-provenance attestations on the wheel, sdist, and container image; PEP 740 attestations on the PyPI upload; and a CycloneDX SBOM — so you can prove the artifact you installed was built from this repo. See Provenance & supply chain.
Author: Santiago Maniches · ORCID 0009-0005-6480-1987 · TOPOLOGICA LLC
Research authorship: Santiago Maniches independently directs and maintains this project and is the sole scientific author of its research contributions. AI-assisted tools have supported coding, documentation, and internal review; they do not constitute scientific coauthors or independent validation. Semantic Scholar content and reused software and methods are attributed to their respective sources.
Quick start
uvx s2-mcp-server # run instantly, no install
claude mcp add semantic-scholar -- uvx s2-mcp-server # or register it in Claude Code
No API key is needed to start. Set SEMANTIC_SCHOLAR_API_KEY to use your
assigned Semantic Scholar quota and avoid the shared unauthenticated pool.
Semantic Scholar currently assigns introductory API keys a 1 request/second
limit across all endpoints. Claude Desktop, Docker, pip, and remote
(Streamable HTTP) setups are in Installation.
Provenance & supply chain
A research tool is only as trustworthy as the chain from its source to the binary you run. Every release of this server ships cryptographically verifiable supply-chain evidence, all generated in CI from the tagged commit:
| Guarantee | What it proves | Where it is produced |
|---|---|---|
| SLSA build provenance (wheel + sdist) | the published distributions were built by this repo's publish.yml from the released tag, not hand-uploaded |
publish.yml — actions/attest-build-provenance (build job) |
| SLSA build provenance (container image) | the ghcr.io image digest was built by this repo's docker.yml |
docker.yml — actions/attest-build-provenance, push-to-registry (lines 141–147) |
| PEP 740 attestations | the PyPI upload itself carries Sigstore-backed attestations under Trusted Publishing | publish.yml — attestations: true (publish-pypi job) |
| CycloneDX SBOM | a machine-readable bill of materials, generated in an unprivileged job from the exact wheel's statically resolved dependency metadata (wheels only, none of it executed), SHA-256-bound to that wheel, then attested against the wheel alone | publish.yml — cyclonedx-py + scripts/release_sbom.py (sbom job) + actions/attest-sbom (attest-sbom job) |
| SHA-pinned Actions | every CI action is pinned to a commit SHA, so the release pipeline itself cannot silently change | all jobs in .github/workflows/ (e.g. publish.yml, docker.yml) |
Verify the wheel and the container image against their attestations with the GitHub CLI:
# Wheel / sdist (download from the PyPI project or the release assets first)
gh attestation verify s2_mcp_server-*.whl --repo smaniches/semantic-scholar-mcp
# Container image
gh attestation verify oci://ghcr.io/smaniches/semantic-scholar-mcp:latest \
--repo smaniches/semantic-scholar-mcp
The full supply-chain posture, including the known-limitations list, is in SECURITY.md. This is release-time provenance (proving how the artifact was built); the server does not currently attach a per-response receipt to individual API results.
How it compares
There is no public Semantic Scholar MCP standard, so the most useful comparison is against the obvious alternative: calling the Semantic Scholar REST API yourself from an agent. Everything in the right-hand column is plumbing this server already owns and the caller would otherwise reimplement.
| This server | Raw S2 REST API from an agent | |
|---|---|---|
| Tool surface | 14 typed MCP tools (search, retrieval, recommendations, status) | caller composes raw HTTP requests |
| Citation graph | both directions (citations and references) in get_paper |
manual paging over two endpoints |
| Bulk operations | papers (≤500) and authors (≤1000) in one call | caller batches and paginates |
| Full-text snippet search | snippet_search with surrounding context |
separate endpoint, caller-assembled |
| Paper-ID resolution | seven formats — Semantic Scholar ID, DOI, ArXiv, PubMed, Corpus ID, ACL, URL — validated pre-flight (validators.py) |
caller normalizes and validates IDs |
| Rate limiting | client-side limiter with a conservative S2-compatible authenticated default and configurable assigned quota (client.py) |
caller throttles by hand |
| Retry / backoff | bounded, jittered retry on 429/502/503/timeout, honors Retry-After (client.py) |
caller implements retry |
| Errors | typed exception hierarchy, branchable by caller (errors.py) |
parse HTTP status strings |
| Output | chat-tuned Markdown or JSON per call (formatters.py) |
raw JSON |
| Supply-chain provenance | SLSA + PEP 740 + CycloneDX SBOM per release (see above) | n/a |
| Citability | minted Zenodo DOI, MIT licensed | n/a |
Installation
Option 1: One-Line Install (Recommended)
# No cloning needed — runs directly from PyPI
uvx s2-mcp-server
Option 2: Claude Code
claude mcp add semantic-scholar -- uvx s2-mcp-server
Option 3: Claude Desktop (Windows)
Add to %APPDATA%\Claude\claude_desktop_config.json:
{
"mcpServers": {
"semantic-scholar": {
"command": "uvx",
"args": ["s2-mcp-server"],
"env": {
"SEMANTIC_SCHOLAR_API_KEY": "your-key-here"
}
}
}
}
Option 4: Claude Desktop (macOS)
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"semantic-scholar": {
"command": "uvx",
"args": ["s2-mcp-server"],
"env": {
"SEMANTIC_SCHOLAR_API_KEY": "your-key-here"
}
}
}
}
Option 5: pip / From Source
pip install s2-mcp-server
# or
git clone https://github.com/smaniches/semantic-scholar-mcp.git
cd semantic-scholar-mcp && pip install -e .
Option 6: Docker
docker pull ghcr.io/smaniches/semantic-scholar-mcp:latest
docker run -e SEMANTIC_SCHOLAR_API_KEY=your-key ghcr.io/smaniches/semantic-scholar-mcp
Option 7: Remote server (Streamable HTTP) — requires ≥ 1.5.0
# Serve MCP over HTTP at http://127.0.0.1:8000/mcp instead of stdio
# (--from pins the floor: uvx may otherwise reuse a cached older version)
uvx --from "s2-mcp-server>=1.5.0" s2-mcp-server --transport http
See Remote access (Streamable HTTP) for client configuration, per-request API keys, and deployment guidance.
Note: An API key is optional for most public Semantic Scholar endpoints. This server conservatively throttles unkeyed requests to about one per second, but anonymous users share an upstream pool. Semantic Scholar can return HTTP 429 even when this server respects its local interval. For dependable, sustained research workflows, configure an assigned API key. See Known Limitations.
Architecture
flowchart LR
Client["MCP client<br/>(Claude Desktop, Claude Code,<br/>Cursor, Cline, Continue, …)"]
subgraph Server ["s2-mcp-server (this package)"]
direction TB
FastMCP["FastMCP runtime<br/>(stdio / Streamable HTTP, lifespan)"]
Tools["14 @mcp.tool functions<br/>(server.py)"]
Models["Pydantic input models<br/>+ field sets (models.py)"]
Validators["Paper-ID validator<br/>(validators.py)"]
Cache["TTL cache<br/>(cache.py)"]
Fmt["Markdown formatters<br/>(formatters.py)"]
HTTP["httpx client<br/>+ rate limit + retry/backoff<br/>(client.py)"]
Errors["Typed exceptions<br/>(errors.py)"]
Log["Structured JSON logger<br/>(logging_config.py)"]
end
S2Graph["Semantic Scholar<br/>Graph API"]
S2Recs["Semantic Scholar<br/>Recommendations API"]
Client <-- "stdio or Streamable HTTP<br/>(JSON-RPC)" --> FastMCP
FastMCP --> Tools
Tools --> Models
Tools --> Validators
Tools --> Cache
Tools --> HTTP
Tools --> Fmt
HTTP --> Errors
HTTP --> Log
HTTP -- "GET / POST<br/>x-api-key" --> S2Graph
HTTP -- "GET / POST<br/>x-api-key" --> S2Recs
Module responsibilities (src/semantic_scholar_mcp/):
| Module | Responsibility |
|---|---|
server.py |
FastMCP instance, 14 @mcp.tool registrations, lifespan, main() entry. Re-exports the helper surface for back-compat. |
transport.py |
Streamable HTTP transport: CLI/env parsing (--transport http), uvicorn wiring, and per-request API-key extraction (header / query param / Smithery config) into a request-scoped contextvar. |
client.py |
Shared httpx.AsyncClient singleton, client-side rate limiter (1.0 s public; 1.1 s authenticated default, configurable for assigned quotas), retry loop with exponential backoff + jitter on 429/502/503/timeout, HTTP→typed-exception mapping. |
models.py |
Pydantic input models per tool, ResponseFormat enum, the tiered paper field-set constants (PAPER_SEARCH_FIELDS, …_LITE, PAPER_BULK_SEARCH_FIELDS, PAPER_DETAIL_FIELDS) plus AUTHOR_FIELDS. |
validators.py |
Pre-flight paper-ID validation. Rejects NUL bytes, ?, #, path traversal; accepts the seven canonical ID formats. |
cache.py |
In-memory TTL cache (5 min, 200 entries, oldest-first eviction) for paper/author lookups within a session. |
formatters.py |
Markdown renderers for paper and author dicts, tuned for chat-surface readability. |
errors.py |
SemanticScholarError hierarchy: AuthenticationError, RateLimitError, NotFoundError, ValidationError, ServerError. |
logging_config.py |
One-JSON-per-line StructuredFormatter on stderr; safe to ship through any log aggregator. |
Design choices worth knowing
- Single
httpx.AsyncClientper process. Created lazily, closed in the FastMCP lifespan teardown. Amortizes connection setup; respects keep-alive limits. The lifespan is reference-counted: under the Streamable HTTP transport the SDK enters it per request, so teardown only runs when the last holder exits. - Rate limit is enforced at the client, not the API. A semaphore + last-request timestamp ensures we never exceed the per-tier interval even when the MCP host issues tool calls in parallel.
- Retry is bounded and jittered. Up to
MAX_RETRIES = 3, base 1 s, capped at 30 s. HonorsRetry-Afterwhen present. - Errors are typed. Status codes map onto a small exception hierarchy so callers can branch on
AuthenticationErrorvsRateLimitErrorvsNotFoundErrorinstead of parsing strings. - Input validation is pre-flight. Paper IDs are checked before any outbound request; bad IDs never hit the wire.
- Version is single-source.
__version__is derived fromimportlib.metadata.version("s2-mcp-server"), so bumpingpyproject.tomlis sufficient; release-please bumps the manifest,server.json(×2 paths),CITATION.cff, and.zenodo.jsonin lockstep on every release.
Configuration
API Key Options
You can provide your API key in three ways:
-
Environment Variable (recommended for persistent use):
export SEMANTIC_SCHOLAR_API_KEY="your-api-key-here" -
Per-request HTTP header (Streamable HTTP transport only): send
x-api-key: your-keywith each request — see Remote access (Streamable HTTP). -
Per-Request Parameter (overrides env var):
{ "api_key": "your-api-key-here" }Deprecated: per-request
api_keyis deprecated and will be removed in v2.0.0. Tool-call arguments may be visible in MCP transcripts, client logs, and the LLM's tool-call history. Use theSEMANTIC_SCHOLAR_API_KEYenvironment variable instead. See SECURITY.md for details.
Get a free API key at: https://www.semanticscholar.org/product/api
Claude Desktop Setup
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
{
"mcpServers": {
"semantic-scholar": {
"command": "python",
"args": ["-m", "semantic_scholar_mcp"],
"env": {
"SEMANTIC_SCHOLAR_API_KEY": "your-api-key-here"
}
}
}
}
Then restart Claude Desktop.
Remote access (Streamable HTTP)
stdio remains the default transport. --transport http serves the same 14
tools over the MCP Streamable HTTP transport,
which is what remote clients — claude.ai custom connectors, Smithery
listings, mcp-remote bridges — connect to.
Requires
s2-mcp-server≥ 1.5.0. Earlier releases (≤ 1.4.0) do not parse CLI flags: they silently ignore--transport httpand start a stdio server instead, never opening the port.
# Local HTTP endpoint at http://127.0.0.1:8000/mcp
# (--from pins the floor: uvx may otherwise reuse a cached older version)
uvx --from "s2-mcp-server>=1.5.0" s2-mcp-server --transport http
# Bind a public interface and custom port (only behind a TLS proxy — see Security).
# The DNS-rebinding guard only admits localhost Host headers by default, so
# also allow the public hostname clients will use (":*" matches any port).
uvx --from "s2-mcp-server>=1.7.1" s2-mcp-server --transport http --host 0.0.0.0 --port 8080 \
--allowed-host "mcp.example.org:*"
# Docker
docker run -p 8000:8000 ghcr.io/smaniches/semantic-scholar-mcp --transport http
Flags and environment variables
| Flag | Env var | Default | Meaning |
|---|---|---|---|
--transport |
MCP_TRANSPORT |
stdio |
stdio, http (alias: streamable-http) |
--host |
MCP_HOST |
127.0.0.1 |
Bind address (0.0.0.0 in the Docker image) |
--port |
MCP_PORT, then PORT |
8000 |
Bind port (PORT is honored for hosting platforms) |
--path |
MCP_PATH |
/mcp |
URL path of the MCP endpoint |
--allowed-host (repeatable) |
MCP_ALLOWED_HOSTS (comma-separated) |
localhost only | Extra Host header values admitted by the DNS-rebinding guard, e.g. mcp.example.org:* (≥ 1.7.1) |
--allowed-origin (repeatable) |
MCP_ALLOWED_ORIGINS (comma-separated) |
localhost only | Extra Origin header values admitted by the DNS-rebinding guard, e.g. https://mcp.example.org (≥ 1.7.1) |
--version |
— | — | Print the package version and exit |
| — | MCP_STATELESS_HTTP |
true |
One independent server interaction per request (recommended) |
| — | MCP_JSON_RESPONSE |
true |
Plain JSON responses instead of SSE streams |
CLI flags beat environment variables. The server is stateless and returns JSON by default — the configuration recommended for production Streamable HTTP deployments — and no tool relies on sessions, streaming, or server-initiated messages, so there is no functional trade-off.
Per-request API keys (bring your own key)
When served over HTTP, each request may carry its own Semantic Scholar API key; concurrent users never share or observe each other's keys. Sources, in precedence order:
x-api-keyHTTP header (recommended)SEMANTIC_SCHOLAR_API_KEYquery parameter (Smithery session config)api_keyquery parameter- Legacy base64
?config=parameter (older Smithery deployments)
A request without a key falls back to the server's SEMANTIC_SCHOLAR_API_KEY
environment variable, or to keyless public-tier access.
Client configuration
Claude Code
claude mcp add --transport http semantic-scholar http://127.0.0.1:8000/mcp \
--header "x-api-key: your-key-here"
JSON config (clients that accept a url)
{
"mcpServers": {
"semantic-scholar": {
"type": "http",
"url": "http://127.0.0.1:8000/mcp",
"headers": { "x-api-key": "your-key-here" }
}
}
}
claude.ai custom connectors require a public HTTPS URL and accept either
authless servers or OAuth — API keys in the connector URL are not supported
by claude.ai. Host the server with the key supplied server-side
(SEMANTIC_SCHOLAR_API_KEY env var) and register the public /mcp URL as
the connector.
Smithery lists remote servers by URL (smithery mcp publish <url>); the
per-request key extraction above is compatible with Smithery session config
out of the box.
Security notes
- The HTTP transport performs no authentication of inbound callers. The
default bind is loopback (
127.0.0.1). Expose it publicly only behind a TLS-terminating reverse proxy, and prefer thex-api-keyheader over query parameters (URLs end up in access logs). - API keys are request-scoped, and the server itself never logs them. (A key
placed in a URL query parameter can still appear in access logs, as noted
above — prefer the
x-api-keyheader.) - See SECURITY.md for the project's broader threat model.
Supported ID Formats
The server accepts the following paper identifier formats:
| Format | Pattern | Example |
|---|---|---|
| Semantic Scholar ID | 40-character hex | 649def34f8be52c8b66281af98ae884c09aef38b |
| DOI | DOI:xxx |
DOI:10.1038/s41586-021-03819-2 |
| ArXiv | ARXIV:xxx |
ARXIV:2106.15928 or ARXIV:2106.15928v2 |
| PubMed | PMID:xxx |
PMID:32908142 |
| Corpus ID | CorpusId:xxx |
CorpusId:215416146 |
| ACL | ACL:xxx |
ACL:P19-1285 |
| URL | URL:xxx |
URL:https://arxiv.org/abs/2106.15928 |
Tools Reference
1. semantic_scholar_search_papers
Search for academic papers with advanced filters.
Parameters: | Parameter | Type | Required | Description | |-----------|------|----------|
