ref-manager

Tutorial

Four ways to find papers, one way in

Whatever route surfaces a PMID — a citation you already have, a PICO question, a snowball from a key paper, or a researcher's back-catalog — /ref:add is always the explicit, deliberate step that actually creates a record (D12). What you're holding often isn't a bare PMID, though: a URL, a downloaded PDF, a folder of PDFs, a .bib/.csl.json export, or a DOI. /ref:add-url, /ref:add-pdf, and the source-aware /ref:import resolve those to a PMID first, then call the same underlying add step — so the identity guarantee holds no matter which door you came in. This tutorial walks all four discovery routes plus the intake commands for whatever form the paper takes. Commands used: /ref:add, /ref:add-url, /ref:add-pdf, /ref:add-fetch, /ref:import, /ref:search-pubmed, /ref:update-queries, /ref:related, /ref:person, and /ref:discover.

The journey

1 KNOWN PMID a citation, a colleague's list, a reference manager 2 PICO SEARCH search-pubmed update-queries 3 SNOWBALL related --direction backward | forward 4 PORTFOLIO person + discover then confirm /ref:add <pmid...> the only step that creates papers/<pmid>/meta.json
you already hold the PMID the plugin helps you find candidate PMIDs
Routes 2–4 only ever surface candidates — a saved search run, a snowball row, a discovery candidate. None of them writes a paper record. /ref:add is the single, explicit gate every PMID passes through (D12), so duplicate detection, citekey allocation and the "metadata-only is not a failure" rule live in exactly one place.

The example PMIDs, 9900010199000108, are placeholders outside PubMed's real range. Blocks labelled output from a scratch library were produced by running the plugin's scripts against a throwaway library, standing in by hand for the PubMed MCP calls. Blocks labelled illustrative output need the network or a live MCP tool and follow the output format in the script source.

Route 1 — you already have something to add

Most of the time you already know what you want: a citation from a paper you're reading, a list a colleague emailed, an export from another reference manager. /ref:add takes one or more PMIDs directly — this is the base case every other route eventually feeds into.

output from a scratch library

 /ref:add 99000101 99000102 99000103
99000101: added (citekey=alvarez2023predictive)
99000102: added (citekey=nakamura2024surface)
99000103: added (citekey=okoye2022transcriptomic) — no abstract available — metadata-only record, not fabricated (§3a)
A file of PMIDs works the same way. There's no separate flag for it — /ref:add only takes PMIDs as arguments, so if a colleague sent you pmids.txt (one per line), have Claude read the file and pass its contents as the argument list: /ref:add $(cat pmids.txt), or just paste the list. Every PMID still gets its own independent added / already_present / failed line.
No abstract is not a failure. Some records genuinely have none. added with a metadata-only note is the honest outcome — never a silently fabricated abstract.

When what you have isn't a bare PMID

PMID is still the identity key everything resolves to, but plenty of what actually lands in front of you isn't a PMID yet — a link, a downloaded PDF, a whole folder of PDFs from a shared drive, a .bib/.csl.json export from another reference manager, or a bare DOI copied off a reference list. Four commands cover these, each resolving a candidate PMID first and then calling the same add step:

  • /ref:add-url <url...> — extracts PMID/PMCID/DOI clues from PubMed, PMC, DOI, and publisher URLs (locally first, then a page fetch if needed), confirms ambiguous matches, adds the resolved PMIDs, and fetches full text unless --no-fetch is passed.
  • /ref:add-pdf <path...> — extracts DOI/PMID/PMCID/title clues from local PDFs with pdftotext (no network call), resolves each to a PMID, adds the metadata, then attaches every PDF to its paper.
  • /ref:add-fetch <pmid...> — for when you already have PMIDs and just want add-then-fetch in one step instead of running /ref:add and /ref:fetch separately.
  • /ref:import <item...> — the source-aware front door. Hand it a mix of PMIDs, URLs, PDF paths, a folder of PDFs, .bib/.csl.json files, or bare DOIs in one call. It splits the inputs by type, routes URLs and PDFs through the same identification helpers as /ref:add-url and /ref:add-pdf, expands directories to the PDFs they contain, resolves bibliography-file entries and bare DOIs to PMIDs through PubMed MCP (asking for confirmation on anything ambiguous), then adds and — unless --no-fetch is passed — fetches everything that resolved.

/ref:import is the one to reach for whenever the source type is mixed or you don't want to figure out which specific command applies; /ref:add-url and /ref:add-pdf are worth using directly when you already know you only have one type of thing.

illustrative output

 /ref:import 99000109 https://doi.org/10.1038/s41586-026-00001-2 ~/Downloads/nguyen2025striatal.pdf
identify:
99000109: manual -> 99000109
https://doi.org/10.1038/s41586-026-00001-2: doi -> 99000110
~/Downloads/nguyen2025striatal.pdf: pmid -> 99000111

add:
99000109: added (citekey=singh2024cortical)
99000110: added (citekey=weber2026striatal)
99000111: already_present

fetch:
99000109: acquired
99000110: abstract_only
99000111: duplicate_noop
Bibliography files are source material, not authoritative IDs. A .bib or .csl.json file tells /ref:import what to look for; the entries still have to resolve to a PMID through PubMed MCP before anything is added, the same as a DOI or a title clue would. And as with every route here, one input's failure — an unresolved PDF, an ambiguous DOI, a bad URL — never blocks the rest.

Route 2 — turn a PICO question into a saved search

Give Claude a plain research question. It drafts a PICO-framed boolean PubMed query (MeSH + free text) using its own clinical/scientific judgment, runs it, and freezes the exact expression, source and result PMIDs as an immutable run — /ref:search-pubmed itself never touches PubMed or parses PICO; it only persists what it's handed.

