# Zotero Keeper

> Zotero 10+ Local API and PubMed MCP research workspace: Keeper 2.1, PubMed Search 0.6.1, and a one-click VS Code extension.

- **Type:** MCP server
- **Install:** `agentstack add mcp-u9401066-zotero-keeper`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [u9401066](https://agentstack.voostack.com/s/u9401066)
- **Installs:** 0
- **Category:** [Developer Tools](https://agentstack.voostack.com/c/developer-tools)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [u9401066](https://github.com/u9401066)
- **Source:** https://github.com/u9401066/zotero-keeper
- **Website:** https://marketplace.visualstudio.com/items?itemName=u9401066.vscode-zotero-mcp

## Install

```sh
agentstack add mcp-u9401066-zotero-keeper
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Zotero Keeper 📚

Let AI manage your references! A MCP Server connecting VS Code Copilot / Claude Desktop to your local Zotero library.

[](https://www.python.org/downloads/)
[](https://github.com/modelcontextprotocol/python-sdk)
[](LICENSE)
[](https://www.zotero.org/)
[](https://github.com/u9401066/zotero-keeper/actions/workflows/ci.yml)
[](CONTRIBUTING.md)

> 🌐 **English** | **[繁體中文](README.zh-TW.md)**

---

## 🚀 Recommended Install (VS Code)

> **Prerequisites**: [Zotero 7, 8, 9, or 10+](https://www.zotero.org/download/) must be running. Zotero 10+ is required for authorized Local API writes.

[📦 Install Zotero + PubMed MCP from the VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=u9401066.vscode-zotero-mcp)

The **v0.7.0 VSIX is the recommended distribution**. It creates an isolated environment and installs Zotero Keeper 2.1.0 plus the pinned PubMed Search MCP 0.6.1 snapshot. The `uvx`/PyPI path remains available for older direct-server installs, but it must not be treated as the 2.1 release until PyPI is updated.

> ⚠️ MCP SDK 2.0 is not compatible with 1.x. After upgrading the extension, run **Zotero MCP: Reinstall Python Environment** if VS Code still has an older managed environment.

---

## ✨ What is this?

**Zotero Keeper** is a [MCP Server](https://modelcontextprotocol.io/) that lets your AI assistant:

- 🔍 **Search references**: "Find papers about CRISPR from 2024"
- 📖 **View details**: "What's the abstract of this article?"
- ➕ **Add references**: "Add this DOI to my Zotero" (with auto-fetch metadata!)
- 🔄 **PubMed integration**: "Search PubMed, skip what I already have"
- 📁 **Interactive save**: Shows collection options for you to choose!
- 🗂️ **Zotero 10+ organization**: Create nested collections, update existing records, add notes, and attach files to records already in your library
- 📚 **Modern literature discovery**: PubMed Search MCP 0.6.1 exposes 45 tools in 16 categories, including the two-tool Research Chronicle workflow

No more manually searching, copying, pasting. Just tell your AI in natural language!

---

## ✨ Features

- **🔌 MCP SDK 2.0 native**: Built on the v2 `MCPServer` API (not the incompatible 1.x `FastMCP` surface)
- **📖 MCP Resources**: Browse Zotero data via URIs (`zotero://collections`, etc.)
- **💬 MCP Elicitation**: Interactive collection selection using an exact collection key; `ROOT` always requires a second confirmation
- **🔒 Auto-fetch Metadata**: DOI/PMID → complete abstract + all fields automatically!
- **📊 Citation Metrics**: RCR and NIH Percentile stored in Zotero extra fields
- **🛡️ Collection Validation**: Use `collection_name` for safer auto-validation
- **📖 Read Operations**: Search, list, and retrieve items from local Zotero
- **✏️ Write Operations**: Keep Connector imports for Zotero 7–9; use runtime-authorized Local API writes on Zotero 10+
- **🧠 Smart Features**: Duplicate detection, validation, intelligent import
- **📁 Collection Support**: Nested collections (folders) with hierarchy
- **🏗️ Clean Architecture**: DDD with onion architecture
- **🔒 Local Zotero boundary**: Zotero library operations stay on the local
  loopback API; PubMed discovery uses the configured external literature APIs

---

## 🚀 Quick Start

### Prerequisites

- ✅ [Python 3.12+](https://www.python.org/downloads/)
- ✅ [Zotero 7, 8, 9, or 10+](https://www.zotero.org/download/) (must be running; 10+ for Local API writes)
- ✅ [VS Code](https://code.visualstudio.com/) + GitHub Copilot, or [Claude Desktop](https://claude.ai/)
- ✅ [uv](https://docs.astral.sh/uv/getting-started/installation/) (recommended)

### Installation

```bash
# Clone the repository
git clone https://github.com/u9401066/zotero-keeper.git
cd zotero-keeper/mcp-server

# Install with uv (required)
uv sync --extra all

# Test (make sure Zotero is running)
uv run python -m zotero_mcp
```

### Configure VS Code Copilot

Create `.vscode/mcp.json` in your workspace:

```json
{
  "servers": {
    "zotero-keeper": {
      "type": "stdio",
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/path/to/zotero-keeper/mcp-server",
        "python", "-m", "zotero_mcp"
      ]
    }
  }
}
```

### Configure Claude Desktop

Add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "zotero-keeper": {
      "command": "uv",
      "args": ["run", "python", "-m", "zotero_mcp"],
      "cwd": "/path/to/zotero-keeper/mcp-server"
    }
  }
}
```

> 💡 Use absolute paths and ensure [uv](https://docs.astral.sh/uv/) is installed.

### Common Environment Variables

If you run the server directly, these are the main settings you may want to provide via `.env` or your MCP launcher configuration:

```bash
ZOTERO_HOST=localhost
ZOTERO_PORT=23119
ZOTERO_TIMEOUT=30
NCBI_EMAIL=your.email@example.com
# NCBI_API_KEY=your_api_key_here
# ZOTERO_KEEPER_ENABLE_LEGACY_PUBMED_TOOLS=1
# PUBMED_SEARCH_PATH=/path/to/pubmed-search-mcp
```

- Use `NCBI_EMAIL` and optional `NCBI_API_KEY` for higher NCBI/PubMed rate limits.
- Use `ZOTERO_KEEPER_ENABLE_LEGACY_PUBMED_TOOLS=1` only if you intentionally want keeper's older PubMed bridge/import tools.
- Use `PUBMED_SEARCH_PATH` only during local development when you want keeper to import a checked-out `pubmed-search-mcp` tree instead of the installed package.

---

## 📚 Documentation Map

- [README.zh-TW.md](README.zh-TW.md) — Traditional Chinese overview
- [mcp-server/README.md](mcp-server/README.md) — focused server usage and tool reference
- [vscode-extension/README.md](vscode-extension/README.md) — VS Code extension setup and UX
- [docs/COLLABORATION_WORKFLOW.md](docs/COLLABORATION_WORKFLOW.md) — collaboration-safe flow between pubmed-search-mcp and keeper
- [docs/tools-reference.md](docs/tools-reference.md) — parameter reference and examples for public tools
- [docs/faq.md](docs/faq.md) — installation, troubleshooting, and workflow FAQ
- [docs/ZOTERO_LOCAL_API.md](docs/ZOTERO_LOCAL_API.md) — Zotero API capability notes and limitations
- [docs/ZOTERO_MCP_LANDSCAPE.md](docs/ZOTERO_MCP_LANDSCAPE.md) — what is official, what is community-maintained, and safe coexistence guidance
- [ARCHITECTURE.md](ARCHITECTURE.md) — component and layering overview
- [CONTRIBUTING.md](CONTRIBUTING.md) — development workflow and contribution guide

---

## 🔧 Available Tools (32 default public + 5 legacy opt-in)

> 💡 **Tip**: Most read operations can also be done via [MCP Resources](#-mcp-resources-browsable-data) without calling tools.

### 📖 Core Tools (server.py - 6 tools)

| Tool | Description | Example |
|------|-------------|---------|
| `check_connection` | Test Zotero connectivity | "Is Zotero running?" |
| `search_items` | Search references | "Find papers about CRISPR" |
| `get_item` | Get item details | "Show abstract for key:ABC123" |
| `list_items` | List recent items | "Show papers in collection X" |
| `list_tags` | List all tags | "What tags have I used?" |
| `get_item_types` | Available item types | "What types can I add?" |

### 📁 Collection Tools (server.py - 5 tools)

> ⚠️ These can also be accessed via `zotero://collections/...` Resources

| Tool | Description | Equivalent Resource |
|------|-------------|--------------------|
| `list_collections` | List all folders | `zotero://collections` |
| `get_collection` | Get collection details | `zotero://collections/{key}` |
| `get_collection_items` | Items in a collection | `zotero://collections/{key}/items` |
| `get_collection_tree` | Hierarchical tree view | `zotero://collections/tree` |
| `find_collection` | Find by name | — (Tool only) |

### 🗂️ Zotero 10+ Local API Tools (local_api_tools.py - 8 tools)

These tools use Zotero's official Local API v3 write support. Before preview,
obtain a response-bound `server_id` from a Local API read or
`authorize_local_writes`, and include it as `expected_server_id`. The key stays
inside the Keeper process; all writes are restricted to loopback and bound to
the reviewed Zotero Server-ID.

| Tool | Description | Safety boundary |
|------|-------------|-----------------|
| `authorize_local_writes` | Ask Zotero to authorize Keeper; use `require_remembered=true` before file upload | Never returns or logs the key |
| `create_collection` | Create a top-level or nested collection | Exact parent key + explicit confirmation |
| `add_items_to_collection` | Add up to 50 existing items without removing other memberships | Validates every key before one versioned batch write |
| `update_item_fields` | Update approved scalar metadata fields | Requires the current local object version |
| `create_note` | Add a child note to an existing item | Validates the parent + explicit confirmation |
| `create_saved_search` | Create a Zotero saved search | Structured conditions + explicit confirmation |
| `attach_file_to_item` | Attach a local file to an existing item | Remembered authorization + validated loopback upload URL |
| `set_attachment_fulltext` | Write indexed text for an attachment | Response-bound library cursor + Server-ID + explicit confirmation |

These operations are unavailable on Zotero 7–9, which retain the existing
Connector import path. Keeper intentionally does not expose an unrestricted raw
PATCH or general-purpose destructive DELETE tool.

For each mutation, put `expected_server_id` (and the response-bound item object
version or full-text library cursor, when applicable) into the `confirm=false`
proposal before asking for approval. If later authorization reports a different
identity, discard that proposal, reread, preview again, and obtain new approval;
never add identity only after preview. Confirmed execution repeats the unchanged
proposal. A 412 is never retried automatically.

### ✏️ Save Tools (interactive_tools.py - 2 tools)

> 📊 **Auto RCR**: When PMID is provided, automatically fetches Relative Citation Ratio from iCite and stores in Zotero's extra field

| Tool | Description | Example |
|------|-------------|--------|
| `interactive_save` ⭐ | Interactive save + auto RCR | "Save this paper to Zotero" |
| `quick_save` | Quick save + auto RCR | "Quick save to AI Research" |

All save paths fail closed when no destination is confirmed. `interactive_save` asks for an exact collection key (or the explicit sentinel `ROOT`); choosing `ROOT` triggers a second confirmation. `skip_collection_prompt=True` aborts instead of silently saving to the library root. `quick_save`, `import_articles`, and `import_pdf` reject a missing collection unless the user has explicitly confirmed root storage and the caller passes `allow_library_root=true`.

### 🔍 Saved Search Tools (saved_search_tools.py - 3 tools)

| Tool | Description | Example |
|------|-------------|---------|
| `list_saved_searches` | List all saved searches | "What saved searches exist?" |
| `run_saved_search` | Execute a saved search | "Which papers have no PDF?" |
| `get_saved_search_details` | Get search conditions | "What's in 'Missing PDF' search?" |

### 🔍 Advanced Search & Ownership Check (search_tools.py - 2 public tools)

| Tool | Description | Example |
|------|-------------|---------|
| `advanced_search` ⭐ | Multi-condition search (itemType, tag, qmode) | "Find all journal articles tagged with AI" |
| `check_articles_owned` | Check if PMIDs exist in Zotero | "Do I have these PMIDs?" |

### 📥 Import Tools (2 tools)

> 🤝 **Collaboration-safe default**: PubMed search/discovery/export lives in pubmed-search-mcp. Zotero Keeper exposes one public import handoff: `import_articles`.

| Tool | Description | Example |
|------|-------------|--------|
| `import_articles` ⭐ | Single public import entry for JSON articles or RIS text | "Import these PubMed results to AI Research" |
| `import_pdf` 📎 | Import a local PDF through Zotero Connector endpoints, with metadata or Zotero recognition | "Import this PDF and attach it to its paper" |

#### Legacy PubMed bridge tools

`search_pubmed_exclude_owned`, `quick_import_pmids`, `import_ris_to_zotero`, `import_from_pmids`, and `batch_import_from_pubmed` are now hidden by default to avoid duplicating pubmed-search-mcp.

If you intentionally want the old standalone keeper behavior, set `ZOTERO_KEEPER_ENABLE_LEGACY_PUBMED_TOOLS=1` before starting the server.

### 📊 Analytics Tools (analytics_tools.py - 2 tools)

| Tool | Description | Example |
|------|-------------|--------|
| `get_library_stats` | Library statistics (year/author/journal) | "Show my library statistics" |
| `find_orphan_items` | Find unorganized items | "Which papers need organizing?" |

### 📎 Attachment & Fulltext Tools (attachment_tools.py - 2 tools)

> 🗂️ **PDF Access**: Zotero 10+ resolves attachment paths through the official Local API. `ZOTERO_DATA_DIR` remains an optional fallback for older Zotero versions or unavailable view URLs.

| Tool | Description | Example |
|------|-------------|--------|
| `get_item_attachments` | List PDFs/snapshots for an item | "What attachments does key:X42A7DEE have?" |
| `get_item_fulltext` | Get Zotero-indexed fulltext content | "Read the full text of key:X42A7DEE" |

#### Recommended PubMed → Zotero workflow

```python
# 1. Search with pubmed-search-mcp
results = unified_search("anesthesia AI", output_format="json")

# 2. Optional: filter against local Zotero
pmids = [
  article.get("identifiers", {}).get("pmid")
  for article in results["articles"]
]
owned = check_articles_owned(pmids=[pmid for pmid in pmids if pmid])

# 3. Import selected records into Zotero
import_articles(
  articles=results["articles"],
  collection_name="AI Research"
)
```

### 🤝 Collaboration-Safe Setup (Summary)

- pubmed-search-mcp runs search/discovery/export; zotero-keeper handles duplicate checks and the single `import_articles` handoff.
- Ensure pubmed-search-mcp is installed or the submodule is present; set `PUBMED_SEARCH_PATH` if you rely on a local checkout.
- Keep legacy PubMed bridge tools disabled unless you set `ZOTERO_KEEPER_ENABLE_LEGACY_PUBMED_TOOLS=1` intentionally.
- Full checklist: see `docs/COLLABORATION_WORKFLOW.md`.

#### advanced_search Examples

```python
# 🔍 依文獻類型搜尋
advanced_search(item_type="journalArticle")  # 只找期刊論文
advanced_search(item_type="book")  # 只找書籍
advanced_search(item_type="-attachment")  # 排除附件

# 🏷️ 依標籤搜尋
advanced_search(tag="AI")  # 具有 AI 標籤的文獻
advanced_search(tags=["AI", "Review"])  # 同時具有兩個標籤 (AND)
advanced_search(tag="AI || ML")  # 具有任一標籤 (OR)

# 📝 全文搜尋 (含 abstract)
advanced_search(q="XGBoost", qmode="everything")  # 搜尋摘要內容

# 🌟 組合條件
advanced_search(
    q="machine learning",
    item_type="journalArticle",
    tag="AI",
    sort="dateAdded",
    direction="desc"
)
```

---

## 📖 MCP Resources (Browsable Data)

The SDK v2 server advertises six concrete resources. Four additional parameterized URI templates resolve individual items, collections, collection contents, and saved searches.

### Concrete resources (6)

| Resource URI | Description |
|--------------|-------------|
| `zotero://collections` | All collections |
| `zotero://collections/tree` | Collection hierarchy |
| `zotero://items` | Recent items |
| `zotero://tags` | All tags |
| `zotero://searches` | Saved searches |
| `zotero://schema/item-types` | Available item types |

### Parameterized resource templates (4)

| Resource template | Description |
|-------------------|-------------|
| `zotero://collections/{key}` | Specific collection |
| `zotero://collections/{key}/items` | Items in collection |
| `zotero://items/{key}` | Item details |
| `zotero://searches/{key}` | Saved-search details |

---

## 🎯 Interactive Save (Recommended!)

The `interactive_save` tool uses **MCP Elicitation** to show collection options:

```
User: "Save this DOI:10.1234/example paper to Zotero"

[MCP Elicitation pops up]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📚 Saving: Deep Learning for Medical Imaging

⭐ Suggested:
   AI Research — key `A1B2C3D4` (match: 90%)
   Medical Imaging — key `M5N6P7Q8` (match: 75%)

📂 All Collections:
   Biology — key `B1O2L3O4` (12

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [u9401066](https://github.com/u9401066)
- **Source:** [u9401066/zotero-keeper](https://github.com/u9401066/zotero-keeper)
- **License:** Apache-2.0
- **Homepage:** https://marketplace.visualstudio.com/items?itemName=u9401066.vscode-zotero-mcp

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-u9401066-zotero-keeper
- Seller: https://agentstack.voostack.com/s/u9401066
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
