Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Literature search and citation management for medical research. Searches PubMed, Semantic Scholar, and bioRxiv/medRxiv with verified citations. Anti-hallucination — every reference verified via API before inclusion. Generates BibTeX entries.
.claude/skills/aperivue-search-lit/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-18 | ✗→✓ | ▲ Improved | 241% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 5% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 336% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 653% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 1895% | 0% |
You are assisting a medical researcher with literature searches and citation management for medical research papers. Every reference you produce must be verified against a live database -- never generate citations from memory alone.
| Database | MCP Tool | Purpose | |----------|----------|---------| | PubMed | mcp__claude_ai_PubMed__search_articles | Search by query, MeSH terms | | PubMed | mcp__claude_ai_PubMed__get_article_metadata | Full metadata for a PMID | | PubMed | mcp__claude_ai_PubMed__find_related_articles | Related articles for a PMID | | PubMed | mcp__claude_ai_PubMed__lookup_article_by_citation | Verify a citation | | PubMed | mcp__claude_ai_PubMed__convert_article_ids | Convert between PMID/DOI/PMCID | | Semantic Scholar | mcp__claude_ai_Scholar_Gateway__semanticSearch | Semantic search across all fields | | bioRxiv/medRxiv | mcp__claude_ai_bioRxiv__search_preprints | Search preprint servers | | bioRxiv/medRxiv | mcp__claude_ai_bioRxiv__get_preprint | Full preprint metadata | | CrossRef | WebFetch with https://api.crossref.org/works/{DOI} | DOI verification |
When PubMed MCP is unavailable (session timeout, "MCP session has been terminated" error, or "No such tool available" error), fall back to NCBI E-utilities via bundled scripts.
Detection: If any mcp__claude_ai_PubMed__* call returns an error containing "terminated", "not found", "not available", or "not connected", switch ALL subsequent PubMed calls in this session to E-utilities. Do not retry MCP after a disconnect — it will not recover within the same conversation.
Scripts (in ${CLAUDE_SKILL_DIR}/references/):
pubmed_eutils.sh — Bash wrapper for NCBI E-utilities APIparse_pubmed.py — Python parser for E-utilities responsesUsage patterns:
bashEUTILS="${CLAUDE_SKILL_DIR}/references/pubmed_eutils.sh" PARSER="${CLAUDE_SKILL_DIR}/references/parse_pubmed.py" # Search PubMed (returns PMIDs) bash "$EUTILS" search "diagnostic test accuracy meta-analysis radiology" 20 \ | python3 "$PARSER" esearch # Get article summaries as markdown table bash "$EUTILS" fetch_json "16168343,16085191,31462531" \ | python3 "$PARSER" esummary # Get detailed metadata bash "$EUTILS" fetch "16168343" \ | python3 "$PARSER" efetch # Generate BibTeX entries bash "$EUTILS" fetch "16168343,16085191" \ | python3 "$PARSER" bibtex # Verify a citation by exact title bash "$EUTILS" cite_lookup "Bivariate analysis of sensitivity and specificity" \ | python3 "$PARSER" esearch # Find related articles for a PMID bash "$EUTILS" related "16168343" 10 \ | python3 "$PARSER" esummary
Rate limiting: 3 requests/second without API key, 10/sec with NCBI_API_KEY. The script auto-sleeps 350ms between calls. For batch operations, keep calls sequential.
E-utilities → MCP equivalence:
| MCP Tool | E-utilities Command | Parser Mode | |----------|-------------------|-------------| | search_articles | search <query> [retmax] | esearch | | get_article_metadata | fetch <pmids> | efetch or bibtex | | find_related_articles | related <pmid> [retmax] | esummary | | lookup_article_by_citation | cite_lookup <title> | esearch → fetch | | convert_article_ids | Not available (use CrossRef DOI lookup) | — |
that needs references.
(concept1 OR synonym1) AND (concept2 OR synonym2).databases to search, and filters.
Gate: Wait for user approval before running searches.
search_articles with the Boolean query.semanticSearch with natural language query.search_preprints if preprints are relevant.| # | Title | Authors (first + last) | Year | Journal | PMID/DOI | Relevance |
|---|-------|----------------------|------|---------|----------|-----------|
| 1 | ... | Kim J, ... Lee S | 2024 | Radiology | 12345678 | High |Search code reports its own haul. Nothing errors when the haul is wrong, and a PRISMA flow built on a wrong number is fiction that nothing downstream contradicts. Two signatures, both real, both from a single run:
own if start >= 2000: break, not the total (1,528 once the query was fixed). The round number was the only tell. Every source reports a total: esearchresult.count, opensearch:totalResults, meta.count. Record api_total beside downloaded, and fail loudly when downloaded < api_total, or when downloaded equals a page or loop cap exactly. Print TRUNCATED and refuse to write the search record.
search=, a relevance-ranked free-text parameter that silently ignores AND/OR; the parameter that honours them is filter=title_and_abstract.search: (true count: 5,282). So run the query once more with one mandatory clause negated. If the hit count does not drop, the boolean is being ignored — the engine is ranking, not filtering.
PubMed via E-utilities is the one place where the naive pattern happens to be safe. Everywhere else, do both.
When a doi column was filled by the pipeline rather than handed over with the record — matched against Crossref by title similarity, at some threshold — a wrong match is a valid, resolvable identifier for a different paper, and nothing downstream can tell. Resolve it and read the title back before any decision rests on it:
bashpython3 scripts/check_doi_record_match.py --table 2_Screening/round3.tsv \ --email <contact> --json qc/doi_record_match.json
DOI_NOT_THIS_RECORD is a DOI that resolves to another paper; DOI_IS_CONTAINER is one that resolves to an issue, supplement or proceedings rather than an article; DOI_UNRESOLVED is reported rather than dropped. This is not /verify-refs, which audits a finished reference list — it runs at screening, where a wrong DOI is still cheap. In one review two of these appeared within two days, and one produced a limitation about a "missed eligible paper" that did not exist.
Optional but recommended for systematic reviews and thorough background work (PRISMA item 7, "records identified through citation searching"). Expands a seed set along the citation graph instead of relying on Boolean recall alone.
Use the deterministic helper references/snowball.py (Semantic Scholar Graph API; nothing generated from memory):
bash# Expand seed DOIs/PMIDs in all directions, dedup against the existing pool, # append verified candidates to references/library.bib python3 references/snowball.py \ --seed DOI:10.1148/radiol.2024123,PMID:38000001 \ --direction all \ --pool references/library.bib \ --out references/library.bib
backward (references the seeds cite), forward (papersciting the seeds), similar (S2 recommendations), or all (default).
references/library.bib by DOI andnormalized title, and within the harvested set.
verified=false +verified_by=semantic_scholar. They are candidates, not confirmed citations — run /verify-refs (or Phase 4 verification) to confirm each against PubMed/CrossRef before citing.
references/library.bib only. NEVER writesmanuscript/_src/refs.bib (the script hard-refuses that path).
citation searching (snowballing): N raw (backward=…, forward=…, similar=…); after dedup against existing pool: M new candidates. — record M in the PRISMA flow's citation-searching box.
A deterministic, network-free challenge card (recorded fixtures + expected output + verify.sh) lives in references/snowball_challenge/.
For each selected paper:
get_article_metadata (PubMed) or get_preprint (bioRxiv).| Paper | Design | N | Key Finding | Limitation | Relevance to Our Study |
|-------|--------|---|-------------|------------|----------------------|This is the most critical part of the skill. Follow these rules without exception:
[UNVERIFIED - NEEDS MANUAL CHECK].https://api.crossref.org/works/{DOI} to confirm the DOI resolves correctly.For each reference (verified or not), generate a BibTeX entry with an explicit verified flag so downstream skills (/lit-sync, /verify-refs, /write-paper) can reason about trust without re-running verification:
bibtex@article{FirstAuthorLastName_Year_ShortKey, author = {Last1, First1 and Last2, First2 and Last3, First3}, title = {Full Title As Retrieved From Database}, journal = {Journal Name}, year = {2024}, volume = {310}, number = {2}, pages = {e234567}, doi = {10.1001/jama.2024.12345}, pmid = {12345678}, verified = {true}, verified_by = {pubmed+crossref}, verified_on = {2026-04-24}, }
verified flag values (required on every entry):
| Value | Meaning | Downstream behavior | |---|---|---| | true | DOI or PMID confirmed via PubMed/CrossRef; title, authors, year all match | Safe to cite; /write-paper citekey-only gate passes | | false | Parsed from text but API lookup failed or returned mismatch | /verify-refs flags as UNVERIFIED; manuscript MUST show [UNVERIFIED - NEEDS MANUAL CHECK] | | manual | User explicitly added despite lookup failure | Treated as verified=false by /verify-refs but suppresses repeat warnings |
verified_by lists the data sources that confirmed the entry (e.g., pubmed, crossref, semantic_scholar, or a combination). verified_on is the ISO date of the most recent successful verification.
BibTeX key convention: FirstAuthorLastName_Year_OneWord (e.g., Kim_2024_Validation).
Target: references/library.bib (candidate pool for /lit-sync to import into Zotero). NEVER write to manuscript/_src/refs.bib — that is /lit-sync's sole-writer path per docs/artifact_contract.md.
Verified: 12 references (verified=true)
Unverified: 1 reference (verified=false) [NEEDS MANUAL CHECK]
Total: 13 referencesIf a Zotero MCP server is available, integrate search results with the user's library:
zotero_search_items (by DOI) to skip papers already in the library — this search-first step is what dedupes; zotero_add_by_doi does not dedupe on its own.zotero_add_by_doi for DOI-based import (its attach_mode argument governs the OA PDF attach attempt at add time).zotero_manage_collections to file into the relevant project collection.zotero_get_annotations to reference the user's prior reading notes.unsynced entries in references/zotero_collection.json so Zotero status is auditable rather than a hidden optional side effect.
> Requires Zotero Desktop running with MCP server. Skip this phase if unavailable. > If skipped, still write references/zotero_collection.json with > status: "skipped" and the reason.
Full-text PDF retrieval is delegated to /fulltext-retrieval — the single authored home of the open-access cascade (arXiv → Unpaywall → PMC → OpenAlex → Crossref → landing page, each validated with a %PDF- header + ≥10 KB size). Do not re-implement OA fetching here.
Pass the verified candidate DOIs from references/library.bib:
bashENGINE="${MEDSCI_SKILLS_ROOT:-$HOME/workspace/medsci-skills}/skills/fulltext-retrieval/fetch_oa.py" # extract DOIs from references/library.bib → dois.txt (one per line) python3 "$ENGINE" dois.txt -o pdfs/ -e <contact-email> --report pdfs/retrieval_report.json
For Zotero-resident PDFs and higher-yield, proxy-aware retrieval, use /lit-sync Phase 2.7, which also invokes /fulltext-retrieval and triggers Zotero's native "Find Available PDF".
For DOIs that open access cannot reach (listed in pdfs/manual_needed.txt):
Never bypass paywalls or publisher access controls, and do not configure unauthorized PDF mirrors. Rate limits and PDF validation are handled inside /fulltext-retrieval.
When called during manuscript writing (especially by /write-paper Phase 7):
For supplying a manuscript's reference pool — typically invoked by /write-paper Step 7.3c (or /self-review Phase 2.5c-2) when the reference adequacy gate finds the draft under target or a named method uncited, but usable directly when building out an original-research bibliography.
This mode is deliberately broad: for an original-research article, return 25–40 verified candidates, not the ~10 a quick search settles on. Do not stop early unless the field is genuinely sparse — and if it is, say so explicitly rather than returning a thin list silently. Respect a narrower journal reference cap or user scope when one is given.
Structure the pool across six candidate categories so the gaps the adequacy gate cares about are all covered:
model, score, equation, or diagnostic criterion (e.g. competing-risk model, multiple imputation, E-value, eGFR equation, concordance statistic). This is the category that clears Methods named-method gaps.
For each candidate, report: PMID/DOI, verification status, candidate category, the target manuscript section it belongs in, and a one-line why it is needed.
Boundary (unchanged): every entry is API-verified before inclusion, and BibTeX is appended only to references/library.bib — the candidate pool for /lit-sync to import into Zotero. Never write to manuscript/_src/refs.bib; that SSOT belongs to /lit-sync. This mode produces candidates; it does not decide inclusion (the user does) and it does not insert references into the manuscript bib.
Run before a study is designed, not after. The question is not "what has been written about this topic" — a background search answers that and still leaves the trap open. It is narrower and it is four questions:
| Ask of | Verdict | |---|---| | the research question | taken / partly taken / open | | the sampling frame (what population, which records, which years) | taken / partly taken / open | | the measurement axis (what is being coded or measured, and at what granularity) | taken / partly taken / open | | the target journal | already published there / adjacent / open |
Each gets its own verdict. A design can be original on one axis and fully occupied on another, and collapsing the four into one answer is what hides that.
Why the fourth row is not vanity: a design once matched an existing paper on frame, coding axis and target journal, and that paper was already published in the journal it was first choice for. A redesign on a different axis then turned out to be partly occupied too — three papers were already coding the same thing as a single item — which did not kill it but did change the claim that could honestly be made, from "nobody has looked at this" to "nobody has decomposed it by provenance". That is a real result of this mode: most of the time it narrows a claim rather than ending a project, and a narrowed claim survives review where the broad one would not.
Search the way a competitor would: the exact frame, the exact measure, and the journal's own site, not only the topic. Report the four verdicts and the papers behind each, then let the user decide. /design-study and /orchestrate should route here first when a new study is being scoped.
For systematic reviews or comprehensive literature sections:
For quickly finding a single reference the user describes:
For expanding from a known paper:
find_related_articles to get related papers.For a structured, dedup-aware, PRISMA-countable expansion (backward + forward + similar) prefer Phase 2.5: Citation Searching with references/snowball.py, which appends verified candidates to references/library.bib and reports a citation-searching count.
Embase has no public API. Use Chrome browser automation (MCP) to search and export:
embase.com — institutional SSO authenticates automatically.If cookie error (login?error#), clear Elsevier/Embase cookies and retry.
/exp + :ab,ti field tags).Uncheck "Map to preferred term in Emtree" when using explicit /exp terms.
Title, Author names, Source, Publication year, Publication type, DOI, Abstract, Language of article, Medline PMID.
python # Each record = consecutive rows until blank row # Row format: [FIELD_NAME, value1, value2, ...] # AUTHOR NAMES row has multiple values (one per author)
PubMed → Embase query translation:
[Mesh] → Emtree /exp[tiab] → :ab,ti[Title/Abstract] → :ab,ti'artificial ascites')rate-limiting and skip CrossRef for ALL remaining references. Go directly to PubMed title verification. This avoids N×retry token waste.
failures silently and report a single summary line at the end: CrossRef unavailable for {N} references (rate-limited). Verified via PubMed instead.
/analyze-stats or /check-reporting for that)./write-paper for that).| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-18 | fail→pass | 12,973 | 6,304 | -51% | 1 | 1 | 0% | 2,177 | 7,421 | +241% | 0 | 0 | — |
case-01 | fail→fail | 26,437 | 20,458 | -23% | 1 | 1 | 0% | 5,160 | 9,008 | +75% | 0 | 0 | — |
case-02 | fail→fail | 20,474 | 16,837 | -18% | 1 | 1 | 0% | 3,291 | 9,384 | +185% | 0 | 0 | — |
case-03 | fail→fail | 30,620 | 6,926 | -77% | 1 | 1 | 0% | 6,085 | 6,872 | +13% | 0 | 0 | — |
case-04 | fail→fail | 8,653 | 17,691 | +104% | 1 | 1 | 0% | 1,457 | 9,346 | +541% | 0 | 0 | — |
case-05 | fail→pass | 44,695 | 14,173 | -68% | 1 | 1 | 0% | 8,229 | 8,624 | +5% | 0 | 0 | — |
case-06 | pass→pass | 4,873 | 5,117 | +5% | 1 | 1 | 0% | 666 | 7,147 | +973% | 0 | 0 | — |
case-07 | fail→fail | 15,448 | 6,663 | -57% | 1 | 1 | 0% | 2,761 | 7,491 | +171% | 0 | 0 | — |
case-08 | fail→fail | 12,835 | 9,399 | -27% | 1 | 1 | 0% | 2,453 | 6,948 | +183% | 0 | 0 | — |
case-09 | fail→fail | 9,963 | 5,213 | -48% | 1 | 1 | 0% | 1,798 | 7,314 | +307% | 0 | 0 | — |
case-10 | pass→pass | 11,208 | 6,735 | -40% | 1 | 1 | 0% | 1,938 | 7,496 | +287% | 0 | 0 | — |
case-11 | fail→pass | 10,626 | 7,870 | -26% | 1 | 1 | 0% | 1,706 | 7,442 | +336% | 0 | 0 | — |
case-12 | fail→pass | 4,628 | 10,068 | +118% | 1 | 1 | 0% | 993 | 7,482 | +653% | 0 | 0 | — |
case-13 | fail→pass | 2,436 | 4,824 | +98% | 1 | 1 | 0% | 360 | 7,183 | +1895% | 0 | 0 | — |
case-14 | fail→pass | 8,078 | 4,997 | -38% | 1 | 1 | 0% | 1,321 | 7,276 | +451% | 0 | 0 | — |
case-15 | fail→pass | 12,180 | 16,651 | +37% | 1 | 1 | 0% | 1,962 | 7,173 | +266% | 0 | 0 | — |
case-16 | fail→pass | 7,991 | 9,040 | +13% | 1 | 1 | 0% | 419 | 8,058 | +1823% | 0 | 0 | — |
case-17 | fail→fail | 10,573 | 8,731 | -17% | 1 | 1 | 0% | 2,117 | 7,805 | +269% | 0 | 0 | — |
case-19 | fail→pass | 14,752 | 9,152 | -38% | 1 | 1 | 0% | 2,398 | 7,925 | +230% | 0 | 0 | — |
case-20 | fail→fail | 8,955 | 9,825 | +10% | 1 | 1 | 0% | 1,523 | 6,640 | +336% | 0 | 0 | — |
case-21 | fail→pass | 15,159 | 17,999 | +19% | 1 | 1 | 0% | 2,546 | 9,700 | +281% | 0 | 0 | — |
case-22 | pass→pass | 13,389 | 5,780 | -57% | 1 | 1 | 0% | 2,151 | 7,272 | +238% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 18 counted toward the lift figure. The other 4 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +45 percentage points is the difference between those two pass rates over the 18 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.