Skip to main content
Glama
dingyifei

pubmed-proxy-mcp

by dingyifei
README.md
# pubmed-proxy-mcp

MCP server and CLI for NCBI PubMed E-utilities with built-in rate limiting.

## Features

- **PubMed search** — Full query syntax with field tags, boolean operators, date ranges, pagination, and sorting
- **Article metadata** — Fetch title, authors, journal, year, DOI, and abstract for any PMID
- **Batch lookup** — Fetch metadata for up to 20 PMIDs at once
- **Full-text retrieval** — Get full article text from PubMed Central (when available)
- **BibTeX citations** — Generate BibTeX entries from PMID or DOI (with CrossRef fallback)
- **Bibliography management** — Add citations to .bib files, search, list, validate, and batch-initialize
- **BibTeX correction** — Correct .bib files against authoritative NCBI/CrossRef metadata with smart field comparison
- **Rate limiting** — Built-in token bucket (10 req/s NCBI, 1 req/s CrossRef)

## Quick Start for LLM Agents

If you are an LLM agent that has been pointed to this tool for literature research, follow these steps:

**1. Check that the NCBI API key is configured (recommended but optional):**

```bash
echo $NCBI_API_KEY
```

If empty, commands will still work but at a lower rate limit (3 req/s instead of 10 req/s).
To get the faster rate, set an API key:

```bash
export NCBI_API_KEY="your-api-key"
```

Or pass it per-command with `--api-key <key>`.
To obtain a key: create an account at https://www.ncbi.nlm.nih.gov/account/, then go to
Settings > API Key Management > Create an API Key.

**2. If using MCP tools** (e.g. via Claude Desktop), the MCP server must be configured
in the client. The CLI commands below work standalone without the MCP server.

**3. Typical research workflow:**

```bash
# Search for relevant articles
pubmed-proxy search "your topic here" --summaries --max 10

# Get full metadata + abstract for interesting PMIDs
pubmed-proxy lookup 23381990

# Get full-text if available (PMC only)
pubmed-proxy lookup --full-text 23381990

# Generate BibTeX citations for references
pubmed-proxy citation 23381990 16855256 >> references.bib
```

## Installation

```bash
pip install -e .            # Core (MCP server + basic CLI)
pip install -e ".[bib]"     # With bibliography management (bibtexparser)
```

Requires Python 3.10+. An NCBI API key is recommended for higher rate limits (see Quick Start above).

## MCP Server

Run as an MCP server (stdio transport). Works without an API key (3 req/s) or with one (10 req/s):

```bash
python -m pubmed_proxy_mcp
```

### MCP Tools

| Tool | Description |
|------|-------------|
| `search_articles` | Search PubMed with full query syntax, filters, pagination, optional summaries |
| `get_article_metadata` | Get metadata + abstract for a single PMID |
| `batch_get_metadata` | Batch fetch metadata for up to 20 PMIDs |
| `get_full_text` | Get full-text article content via PMC |
| `get_citation_bibtex` | Get BibTeX entry for a PMID |
| `get_citation_by_doi` | Get BibTeX entry for a DOI |
| `search_doi_to_pmid` | Convert DOI to PMID |
| `bib_add_citation` | Fetch BibTeX and add to a .bib file |
| `bib_search` | Search across all .bib files for a keyword |
| `bib_list` | List entries in .bib files |
| `bib_show` | Show full BibTeX for a citekey |
| `bib_validate` | Validate citekeys in markdown against .bib |
| `bib_init` | Scan markdown for PMIDs and create .bib files |
| `bib_stats` | Summary statistics for .bib files |
| `bib_correct` | Correct a .bib file against NCBI/CrossRef metadata |

## CLI Reference

An NCBI API key is optional but recommended. Without one, the rate limit is 3 req/s
instead of 10 req/s. If not set, a warning is printed to stderr but commands still work.

```bash
echo $NCBI_API_KEY    # If empty, commands work at 3 req/s
```

To use a key, either `export NCBI_API_KEY=<key>` or pass `--api-key <key>` to any command.

### `pubmed-proxy server` — Shared Rate-Limiting Proxy

**Note:** The proxy server is only needed when running multiple CLI commands in parallel
(e.g. from multiple subagents). For single-command usage, the CLI works fine on its own
with its built-in rate limiter — no server required.

When running parallel commands, each process has its own rate limiter. To share a single
rate limiter across all processes, start the proxy server first:

```bash
# Terminal 1: start the shared proxy server
pubmed-proxy server                    # default port 8765
pubmed-proxy server --port 9000        # custom port
```

Then point CLI commands at it with `--server`:

```bash
# Terminal 2+: commands share the proxy's rate limiter
pubmed-proxy --server http://localhost:8765 search "CRISPR" --max 10
pubmed-proxy --server http://localhost:8765 lookup 23381990
```

