Let AI manage your references! A MCP Server connecting VS Code Copilot / Claude Desktop to your local Zotero library.
🌐 English | 繁體中文
Prerequisites: Zotero 7 or 8 must be running
💡 Requires uv - Click installs automatically via
uvx zotero-keeper
Zotero Keeper is a MCP Server 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!
No more manually searching, copying, pasting. Just tell your AI in natural language!
- 🔌 MCP Native: Built with FastMCP SDK for seamless AI integration
- 📖 MCP Resources: Browse Zotero data via URIs (
zotero://collections, etc.) - 💬 MCP Elicitation: Interactive collection selection with numbered options
- 🔒 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_namefor safer auto-validation - 📖 Read Operations: Search, list, and retrieve items from local Zotero
- ✏️ Write Operations: Add references via Connector API
- 🧠 Smart Features: Duplicate detection, validation, intelligent import
- 📁 Collection Support: Nested collections (folders) with hierarchy
- 🏗️ Clean Architecture: DDD with onion architecture
- 🔒 No Cloud Required: All operations are local
- ✅ Python 3.12+
- ✅ Zotero 7 or 8 (must be running)
- ✅ VS Code + GitHub Copilot, or Claude Desktop
- ✅ uv (recommended)
# 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_mcpCreate .vscode/mcp.json in your workspace:
{
"servers": {
"zotero-keeper": {
"type": "stdio",
"command": "uv",
"args": [
"run",
"--directory",
"/path/to/zotero-keeper/mcp-server",
"python", "-m", "zotero_mcp"
]
}
}
}Add to claude_desktop_config.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 is installed.
If you run the server directly, these are the main settings you may want to provide via .env or your MCP launcher configuration:
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_EMAILand optionalNCBI_API_KEYfor higher NCBI/PubMed rate limits. - Use
ZOTERO_KEEPER_ENABLE_LEGACY_PUBMED_TOOLS=1only if you intentionally want keeper's older PubMed bridge/import tools. - Use
PUBMED_SEARCH_PATHonly during local development when you want keeper to import a checked-outpubmed-search-mcptree instead of the installed package.
- README.zh-TW.md — Traditional Chinese overview
- mcp-server/README.md — focused server usage and tool reference
- vscode-extension/README.md — VS Code extension setup and UX
- docs/COLLABORATION_WORKFLOW.md — collaboration-safe flow between pubmed-search-mcp and keeper
- docs/tools-reference.md — parameter reference and examples for public tools
- docs/faq.md — installation, troubleshooting, and workflow FAQ
- docs/ZOTERO_LOCAL_API.md — Zotero API capability notes and limitations
- ARCHITECTURE.md — component and layering overview
- CONTRIBUTING.md — development workflow and contribution guide
💡 Tip: Most read operations can also be done via MCP Resources without calling 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?" |
⚠️ These can also be accessed viazotero://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) |
📊 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" |
| 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?" |
| 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?" |
🤝 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" |
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.
| 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?" |
🗂️ PDF Access: List attached PDFs and read Zotero-indexed fulltext. Requires
ZOTERO_DATA_DIRfor file paths.
| 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" |
# 1. Search with pubmed-search-mcp
results = unified_search("anesthesia AI", output_format="json")
# 2. Optional: filter against local Zotero
owned = check_articles_owned([article["pmid"] for article in results["articles"] if article.get("pmid")])
# 3. Import selected records into Zotero
import_articles(
articles=results["articles"],
collection_name="AI Research"
)- pubmed-search-mcp runs search/discovery/export; zotero-keeper handles duplicate checks and the single
import_articleshandoff. - Ensure pubmed-search-mcp is installed or the submodule is present; set
PUBMED_SEARCH_PATHif you rely on a local checkout. - Keep legacy PubMed bridge tools disabled unless you set
ZOTERO_KEEPER_ENABLE_LEGACY_PUBMED_TOOLS=1intentionally. - Full checklist: see
docs/COLLABORATION_WORKFLOW.md.
# 🔍 依文獻類型搜尋
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"
)No tool calls needed! AI can directly browse Zotero data:
| Resource URI | Description |
|---|---|
zotero://collections |
All collections |
zotero://collections/tree |
Collection hierarchy |
zotero://collections/{key} |
Specific collection |
zotero://collections/{key}/items |
Items in collection |
zotero://items |
Recent items |
zotero://items/{key} |
Item details |
zotero://tags |
All tags |
zotero://searches |
Saved searches |
zotero://searches/{key} |
Search details |
zotero://schema/item-types |
Available item types |
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:
1. AI Research (match: 90%) - Title matches
2. Medical Imaging (match: 75%) - Keyword matches
📂 All Collections:
3. Biology (12 items)
4. Chemistry (8 items)
5. To Read (23 items)
0. Save to My Library (no collection)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Enter the number of your choice: [User enters: 1]
AI: ✅ Saved to 'AI Research' collection!
When you provide a DOI or PMID, the tool automatically fetches complete metadata:
- DOI → CrossRef API → Full abstract, authors, journal, date
- PMID → PubMed API → Full abstract, MeSH terms, affiliations
No more missing abstracts! Just provide the identifier.
Zotero supports nested collections. Recommended strategies:
📁 My Library
├── 📁 Research Topics
│ ├── 📂 CRISPR Gene Editing
│ ├── 📂 Machine Learning in Medicine
│ └── 📂 Anesthesia Safety
├── 📁 Projects
│ ├── 📂 2024 Paper Draft
│ └── 📂 PhD Thesis
└── 📁 Reading List
├── 📂 To Read
└── 📂 Important
💡 Best Practice: Use collections for primary organization, tags for cross-cutting attributes (e.g., "to-read", "important", "review").
Works seamlessly with pubmed-search-mcp:
You: "Find new anesthesia AI papers from 2024 that I don't have"
AI executes:
1. pubmed-search-mcp: unified_search("anesthesia AI", min_year=2024, output_format="json")
→ Found 30 candidate articles
2. zotero-keeper: check_articles_owned([...pmids...])
→ Detects which PMIDs already exist locally
3. zotero-keeper: import_articles(articles=selected_articles, collection_name="AI Research")
→ Imports the selected records into Zotero
You: Done! 25 new papers in Zotero
cd mcp-server
uv sync --extra pubmedIf Zotero runs on another computer:
# Enable Local API (in Zotero → Tools → Developer → Run JavaScript)
Zotero.Prefs.set("httpServer.localAPI.enabled", true)
# Open firewall
netsh advfirewall firewall add rule name="Zotero" dir=in action=allow protocol=TCP localport=23119
# Setup port proxy (Zotero only listens on 127.0.0.1)
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=23119 connectaddress=127.0.0.1 connectport=23119{
"env": {
"ZOTERO_HOST": "192.168.1.100",
"ZOTERO_PORT": "23119"
}
}┌─────────────────────────────────────────────────┐
│ AI Agent (VS Code / Claude) │
└──────────────────────┬──────────────────────────┘
│ MCP Protocol
│ ├── Tools (22)
│ ├── Resources (10 URIs)
│ └── Elicitation (interactive input)
▼
┌─────────────────────────────────────────────────┐
│ Zotero Keeper MCP Server │
│ ┌───────────────────────────────────────────┐ │
│ │ MCP Layer │ │
│ │ ├── server.py (11 tools: 6 core + 5 collection) │
│ │ ├── resources.py (10 URIs, incl. collections) │
│ ├── interactive_tools.py (2 save tools) │ │
│ │ ├── saved_search_tools.py (3 tools) │ │
│ │ ├── search_tools.py (3 tools) │ │
│ │ ├── pubmed_tools.py (2 tools) │ │
│ │ ├── batch_tools.py (1 tool) │ │
│ │ └── smart_tools.py (helpers only) │ │
│ └───────────────────────────────────────────┘ │
└──────────────────────┬──────────────────────────┘
│ HTTP (port 23119)
▼
┌─────────────────────────────────────────────────┐
│ Zotero Desktop Client │
│ ├── Local API (/api/...) → Read │
│ └── Connector API (/connector/...) → Write │
└─────────────────────────────────────────────────┘
Zotero provides two local APIs, but neither supports full CRUD:
| API | Endpoint | Read | Create | Update | Delete |
|---|---|---|---|---|---|
| Local API | /api/... |
✅ | ❌ | ❌ | ❌ |
| Connector API | /connector/... |
❌ | ✅ | ❌ | ❌ |
Local API (port 23119):
- Designed for reading Zotero data (items, collections, tags)
- Per official source code: "Write access is not yet supported."
- DELETE/PATCH/PUT methods return
501 Not Implemented
Connector API (port 23119):
- Designed for browser extensions to save new items
saveItemsendpoint: Always creates NEW items, never updates- Even if you import the same PMID twice → creates duplicate items
- No
updateItemordeleteItemendpoints exist
| Operation | API Support | Technical Reason |
|---|---|---|
| ❌ Delete items | 501 Not Implemented | Local API is read-only |
| ❌ Update items | 501 Not Implemented | Local API is read-only |
| ❌ Move items to collection | Cannot modify | Connector API only creates, never updates |
| ❌ Add tags to existing items | Cannot modify | No update endpoint available |
| ❌ Create collections | 400 Bad Request | Connector API doesn't support it |
| ❌ Delete collections | 501 Not Implemented | Local API is read-only |
| ❌ Merge duplicates | No API | Must use Zotero GUI |
"Smart Management" Limitations:
❌ Cannot do:
- "Move these 10 papers to another collection"
- "Delete all duplicate references"
- "Help me organize my collections"
- "Archive old papers"
✅ Can do:
- "Add to specific collection when importing" (at creation time)
- "Search for matching references" (then handle manually)
- "List potential duplicates" (but manual deletion needed)
| Need | Alternative |
|---|---|
| Organize collections | Drag & drop in Zotero GUI |
| Delete duplicates | Zotero → Tools → "Merge duplicates" |
| Batch operations | Use Zotero Actions & Tags plugin |
| Auto-categorize | Use Zutilo plugin |
Zotero team is working on Local API write support:
- GitHub Issue #1320 - Request for write support
- Expected in future Zotero releases (8.x+)
We'll update zotero-keeper as soon as Zotero supports it!
| API | Execute Saved Search |
|---|---|
| Web API (api.zotero.org) | ❌ Can only read search metadata |
| Local API | ✅ Can execute and retrieve results! |
Recommended Saved Searches (create once, use forever):
| Name | Condition | AI Prompt |
|---|---|---|
| Missing PDF | Attachment File Type is not PDF | "Which papers have no PDF?" |
| Missing DOI | DOI is empty | "Which items lack DOI?" |
| Recent | Date Added in last 7 days | "What did I add this week?" |
| Unread | Tag is not "read" | "What haven't I read?" |
| Duplicates | Similar titles | "Potential duplicate items?" |
We support both developer-oriented and researcher-friendly entry points today, while keeping room for simpler packaging later.
| Path | Status | Best for |
|---|---|---|
| VS Code extension | ✅ Available now | Researchers who want guided setup inside VS Code |
Source checkout + uv sync |
✅ Available now | Contributors and local development |
Direct MCP registration via uvx zotero-keeper |
✅ Available now | Existing MCP-capable clients |
| Standalone executable | 🚧 Planned | Users who do not want to install Python/uv |
| Homebrew / Chocolatey | 🚧 Planned | OS-level package manager workflows |
💡 Want to help improve installation? See CONTRIBUTING.md.
- Make sure Zotero is running
- Test:
curl http://127.0.0.1:23119/connector/ping - Should return:
Zotero is running
- Use absolute paths
- Check Python environment
- Restart VS Code / Claude Desktop
cd mcp-server
uv sync --extra pubmed- CHANGELOG - Release notes
- ARCHITECTURE - Technical architecture
- CONTRIBUTING - How to contribute
- ROADMAP - Development roadmap
- docs/tools-reference.md - Full MCP tools parameter reference
- docs/faq.md - Frequently asked questions
- pubmed-search-mcp - PubMed search (Apache 2.0)
Contributions welcome! See CONTRIBUTING.md.
Apache 2.0 - See LICENSE
Made with ❤️ for researchers
Let AI manage your references, focus on your research!