loading…
Search for a command to run...
loading…
An MCP server that resolves paper requests (DOI, arXiv, etc.), checks a local store, fetches open-access PDFs, and returns request IDs. It integrates with acato
An MCP server that resolves paper requests (DOI, arXiv, etc.), checks a local store, fetches open-access PDFs, and returns request IDs. It integrates with acatome-extract for ingestion and provides tools for submission, status, update, and file upload.
Paper-request MCP for scientific papers. The missing piece between precis-mcp (navigates what's already in your library) and acatome-extract (ingests PDFs that land in an inbox).
An LLM says "I want this paper" (DOI, arXiv id, title, or free-form citation). Quest:
acatome-extract takes over.Quest fetches from arXiv, Unpaywall, OpenAlex, Europe PMC, and Semantic
Scholar's open-access index only. It does not, will not, and cannot be
configured to use Sci-Hub, LibGen, institutional proxies without explicit opt-
in, or any other paywall-circumvention mechanism. Failed retrievals yield a
needs_user status with the publisher URL, for you to retrieve manually.
pip install acatome-quest-mcp
# or with uv
uv add acatome-quest-mcp
For dedup against a local acatome-store:
pip install 'acatome-quest-mcp[store]'
| Tool | What it does |
|---|---|
submit(ref, *, dry_run=False, source=None, priority=0, created_by=None) |
Resolve + optionally queue. Idempotent. |
status(id=None, *, filter=None) |
Read one or many requests. |
update(id, mode, **kwargs) |
Mutate. Modes: confirm, repoint, flag, priority, cancel. |
submit_file(url?, content_base64?, filename?, request_id?, ref?, created_by?) |
Attach a user-supplied PDF (e.g. Discord attachment) to an existing request or create a new one, flip to ingesting. |
submit(ref={"doi": "10.1021/jacs.2c01234"})
submit(ref={"title": "Anion exchange membranes for NOx reduction",
"authors": ["Feng, Z."], "year": 2024})
submit(ref={"raw": "Feng et al. 2024, Adv. Funct. Mater. 34, 2300512"})
submit(ref={"doi": "10.1234/x"}, dry_run=True) # resolve only, no queue
submit(ref={"doi": "10.1234/x"},
source={"document": "ch02.tex", "line": 147})
Response:
{
"id": "9f3b…",
"status": "found_in_store",
"resolved": {"doi": "10.1021/jacs.2c01234",
"title": "…", "authors": ["…"], "year": 2024,
"ref": "smith2022jacs"},
"candidates": [],
"misconceptions": []
}
status(id="9f3b…")
status(filter={"status": "needs_user"})
status(filter={"created_by": "asa", "has_misconception": True})
status(filter={"source_document": "ch02.tex"})
update(id, mode="confirm", choice=0) # pick candidates[0]
update(id, mode="repoint", doi="10.1023/A:…") # user-corrected DOI
update(id, mode="flag", code="retracted",
evidence="Retraction Watch 2024-08-12")
update(id, mode="priority", priority=5)
update(id, mode="cancel")
# User drops a PDF for an already-tracked request (reopens failed / needs_user):
submit_file(url="https://cdn.discordapp.com/…/paper.pdf",
request_id="7f3a…",
filename="feng2024.pdf")
# User supplies both a PDF and a DOI in one step (creates the request):
submit_file(url="https://cdn.discordapp.com/…/paper.pdf",
ref={"doi": "10.1021/jacs.2c01234"},
created_by="asa")
# Bytes already in memory (no URL to fetch):
submit_file(content_base64="JVBERi0xLjQKJf…",
request_id="7f3a…")
PDF magic bytes are validated; HTML error pages are rejected. The file is written to the extractor's inbox (~/.acatome/inbox/ by default) and the request flips to ingesting. If the paper's DOI is already in the store, the tool short-circuits to found_in_store without writing anything.
The acatome-quest binary exposes the same surface as the MCP plus a couple of
shell-friendly helpers:
acatome-quest submit 10.1021/jacs.2c01234
acatome-quest status <id>
acatome-quest status --filter status=needs_user
acatome-quest status --filter status=needs_user --count # just prints "3"
acatome-quest update <id> repoint --doi 10.1023/A:…
acatome-quest submit-file --path ./feng2024.pdf --request-id 7f3a…
acatome-quest submit-file --url https://.../paper.pdf --doi 10.1021/jacs.2c01234
acatome-quest report # markdown worklist
acatome-quest report --document ch04.tex --format markdown # scoped
acatome-quest runner [--once]
acatome-quest reconcile
report renders a paste-ready markdown document for every request in
needs_user, failed, or extract_failed — each entry with citation,
DOI/arXiv link, failure reason, misconception evidence, and a concrete
suggested action (repoint DOI, drop PDF into ~/.acatome/inbox/, request via
interlibrary loan, …). Hand it to a librarian or paste into an ILL form.
| Status | Meaning |
|---|---|
queued |
Accepted, not yet fetched |
resolving |
Metadata lookup in progress (transient) |
found_in_store |
Dedup hit — slug in resolved.ref |
needs_user |
Disambiguation or manual fetch required |
fetching |
Runner has claimed and is downloading |
ingesting |
PDF in inbox, waiting for acatome-extract watch |
ingested |
Extraction done, slug in resolved.ref |
extract_failed |
PDF delivered but extraction failed |
failed |
All sources exhausted |
cancelled |
update(mode=cancel) was called |
| Code | Severity | Trigger |
|---|---|---|
doi_invalid |
major | Crossref 404 or syntactically malformed |
doi_truncated |
major | 404, but doi + digit resolves |
doi_title_mismatch |
critical | DOI resolves but title fuzz < 60 vs request |
title_not_found |
critical | No S2/Crossref hit (fabrication suspect) |
duplicate_of |
minor | Already in store under another slug |
retracted |
critical | S2 / Retraction Watch flag |
preprint_of |
info | arXiv preprint of a later journal paper |
pdf_mismatch |
critical | User-dropped PDF resolved to a different paper than the request it was attached to |
agent ──submit()──► acatome-quest-mcp (FastMCP, stdio)
│
▼
cluster.papers.requests (Postgres)
│
▼
acatome-quest-runner (launchd, poll 30 s)
│
fetch: arxiv → unpaywall → …
│
▼
~/.acatome/inbox/<slug>__<hash>.pdf
│
▼
acatome-extract watch → acatome-store
▲
└── runner polls by DOI, flips to `ingested`
| Env var | Default | Description |
|---|---|---|
DATABASE_URL |
postgresql://localhost/cluster |
Postgres DSN |
QUEST_SCHEMA |
papers |
Schema name for the requests table |
ACATOME_INBOX |
~/.acatome/inbox |
Drop directory watched by acatome-extract |
UNPAYWALL_EMAIL |
(required at runner start) | Polite-pool contact |
ACATOME_CROSSREF_MAILTO |
(recommended) | Crossref polite pool |
SEMANTIC_SCHOLAR_API_KEY |
(optional) | Raises S2 rate limit |
QUEST_POLL_INTERVAL |
30 |
Runner tick seconds |
QUEST_MAX_CONCURRENT |
4 |
Max parallel fetches |
QUEST_INGEST_TIMEOUT |
900 |
Seconds to wait for ingest after PDF drop |
QUEST_MAX_OPEN_PER_AGENT |
50 |
Per-created_by cap |
uv sync
uv run pytest
uv run ruff check .
uv run mypy src tests
GPL-3.0-or-later. See LICENSE.
Выполни в терминале:
claude mcp add acatome-quest-mcp -- npx Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development