The MCP server also auto-detects the proxy: if `pubmed-proxy server` is running on
port 8765 when the MCP server starts, it will use the shared rate limiter automatically.
Set `PUBMED_PROXY_URL` to override the proxy URL for the MCP server.

**Arguments:**

| Argument | Required | Description |
|----------|----------|-------------|
| `--port PORT` | No | Server port (default: 8765) |

**Global `--server` flag:**

| Argument | Required | Description |
|----------|----------|-------------|
| `--server URL` | No | Proxy server URL (e.g. `http://localhost:8765`). When set, all NCBI requests go through the proxy for shared rate limiting |

---

### `pubmed-proxy search` — Search PubMed

Search PubMed using full NCBI query syntax. Supports field-specific tags, boolean
operators (`AND`, `OR`, `NOT`), and MeSH terms.

**Arguments:**

| Argument | Required | Description |
|----------|----------|-------------|
| `query` | Yes | PubMed search query string |
| `--max N` | No | Maximum results to return (default: 20, max: 10000) |
| `--page N` | No | Page number, 0-indexed (default: 0). Page 1 with --max 10 starts at result 11 |
| `--sort ORDER` | No | Sort order: `relevance` (default), `pub_date`, `author`, `journal` |
| `--date-type TYPE` | No | Date field to filter: `pdat` (publication date) or `edat` (Entrez date) |
| `--min-date DATE` | No | Start date filter, format `YYYY/MM/DD` or `YYYY` |
| `--max-date DATE` | No | End date filter, format `YYYY/MM/DD` or `YYYY` |
| `--summaries` | No | Include title, authors, journal, and year for each result |

**Query syntax examples:**

- `"E. coli iron regulation"` — keyword search
- `"Smith[Author] AND cancer[Title]"` — field-specific search
- `"CRISPR[Title] AND 2024[PDAT]"` — with publication date tag
- `"breast cancer AND review[PT]"` — filter by publication type
- `"Nature[Journal] AND gene editing"` — journal-specific search

**Output without `--summaries`:**

```
Found 1543 results (showing 1-20):

  38471234
  38469012
  ...
```

**Output with `--summaries`:**

```
Found 1543 results (showing 1-5):

  PMID: 38471234
  Title: CRISPR-Cas9 gene editing in E. coli
  Authors: Smith, JD; Doe, A; Lee, B (+4 more)
  Journal: Nature Biotechnology (2024)

  PMID: 38469012
  Title: Another relevant paper
  Authors: Jones, C; Wang, X
  Journal: Science (2024)
```

**Examples:**

```bash
# Basic keyword search
pubmed-proxy search "antibiotic resistance"

# Field-specific with boolean operators
pubmed-proxy search "Smith[Author] AND cancer[Title]"

# Filter by date range (publication date)
pubmed-proxy search "CRISPR" --min-date 2023/01/01 --max-date 2024/12/31 --date-type pdat

# Sort by publication date, get 10 results
pubmed-proxy search "biofilm" --max 10 --sort pub_date

# Page 2 of results (results 11-20)
pubmed-proxy search "biofilm" --max 10 --page 1

# Include metadata summaries for quick scanning
pubmed-proxy search "antibiotic resistance" --summaries --max 5
```

---

### `pubmed-proxy lookup` — Get Article Metadata

Fetch metadata and abstract for one or more PubMed articles by PMID.

**Arguments:**

| Argument | Required | Description |
|----------|----------|-------------|
| `pmids` | Yes | One or more PubMed IDs (space-separated) |
| `--full-text` | No | Fetch full article text from PMC instead of just metadata. Only works for the first PMID. Not all articles have free full text |

**Output (metadata mode):**

```
PMID: 23381990
Valid: Yes
Title: The Fur regulon of pathogenic Neisseria
Authors: Smith, JD, Doe, A, Lee, B, Wang, X, Jones, C
Journal: Journal of Bacteriology (2013)
DOI: 10.1128/JB.01411-12
Abstract: The ferric uptake regulator (Fur) is a transcription factor...
```

For multiple PMIDs, each article's metadata is separated by a blank line.
Invalid PMIDs return `Valid: No` with an error message.

**Output (full-text mode):**

```
PMID: 23381990 (Full Text)
PMCID: PMC3697534

## Introduction

The ferric uptake regulator (Fur) is a global...

## Results

We identified 47 genes...
```

If the article is not available in PMC, shows: `Error: Full text not available in PMC`

**Examples:**

```bash
# Single article metadata + abstract
pubmed-proxy lookup 23381990

# Batch lookup (up to 20 PMIDs)
pubmed-proxy lookup 23381990 16855256 25157846

# Full article text (PMC only, first PMID only)
pubmed-proxy lookup --full-text 23381990
```

---

