Paper-request MCP: track, resolve, fetch (OA-only), and flag misconceptions for scientific papers
Project description
acatome-quest-mcp
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:
- Checks the store first — no duplicate work if we already have it.
- Resolves the metadata via Crossref + Semantic Scholar + arXiv.
- Flags misconceptions — broken DOI, DOI↔title mismatch, duplicate of an existing slug, fabrication suspect.
- Fetches the PDF from legitimate open-access sources only and drops it
into the existing watch inbox, where
acatome-extracttakes over. - Returns a request id in milliseconds. Slow extraction happens out of band; the MCP call never blocks.
Open access only — by policy
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.
Install
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]'
Three tools
| 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
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
status(id="9f3b…")
status(filter={"status": "needs_user"})
status(filter={"created_by": "asa", "has_misconception": True})
status(filter={"source_document": "ch02.tex"})
update
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")
Statuses
| 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 |
Misconception codes
| 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 |
Architecture
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`
Configuration
| 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 |
Development
uv sync
uv run pytest
uv run ruff check .
uv run mypy src tests
License
GPL-3.0-or-later. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file acatome_quest_mcp-0.1.0.tar.gz.
File metadata
- Download URL: acatome_quest_mcp-0.1.0.tar.gz
- Upload date:
- Size: 36.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b52c0c6621de4c2f3af949e39cd7ffaa1c5d7b124fc5e00574bf86a345220298
|
|
| MD5 |
28fbba5e40f0c924120c806ec3d09d29
|
|
| BLAKE2b-256 |
10dd9af50c341b9210cb51baf823cae6aadc97646a340e1af99dbfc10303496e
|
Provenance
The following attestation bundles were made for acatome_quest_mcp-0.1.0.tar.gz:
Publisher:
publish.yml on retospect/acatome-quest-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
acatome_quest_mcp-0.1.0.tar.gz -
Subject digest:
b52c0c6621de4c2f3af949e39cd7ffaa1c5d7b124fc5e00574bf86a345220298 - Sigstore transparency entry: 1340669331
- Sigstore integration time:
-
Permalink:
retospect/acatome-quest-mcp@a4af741c285a17f9968d8c12fd3154f0c9ee6124 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/retospect
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a4af741c285a17f9968d8c12fd3154f0c9ee6124 -
Trigger Event:
push
-
Statement type:
File details
Details for the file acatome_quest_mcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: acatome_quest_mcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 33.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2100acc57fb3c7d3b802384faedfb22efdd1150765317288428bc532dce9b50f
|
|
| MD5 |
a80970345d02e892e3bacd15e0da7ba4
|
|
| BLAKE2b-256 |
89404da0465e79924da8b3a1b4dc4984f1309dedf4d3355a5033987de455ff43
|
Provenance
The following attestation bundles were made for acatome_quest_mcp-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on retospect/acatome-quest-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
acatome_quest_mcp-0.1.0-py3-none-any.whl -
Subject digest:
2100acc57fb3c7d3b802384faedfb22efdd1150765317288428bc532dce9b50f - Sigstore transparency entry: 1340669334
- Sigstore integration time:
-
Permalink:
retospect/acatome-quest-mcp@a4af741c285a17f9968d8c12fd3154f0c9ee6124 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/retospect
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a4af741c285a17f9968d8c12fd3154f0c9ee6124 -
Trigger Event:
push
-
Statement type: