Acatome Quest Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Acatome Quest Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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:
existing slug, fabrication suspect.
into the existing watch inbox, where acatome-extract takes over.
band; the MCP call never blocks.
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-mcpFor 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 reconcilereport 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 testsGPL-3.0-or-later. See LICENSE.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.