### `pubmed-proxy citation` — Fetch BibTeX Citations

Generate BibTeX `@article` entries for PubMed articles. Output goes to stdout and can
be piped or appended to .bib files. Citekeys use the format `pmid_XXXXXXXX`.

**Arguments:**

| Argument | Required | Description |
|----------|----------|-------------|
| `pmids` | No* | One or more PubMed IDs (space-separated) |
| `--doi DOI` | No* | Fetch BibTeX by DOI instead. Tries NCBI first for better metadata, falls back to CrossRef |

*Provide either pmids or --doi.

**Output:**

```bibtex
@article{pmid_23381990,
  author = {Smith, JD and Doe, A},
  title = {{The Fur regulon of pathogenic Neisseria}},
  journal = {J Bacteriol},
  year = {2013},
  volume = {195},
  number = {14},
  pages = {3159-3170},
  doi = {10.1128/JB.01411-12},
  pmid = {23381990}
}
```

Multiple PMIDs produce multiple entries separated by blank lines.
DOI-only entries (not in PubMed) use citekey format `doi_<sanitized_doi>`.

**Examples:**

```bash
# Single PMID to stdout
pubmed-proxy citation 23381990

# Multiple PMIDs, append to a file
pubmed-proxy citation 23381990 16855256 25157846 >> references.bib

# By DOI (tries NCBI first, falls back to CrossRef)
pubmed-proxy citation --doi 10.1128/JB.01411-12
```

---

### `pubmed-proxy doi2pmid` — Convert DOI to PMID

Look up the PubMed ID for a given DOI.

**Arguments:**

| Argument | Required | Description |
|----------|----------|-------------|
| `doi` | Yes | Digital Object Identifier |

**Output:**

```
DOI: 10.1038/s41467-019-13483-w
PMID: 31784527
```

If no PMID is found: `PMID: not found`

**Example:**

```bash
pubmed-proxy doi2pmid 10.1038/s41467-019-13483-w
```

---

### `pubmed-proxy correct` — Correct BibTeX Files

Correct a .bib file by verifying each entry against authoritative NCBI/CrossRef
metadata. For each entry, fetches correct metadata by DOI, existing PMID, or
title search (with author-overlap validation to avoid wrong matches).

Requires `pip install -e ".[bib]"`.

**Smart comparison rules:**
- Authors: never replaces full names with initials; expands "and others"; flags mismatches for review
- Pages: normalizes abbreviated ranges (e.g. `735-47` to `735-747`); preserves LaTeX en-dashes
- Journal: prefers longer (more complete) journal name
- Title: never touched (may contain LaTeX formatting)
- Year/volume/number/DOI: trusts authoritative source

**Arguments:**

| Argument | Required | Description |
|----------|----------|-------------|
| `-i, --input FILE` | Yes | Input .bib file path |
| `-o, --output FILE` | No | Output .bib file path. If omitted, corrects in-place and creates a `.bib.bak` backup |

**Output:**

```
  entry1_key: OK via DOI
  entry2_key: FIXED 2 field(s) via DOI
    year: 2012 -> 2013
    pages: 735-47 -> 735-747
  entry3_key: FAIL could not fetch (no DOI/PMID)

Summary: 15 entries processed
  Verified OK:  12
  Corrected:    2
  Failed:       1
  Skipped:      0
Output: corrected.bib
```

**Examples:**

```bash
# Correct to a new output file
pubmed-proxy correct -i references.bib -o corrected.bib

# Correct in-place (creates references.bib.bak backup)
pubmed-proxy correct -i references.bib
```

---

### `pubmed-proxy bib` — Bibliography Management

Manage .bib files: add citations, search, list, validate, and batch-initialize.
All `bib` subcommands accept `--dir DIR` to specify the directory containing .bib
files (default: current directory).

Requires `pip install -e ".[bib]"` (bibtexparser).

#### `bib fetch` — Add Citation to .bib File

Fetch a BibTeX citation by PMID or DOI and add it to a .bib file. Automatically
deduplicates — skips entries whose citekey already exists.

| Argument | Required | Description |
|----------|----------|-------------|
| `identifiers` | No* | One or more PubMed IDs |
| `--doi DOI` | No* | Fetch by DOI instead |
| `--bib FILE` | Yes | Target .bib filename (e.g. `Fur.bib`) |

*Provide either identifiers or --doi.

```bash
pubmed-proxy bib fetch 23381990 --bib Fur.bib
pubmed-proxy bib fetch 23381990 16855256 --bib refs.bib
pubmed-proxy bib fetch --doi 10.1128/JB.01411-12 --bib Fur.bib
pubmed-proxy bib fetch 23381990 --bib refs.bib --dir ./citations
```

Output: `Added: pmid_23381990` or `Already exists: pmid_23381990`

#### `bib search` — Search Across .bib Files

