cookjohn/pm-skills · Archived

pm-export

Export PubMed paper(s) to Zotero or save as RIS file. Supports single paper by PMID or batch export from search results. Use when user wants to save papers to their reference manager.

First seen Apr 9, 2026

Installation

$ npx skills add cookjohn/pm-skills --skill pm-export

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from cookjohn/pm-skills.

npx skills add cookjohn/pm-skills

Browse all from cookjohn/pm-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 19
License MIT
Default branch main
Open issues 0
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Declared agents claude-code

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 5,537 B
  • docs SUMMARY.md 200 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 6 installs

SKILL.md

PubMed Export to Zotero

Export PubMed paper citation data and push to Zotero desktop via local Connector API, or generate MEDLINE/RIS format.

Arguments

$ARGUMENTS contains one or more PMIDs (space-separated), e.g.:

  • 35641793 — single paper
  • 35641793 27741350 35362092 — batch export

Steps

Step 1: Fetch paper metadata (evaluate_script)

Use mcpchrome-devtoolsevaluatescript on any PubMed page. Replace PMIDLIST with the actual PMIDs:

async () => {
  const pmids = "PMID_LIST".split(/[\s,]+/).filter(Boolean);

  // Batch esummary for basic metadata
  const sumResp = await fetch(
    `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&id=${pmids.join(',')}&retmode=json`
  );
  const sumData = await sumResp.json();

  // efetch XML for full records (abstracts, keywords)
  const fetchResp = await fetch(
    `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=${pmids.join(',')}&retmode=xml`
  );
  const xml = await fetchResp.text();
  const xmlDoc = new DOMParser().parseFromString(xml, 'text/xml');

  const papers = pmids.map(pmid => {
    const r = sumData.result?.[pmid] || {};
    const doi = (r.articleids || []).find(a => a.idtype === 'doi')?.value || '';

    // Find this paper's article in the XML
    const articles = xmlDoc.querySelectorAll('PubmedArticle');
    let abstract = '', keywords = [], meshTerms = [], pubTypes = [];
    let issn = '', pmcid = '';
    for (const art of articles) {
      if (art.querySelector('PMID')?.textContent === pmid) {
        abstract = Array.from(art.querySelectorAll('AbstractText'))
          .map(el => el.textContent).join(' ');
        keywords = Array.from(art.querySelectorAll('Keyword'))
          .map(k => k.textContent);
        meshTerms = Array.from(art.querySelectorAll('MeshHeading DescriptorName'))
          .map(d => d.textContent);
        pubTypes = Array.from(art.querySelectorAll('PublicationType'))
          .map(p => p.textContent);
        issn = art.querySelector('ISSN')?.textContent || '';
        pmcid = art.querySelector('ArticleId[IdType="pmc"]')?.textContent || '';
        break;
      }
    }

    return {
      pmid,
      title: r.title || '',
      authors: (r.authors || []).map(a => ({
        lastName: a.name?.split(' ')[0] || '',
        firstName: a.name?.split(' ').slice(1).join(' ') || '',
      })),
      journal: r.fulljournalname || '',
      journalAbbr: r.source || '',
      pubdate: r.pubdate || '',
      volume: r.volume || '',
      issue: r.issue || '',
      pages: r.pages || '',
      doi,
      issn,
      pmcid,
      abstract,
      keywords,
      pubtype: pubTypes,
      language: (r.lang || ['eng'])[0] === 'eng' ? 'en' : (r.lang || ['eng'])[0]
    };
  });

  return papers;
}

Step 2: Push to Zotero

Save the returned JSON array to a temp file, then run the Python script:

python "e:/pm-skills/.claude/skills/pm-export/scripts/push_to_zotero.py" /tmp/pm_papers.json

The Python script runs a 3-step flow:

  1. saveItems — saves metadata to Zotero (POST /connector/saveItems). Note: Zotero 7.x ignores the attachments field in saveItems.
  2. download_pdf — downloads PDF binary via Python urllib (if pdfUrl or pmcid is available)
  3. saveAttachment — uploads PDF binary to Zotero (POST /connector/saveAttachment), linked to the parent item via parentItemID in the X-Metadata header

Other details:

  • Builds Zotero-compatible item JSON (itemType: journalArticle, libraryCatalog: PubMed)
  • Parses PubMed author format into Zotero's {firstName, lastName} structure
  • Sets DOI, PMID (in extra field), URL to PubMed page
  • Uses deterministic session ID for idempotency (same papers → same session → no duplicates)
  • PDF download may fail for some publishers (403, JS-redirect pages); these are reported as "PDF skip"

Step 3: Report

Single paper:

Exported to Zotero:
  Title: {title}
  Authors: {authors}
  Journal: {journal} ({pubdate})
  DOI: {doi}
  PMID: {pmid}

Batch:

Exported {count} papers to Zotero:
  1. {title1} ({journal1}, {pubdate1})
  2. {title2} ({journal2}, {pubdate2})
  ...

MEDLINE Export (alternative)

If the user wants a file instead of Zotero, use efetch to get MEDLINE format directly:

async () => {
  const pmids = "PMID_LIST";
  const resp = await fetch(
    `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=${pmids}&rettype=medline&retmode=text`
  );
  return await resp.text();
}

Save the returned text as a .nbib file. This can be imported into Zotero, EndNote, or any reference manager.

Zotero Troubleshooting

  • Zotero not running: Script returns "Zotero is not running". User must start Zotero desktop.
  • Read-only library: Script returns "Target library is read-only". User must select a writable collection.
  • Already saved: Script returns 409, treated as success (idempotent).

Notes

  • This skill uses 2 tool calls: evaluate_script + bash python
  • Batch export works for any number of PMIDs in a single call
  • The Python script is at e:/pm-skills/.claude/skills/pm-export/scripts/pushtozotero.py
  • Zotero must be running with the Connector API on localhost:23119