output from a scratch library

 /ref:search-pubmed "Does surface-based transcriptomic decoding predict receptor density?" --slug transcriptomic-decoding --create
# Claude drafts this expression, runs it, then saves the run:
{
  "run_id": "run-2a9f0c6be142",
  "query": "(\"transcriptome\"[MeSH] OR transcriptomic) AND (\"receptors, neurotransmitter\"[MeSH] OR receptor density) AND (predict* OR decod*)",
  "source": "pubmed",
  "retrieved_at": "2026-09-16T09:04:11.000000+00:00",
  "pmids": ["99000104", "99000105", "99000106"]
}

The saved run is a list of PMIDs, nothing more — it doesn't add anything to the library by itself. Read the run's PMIDs and hand them to /ref:add:

output from a scratch library

 /ref:add 99000104 99000105 99000106
99000104: added (citekey=lindqvist2024receptor)
99000105: added (citekey=chen2025decoding)
99000106: already_present
Re-run it later without re-deriving it. /ref:update-queries transcriptomic-decoding re-runs the exact stored expression — it never re-derives a new query from your original question — appends a new immutable run, and reports added/removed PMIDs since the last run. Add whatever's new the same way, through /ref:add.

Route 3 — snowball from a paper you already have

/ref:related <pmid> follows a known paper two directions: backward extracts its reference list from already-committed full text (no network call), and forward asks PubMed's find_related_articles for similar papers. Read this carefully before quoting it as a citation graph — see the note below.

output from a scratch library

 /ref:related 99000104 --direction backward
{
  "available": true,
  "count": 2,
  "candidates": [
    { "candidate": "Smith J, et al. Receptor mapping in cortex. J Neurosci 2021.",
      "resolved": false, "direction": "backward", "method": "reference_list" },
    { "candidate": "Diaz R, et al. Transcriptomic gradients revisited. Nat Commun 2020.",
      "resolved": false, "direction": "backward", "method": "reference_list" }
  ]
}

illustrative output

 /ref:related 99000104 --direction forward
{
  "count": 2, "zero_results": false, "note": null,
  "rows": [
    { "candidate": "99000107", "resolved": true, "direction": "forward",
      "method": "find_related_articles:pubmed_pubmed", "already_in_library": false },
    { "candidate": "99000105", "resolved": true, "direction": "forward",
      "method": "find_related_articles:pubmed_pubmed", "already_in_library": true }
  ]
}
pubmed_pubmed is similarity, not "cited by." The live PubMed MCP tool has no true citation-graph link type — the default is word-weighted title/abstract/MeSH similarity. Every forward row records exactly which link_type produced it, so this is never presented as citation data it isn't. Backward candidates are raw reference-list text, not resolved PMIDs; most don't carry one. Look one up yourself if it's worth the effort — don't auto-resolve an entire reference list.

Either direction only surfaces candidates — already_in_library: false is the signal to act on:

output from a scratch library

 /ref:add 99000107
99000107: added (citekey=park2024receptor)

Route 4 — a researcher's back-catalog

Confirm a person's identity first — name similarity alone never establishes it (§3d). Then /ref:discover builds an author-search query from their name variants and ORCID, runs it, and stores the hits as candidates on that person's record, never as library additions.

output from a scratch library

 /ref:person create ecker-c --name "Ecker, Christine" --orcid 0000-0000-0000-0001
{ "slug": "ecker-c", "name_variants": ["Ecker, Christine"], "orcid": "0000-0000-0000-0001",
  "confirmed_publications": [], "candidate_publications": [], "rejected_publications": [] }
 /ref:discover --person ecker-c
{
  "slug": "ecker-c",
  "discovery_runs": [{ "query": "Ecker C[Author] AND 0000-0000-0000-0001[Author - Identifier]",
                        "retrieved_at": "2026-09-16T09:11:03.000000+00:00", "candidate_count": 2 }],
  "candidate_publications": [
    { "pmid": "99000108", "title": "Cortical thickness trajectories in a longitudinal cohort" },
    { "pmid": "99000105", "title": "Transcriptomic decoding of surface-based imaging phenotypes" }
  ]
}

A candidate isn't this person's confirmed publication until you link a specific author-list position — the evidenced identity match §3c requires:

output from a scratch library

 /ref:person confirm-publication ecker-c --pmid 99000108 --author-index 3
{ "slug": "ecker-c", "confirmed_publications": [{ "pmid": "99000108", "author_index": 3 }],
  "candidate_publications": [ … 99000105 still pending … ] }

Confirming an identity match still doesn't create a paper record — that's still /ref:add:

output from a scratch library

 /ref:add 99000108
99000108: added (citekey=ecker2026cortical)
A rejection sticks. If a candidate is a false match (a different, same-named author), /ref:person reject-publication ecker-c --pmid <id> records that permanently — rejected PMIDs are never re-suggested by a later /ref:discover run.

What you now have

<LIBRARY>/
  papers/99000101/ … 99000108/
    meta.json                        # every route above ends here, via /ref:add
  queries/
    transcriptomic-decoding.yaml     # immutable runs from /ref:search-pubmed + /ref:update-queries
  papers/99000104/
    related.json                    # backward + forward candidates, upserted not overwritten
  people/
    ecker-c.json                     # confirmed / candidate / rejected publication state

Once papers are in, get their full text with the /ref:fetch tutorial steps in Systematic review, or jump straight to Thesis chapter for the read-screen-extract-synthesize path. Command reference: /ref:add, /ref:add-url, /ref:add-pdf, /ref:add-fetch, /ref:import, /ref:search-pubmed, /ref:update-queries, /ref:related, /ref:discover.