Search all .bib files in a directory for a keyword. Searches citekeys and all fields
(title, author, journal, etc.). Case-insensitive.

| Argument | Required | Description |
|----------|----------|-------------|
| `keyword` | Yes | Search term |

```bash
pubmed-proxy bib search "Fur"
pubmed-proxy bib search "iron regulation" --dir ./citations
```

Output:

```
Found 3 entries matching 'Fur':

  [Fur.bib] @pmid_23381990: The Fur regulon of pathogenic Neisseria
  [Fur.bib] @pmid_16855256: Fur-mediated iron transport in E. coli
  [iron.bib] @pmid_25157846: Iron homeostasis and Fur regulation
```

#### `bib list` — List Entries in .bib Files

List all entries in all .bib files, or in a specific file.

| Argument | Required | Description |
|----------|----------|-------------|
| `--bib FILE` | No | List only this .bib file (default: all files in directory) |

```bash
pubmed-proxy bib list --dir ./citations
pubmed-proxy bib list --bib Fur.bib --dir ./citations
```

Output:

```
Fur.bib (3 entries):
  @pmid_23381990: The Fur regulon of pathogenic Neisseria (2013)
  @pmid_16855256: Fur-mediated iron transport in E. coli (2006)
  @pmid_25157846: Iron homeostasis and Fur regulation (2014)
```

#### `bib show` — Show Full BibTeX for a Citekey

Look up a citekey across all .bib files and print the complete BibTeX entry.

| Argument | Required | Description |
|----------|----------|-------------|
| `citekey` | Yes | Citation key (e.g. `pmid_23381990`) |

```bash
pubmed-proxy bib show pmid_23381990
pubmed-proxy bib show pmid_23381990 --dir ./citations
```

Output: the full `@article{...}` BibTeX block, preceded by `# From <filename>`.

#### `bib validate` — Validate Citekeys in Markdown

Check that all `@citekey` references and `PMID:XXXXX` patterns in a markdown file
exist in the specified .bib file.

| Argument | Required | Description |
|----------|----------|-------------|
| `file` | Yes | Markdown file to validate |
| `--bib FILE` | Yes | .bib file to validate against |

```bash
pubmed-proxy bib validate evidence.md --bib evidence.bib --dir ./citations
```

Output on success: `OK: 5 citekeys validated, 2 raw PMIDs all in bib`

Output on failure (exits with code 1):

```
MISSING citekeys (not in evidence.bib):
  @pmid_99999999
UNCONVERTED PMIDs (still using PMID:XXXXX format):
  PMID:12345678
```

#### `bib validate-all` — Validate All Evidence Files

Scan all markdown files in a directory and validate each one against its
corresponding .bib file (e.g. `Fur.md` against `Fur.bib`).

| Argument | Required | Description |
|----------|----------|-------------|
| `--scan-dir DIR` | No | Directory containing markdown files (default: current directory) |

```bash
pubmed-proxy bib validate-all --scan-dir ./evidence --dir ./citations
```

Output on success: `All evidence files validated (12 bib files)`

Output on failure (exits with code 1):

```
Fur.md: 2 missing citekey(s) — pmid_99999999, pmid_88888888
iron.md: 1 unconverted PMID(s)

Total: 3 issues across 2 files
```

#### `bib init` — Batch-Create .bib Files from Markdown

Scan markdown files for `PMID:XXXXX` references, fetch BibTeX from PubMed for each,
and create per-file .bib files (e.g. `Fur.md` produces `Fur.bib`).

| Argument | Required | Description |
|----------|----------|-------------|
| `--scan-dir DIR` | No | Directory containing markdown files (default: current directory) |

```bash
pubmed-proxy bib init --scan-dir ./evidence --dir ./citations
```

Output:

```
Found 47 unique PMIDs across 12 files
  Fetching PMID: 23381990...
  Fetching PMID: 16855256...
  ...
Fetched 45/47 PMIDs
Created/updated 12 .bib files in ./citations
```

#### `bib stats` — Citation Database Statistics

Print summary statistics for all .bib files in a directory.

```bash
pubmed-proxy bib stats --dir ./citations
```

Output:

```
Citation Database Statistics:
  .bib files:       12
  Total entries:    47
  Unique citekeys:  45
```

---

## Claude Desktop Configuration

Add to your Claude Desktop config (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "pubmed": {
      "command": "python",
      "args": ["-m", "pubmed_proxy_mcp"],
      "env": {
        "NCBI_API_KEY": "your-api-key"
      }
    }
  }
}
```

The `NCBI_API_KEY` in the `env` block is optional but recommended for the higher 10 req/s
rate limit. Without it the server works at 3 req/s. Replace `"your-api-key"` with your
actual key from https://www.ncbi.nlm.nih.gov/account/settings/.