{
  "markdown": "<!-- mcp-name: io.github.54yyyu/zotero-mcp -->\n\n# Zotero MCP: Chat with your Research Library—Local or Web—in Claude, ChatGPT, and more.\n\n<p align=\"center\">\n  <a href=\"https://www.zotero.org/\">\n    <img src=\"https://img.shields.io/badge/Zotero-CC2936?style=for-the-badge&logo=zotero&logoColor=white\" alt=\"Zotero\">\n  </a>\n  <a href=\"https://www.anthropic.com/claude\">\n    <img src=\"https://img.shields.io/badge/Claude-6849C3?style=for-the-badge&logo=anthropic&logoColor=white\" alt=\"Claude\">\n  </a>\n  <a href=\"https://chatgpt.com/\">\n    <img src=\"https://img.shields.io/badge/ChatGPT-74AA9C?style=for-the-badge&logo=openai&logoColor=white\" alt=\"ChatGPT\">\n  </a>\n  <a href=\"https://modelcontextprotocol.io/introduction\">\n    <img src=\"https://img.shields.io/badge/MCP-0175C2?style=for-the-badge&logoColor=white\" alt=\"MCP\">\n  </a>\n  <a href=\"https://pypi.org/project/zotero-mcp-server/\">\n    <img src=\"https://img.shields.io/pypi/v/zotero-mcp-server?style=for-the-badge&logo=pypi&logoColor=white\" alt=\"PyPI\">\n  </a>\n  <a href=\"https://discord.gg/BvgjbcBUqg\">\n    <img src=\"https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white\" alt=\"Discord\">\n  </a>\n</p>\n\n**Zotero MCP** seamlessly connects your [Zotero](https://www.zotero.org/) research library with [ChatGPT](https://openai.com), [Claude](https://www.anthropic.com/claude), and other AI assistants (e.g., [Cherry Studio](https://cherry-ai.com/), [Chorus](https://chorus.sh), [Cursor](https://www.cursor.com/)) via the [Model Context Protocol](https://modelcontextprotocol.io/introduction). Review papers, get summaries, analyze citations, extract PDF annotations, and more!\n\n---\n\n## ✨ Features\n\n### 🧠 AI-Powered Semantic Search\n- **Vector-based similarity search** over your entire research library (requires `[semantic]` extra)\n- **Multiple embedding models**: Default (free, local), OpenAI, Gemini, and Ollama\n- **Intelligent results** with similarity scores and contextual matching\n- **Auto-updating database** with configurable sync schedules\n\n### 🔍 Search Your Library\n- Find papers, articles, and books by title, author, or content\n- Perform complex searches with multiple criteria\n- Browse collections, tags, and recent additions\n- Semantic search for conceptual and topic-based discovery\n\n### 📚 Access Your Content\n- Retrieve detailed metadata for any item (markdown or BibTeX export)\n- Get full text content (when available)\n- Look up items by BetterBibTeX citation key\n\n### 📝 Work with Annotations\n- Extract and search PDF annotations with page numbers\n- Access Zotero's native annotations\n- Create and update notes and annotations\n- Extract PDF table of contents / outlines (requires `[pdf]` extra)\n\n### ✏️ Write Operations\n- **Add papers by DOI** with auto-fetched metadata and open-access PDF cascade (Unpaywall, arXiv, Semantic Scholar, PMC)\n- **Add papers by URL** (arXiv, DOI links, generic webpages) or from local files\n- Create and manage collections, update item metadata, batch-update tags\n- Find and merge duplicate items with dry-run preview\n- **Hybrid mode**: local reads + web API writes for local-mode users\n\n### 📊 Scite Citation Intelligence (optional `[scite]` extra)\n- **Citation tallies**: See how many papers support, contrast, or mention each item — the MCP version of the [Scite Zotero Plugin](https://github.com/scitedotai/scite-zotero-plugin)\n- **Retraction alerts**: Scan your library for retracted or corrected papers\n- No Scite account required — uses public API endpoints\n\n### 🌐 Flexible Access Methods\n- Local mode for offline access (no API key needed)\n- Web API for cloud library access\n- Hybrid mode: read from local Zotero, write via web API\n\n### ⌨️ Standalone CLI (`zotero-cli`)\n- Search, browse, and edit your library directly from the terminal — no AI assistant required\n- Ideal for scripting, automation, and quick lookups\n- `--json` on every command for pipelines and agents; short aliases (`s`, `g`, `ann`, `coll`) for interactive use\n\n### 🪶 Agent skill — the same library for ~1% of the context\n\nIf your agent has shell access (Claude Code, Cursor, Codex, Windsurf, Gemini CLI, Amp, OpenCode …), one command teaches it to drive `zotero-cli` directly:\n\n```bash\nzotero-mcp install-skill\n```\n\nIt detects the harnesses in your project and installs to each — no flags, no per-tool instructions to look up.\n\nWhy it matters: an MCP server sends **every tool's schema on every request**, before you type anything. The skill sits at 98 tokens until the agent decides it is relevant.\n\n| Route | In context | Paid |\n|---|---:|---|\n| MCP server, default profile (38 tools) | **13,448** | every request |\n| Agent skill, frontmatter only | **98** | always |\n| Agent skill, body loaded | 1,368 | when it fires |\n\n~137x cheaper before either is used, ~10x once the skill has fired. Re-measure any time with `python scripts/measure_context_cost.py`. This is the fixed context cost only — it does not measure task success or round trips, and a cheaper surface that gets the answer wrong is not cheaper. [Details below](#-agent-skill-one-command-for-any-harness).\n\nBoth routes work, and they share one config. Use the MCP server when your client speaks MCP but has no shell (Claude Desktop, ChatGPT); use the skill when it has a shell.\n\n## 🚀 Quick Install\n\n> **New to the command line?** Try the community-built [Zotero MCP Setup](https://github.com/ehawkin/zotero-mcp-setup) — includes a macOS GUI installer (DMG), one-click install scripts for Mac/Windows, and a step-by-step guide. No Terminal experience needed.\n\n### Default Installation (core tools only)\n\nThe base install is lightweight — it includes search, metadata retrieval, annotations, and write operations. No ML/AI dependencies are pulled in.\n\n#### Installing via uv (recommended)\n\n```bash\nuv tool install zotero-mcp-server\nzotero-mcp setup  # Auto-configure (Claude Desktop supported)\n```\n\n#### Installing via pip\n\n```bash\npip install zotero-mcp-server\nzotero-mcp setup  # Auto-configure (Claude Desktop supported)\n```\n\n#### Installing via pipx\n\n```bash\npipx install zotero-mcp-server\nzotero-mcp setup  # Auto-configure (Claude Desktop supported)\n```\n\n### Optional Extras\n\nHeavy ML/PDF dependencies are separated into optional extras so the base install stays fast and small:\n\n| Extra | What it adds | Install command |\n|-------|-------------|-----------------|\n| `semantic` | Semantic search via ChromaDB, sentence-transformers, OpenAI/Gemini embeddings | `pip install \"zotero-mcp-server[semantic]\"` |\n| `pdf` | PDF outline extraction (PyMuPDF) and EPUB annotation support | `pip install \"zotero-mcp-server[pdf]\"` |\n| `scite` | [Scite](https://scite.ai) citation intelligence — tallies and retraction alerts (no account needed) | `pip install \"zotero-mcp-server[scite]\"` |\n| `all` | Everything above | `pip install \"zotero-mcp-server[all]\"` |\n\nFor example, with uv:\n```bash\nuv tool install \"zotero-mcp-server[all]\"    # Full install with all features\nuv tool install \"zotero-mcp-server[semantic]\" # Just semantic search\n```\n\nIf you only need basic library access (search, read, annotate, write), the default install with no extras is all you need.\n\n#### Updating Your Installation\n\nKeep zotero-mcp up to date with the smart update command:\n\n```bash\n# Check for updates\nzotero-mcp update --check-only\n\n# Update to latest version (preserves all configurations)\nzotero-mcp update\n```\n\n## 🧠 Semantic Search\n\nZotero MCP now includes powerful AI-powered semantic search capabilities that let you find research based on concepts and meaning, not just keywords.\n\n### Setup Semantic Search\n\nDuring setup or separately, configure semantic search:\n\n```bash\n# Configure during initial setup (recommended)\nzotero-mcp setup\n\n# Or configure semantic search separately\nzotero-mcp setup --semantic-config-only\n```\n\n**Available Embedding Models:**\n- **Default (all-MiniLM-L6-v2)**: Free, runs locally, good for most use cases\n- **OpenAI**: Better quality, requires API key (`text-embedding-3-small` or `text-embedding-3-large`)\n- **Gemini**: Better quality, requires API key (`gemini-embedding-001`)\n- **Ollama**: Runs locally via Ollama API (requires model name, e.g., 'qwen3-embedding')\n\n**Using Ollama embeddings:**\n\nInstall and start Ollama, then pull an embedding model before running `zotero-mcp update-db`:\n\n```bash\nollama serve\n\n# Small model: fast and lightweight\nollama pull nomic-embed-text\n\n# Medium model: better multilingual retrieval quality\nollama pull bge-m3\n```\n\nWhen prompted by `zotero-mcp setup --semantic-config-only`, choose **Ollama** and use either `nomic-embed-text` or `bge-m3` as the model name. If you change embedding models later, rebuild the index:\n\n```bash\nzotero-mcp update-db --force-rebuild\n```\n\nTwo `semantic_search.embedding_config` keys tune the Ollama path for slower\nhardware or very large libraries:\n\n```jsonc\n\"embedding_config\": {\n  \"model_name\": \"bge-m3\",\n  \"timeout\": 600,            // HTTP timeout per /api/embed call (default 120s)\n  \"request_batch_size\": 64   // documents per request (default 64)\n}\n```\n\nRaise `timeout` if indexing reports `Read timed out`; lower\n`request_batch_size` to make each request cover less GPU work, which usually\nfixes timeouts more reliably than raising the timeout alone.\n\nWhen you choose OpenAI, setup also asks whether database updates should use\nOpenAI Batch API. Batch updates are cheaper for large libraries, but they are\nasynchronous: submit the batch, wait for completion, then import the embeddings.\n\n**Update Frequency Options:**\n- **Manual**: Update only when you run `zotero-mcp update-db`\n- **Auto on startup**: Update database every time the server starts\n- **Daily**: Update once per day automatically\n- **Every N days**: Set custom interval\n\n### Using Semantic Search\n\nAfter setup, initialize your search database:\n\n```bash\n# Build the semantic search database (fast, metadata-only)\nzotero-mcp update-db\n\n# Submit OpenAI embeddings through Batch API for this update\nzotero-mcp update-db --openai-batch\n\n# Check and import completed OpenAI Batch API embeddings\nzotero-mcp openai-batch-status\nzotero-mcp openai-batch-import\n\n# Force realtime OpenAI embeddings even if Batch API is enabled in config\nzotero-mcp update-db --no-openai-batch\n\n# Build with full-text extraction (slower, more comprehensive)\nzotero-mcp update-db --fulltext\n\n# Use your custom zotero.sqlite path\nzotero-mcp update-db --fulltext --db-path \"/Your_custom_path/zotero.sqlite\"\n\n# If you have embedding conflicts or changed models, force a rebuild\nzotero-mcp update-db --force-rebuild\n\n# Check database status\nzotero-mcp db-status\n```\n\n**Example Semantic Queries in your AI assistant:**\n- *\"Find research similar to machine learning concepts in neuroscience\"*\n- *\"Papers that discuss climate change impacts on agriculture\"*\n- *\"Research related to quantum computing applications\"*\n- *\"Studies about social media influence on mental health\"*\n- *\"Find papers conceptually similar to this abstract: [paste abstract]\"*\n\nThe semantic search provides similarity scores and finds papers based on conceptual understanding, not just keyword matching.\n\n### Text Extraction Settings\n\nPDFs are parsed with [pdf-inspector](https://github.com/firecrawl/pdf-inspector), which produces Markdown with the document's heading structure intact. These keys live under `semantic_search.extraction` in `~/.config/zotero-mcp/config.json`:\n\n```json\n{\n  \"semantic_search\": {\n    \"extraction\": {\n      \"pdf_max_pages\": 50,\n      \"fulltext_display_max_pages\": 10,\n      \"attachment_priority\": [\"markdown\", \"pdf\", \"html\", \"other\"]\n    }\n  }\n}\n```\n\n| Key | Default | What it does |\n|---|---|---|\n| `pdf_max_pages` | `50` | Pages extracted per PDF when indexing. Raising it does not widen what search sees on its own — that is bounded by the embedding model's token limit or `chunking.max_chunks_per_item`. |\n| `fulltext_display_max_pages` | `10` | Pages returned by `zotero_get_item_fulltext`. Separate from the above because reading a paper is bounded by your assistant's context, not by recall. |\n| `attachment_priority` | `[\"pdf\", \"html\", \"other\"]` | Order in which attachment kinds are tried when an item has several readable files. |\n\n**`attachment_priority`** exists for the case where you have converted a paper to clean Markdown yourself and attached it next to the original PDF. By default the PDF still wins; listing `\"markdown\"` first makes your converted copy the one that gets read and indexed. Valid entries are `pdf`, `html`, `markdown`, `text` and `other`. `other` is a catch-all matching every kind not named elsewhere in the list, so the default sweeps Markdown and plain text into one bucket where the larger file wins. Omitting `other` means anything unlisted is never chosen.\n\nChanging this setting marks affected items for re-extraction, so a following `zotero-mcp update-db` refreshes text that came from a now-deprioritized attachment rather than leaving stale embeddings behind.\n\nTo read one specific attachment regardless of priority, pass that attachment's own key to `zotero_get_item_fulltext` (find it with `zotero_get_item_children`) — an attachment key bypasses the priority order and reads exactly that file.\n\n## 🖥️ Setup & Usage\n\nFull documentation is available at [Zotero MCP docs](https://stevenyuyy.com/zotero-mcp/).\n\n**Requirements**\n- Python 3.10+\n- Zotero 7+ (for local API with full-text access)\n- An MCP-compatible client (e.g., Claude Desktop, ChatGPT Developer Mode, Cherry Studio, Chorus)\n\n**For ChatGPT setup: see the [Getting Started guide](./docs/getting-started.md).**\n\n### Configure Zotero\n\nThe Zotero local API must be enabled for the MCP server to work.\n\nIn Zotero 9, the local API toggle is under Settings → Advanced → 'Allow other applications on this computer to communicate with Zotero'.\n\nHere is a screenshot:\n\n![Zotero local API](./docs/zotero-local-api.png)\n\n### For Claude Desktop / Claude Code (MCP client)\n\n#### Configuration\nAfter installation, either:\n\n1. **Auto-configure** (recommended):\n   ```bash\n   zotero-mcp setup\n   ```\n\n2. **Manual configuration**:\n   For Claude Desktop, add this to `claude_desktop_config.json`.\n   For Claude Code, add this to `~/.claude.json`:\n   ```json\n   {\n     \"mcpServers\": {\n       \"zotero\": {\n         \"command\": \"zotero-mcp\",\n         \"env\": {\n           \"ZOTERO_LOCAL\": \"true\",\n           \"ZOTERO_API_KEY\": \"YOUR_API_KEY\",\n           \"ZOTERO_LIBRARY_ID\": \"YOUR_LIBRARY_ID\"\n         }\n       }\n     }\n   }\n   ```\n\n   For **local read-only use**, `ZOTERO_LOCAL: \"true\"` is all you need — drop the `ZOTERO_API_KEY` and `ZOTERO_LIBRARY_ID` lines entirely.\n\n   The local API is fast but read-only, so the MCP server uses the Zotero web API for write operations.\n\n   To enable **write mode**:\n   - Keep `ZOTERO_LOCAL: \"true\"` — with API credentials set, the server runs in hybrid mode (fast local reads, web API writes)\n   - Click [here](https://www.zotero.org/settings/security#applications) to generate a Zotero API key and replace `YOUR_API_KEY` with it\n   - `ZOTERO_LIBRARY_ID` is your numeric **userID**, shown on that same page (for a group library, use the group's ID and also set `ZOTERO_LIBRARY_TYPE: \"group\"`).\n\n   > **Important Note**: Environmental variables set in the shell you run `claude` in will override these values.\n\n   > **Tip:** If Claude Desktop reports it can't find the `zotero-mcp` command, use the\n   > absolute path instead (run `zotero-mcp setup-info` or `which zotero-mcp` to find it) —\n   > GUI apps don't always inherit your shell `PATH`.\n\n#### Usage\n\n1. Start Zotero desktop (make sure local API is enabled in preferences)\n2. Launch Claude Desktop / Claude Code\n3. For Claude Desktop, access the Zotero-MCP tool through Claude Desktop's tools interface.\nFor Claude Code, run the `/mcp` command, and make sure the Zotero MCP server is connected.\n\nExample prompts:\n- \"Search my library for papers on machine learning\"\n- \"Find recent articles I've added about climate change\"\n- \"Summarize the key findings from my paper on quantum computing\"\n- \"Extract all PDF annotations from my paper on neural networks\"\n- \"Search my notes and annotations for mentions of 'reinforcement learning'\"\n- \"Show me papers tagged '#Arm' excluding those with '#Crypt' in my library\"\n- \"Search for papers on operating system with tag '#Arm'\"\n- \"Export the BibTeX citation for papers on machine learning\"\n- **\"Find papers conceptually similar to deep learning in computer vision\"** *(semantic search)*\n- **\"Research that relates to the intersection of AI and healthcare\"** *(semantic search)*\n- **\"Papers that discuss topics similar to this abstract: [paste text]\"** *(semantic search)*\n\n### For Autohand Code\n\nAfter installing Zotero MCP, add a local read-only server with:\n\n```bash\nautohand mcp add zotero env ZOTERO_LOCAL=true zotero-mcp\n```\n\nAdd `--scope project` after `add` to keep the server configuration in the current project. For hybrid or web API access, add the credentials described above to the `env` command. See [Autohand Code](https://github.com/autohandai/code-cli/) for current installation and CLI details.\n\n### For Cherry Studio\n\n#### Configuration\nGo to Settings -> MCP Servers -> Edit MCP Configuration, and add the following:\n\n```json\n{\n  \"mcpServers\": {\n    \"zotero\": {\n      \"name\": \"zotero\",\n      \"type\": \"stdio\",\n      \"isActive\": true,\n      \"command\": \"zotero-mcp\",\n      \"args\": [],\n      \"env\": {\n        \"ZOTERO_LOCAL\": \"true\"\n      }\n    }\n  }\n}\n```\nThen click \"Save\".\n\nCherry Studio also provides a visual configuration method for general settings and tools selection.\n\n## 🔧 Advanced Configuration\n\n### Using Web API Instead of Local API\n\nFor accessing your Zotero library via the web API (useful for remote setups):\n\n```bash\nzotero-mcp setup --no-local --api-key YOUR_API_KEY --library-id YOUR_LIBRARY_ID\n```\n\n### Environment Variables\n\n**Zotero Connection:**\n- `ZOTERO_LOCAL=true`: Use the local Zotero API (default: false)\n- `ZOTERO_API_KEY`: Your Zotero API key (for web API)\n- `ZOTERO_LIBRARY_ID`: Your Zotero library ID (for web API)\n- `ZOTERO_LIBRARY_TYPE`: The type of library (user or group, default: user)\n- `ZOTERO_WEBDAV_URL`: Optional WebDAV folder URL for direct attachment downloads in remote mode\n- `ZOTERO_WEBDAV_USERNAME`: Optional WebDAV username\n- `ZOTERO_WEBDAV_PASSWORD`: Optional WebDAV password\n\n**Semantic Search:**\n- `ZOTERO_EMBEDDING_MODEL`: Embedding model to use (default, openai, gemini, ollama)\n- `OPENAI_API_KEY`: Your OpenAI API key (for OpenAI embeddings)\n- `OPENAI_EMBEDDING_MODEL`: OpenAI model name (text-embedding-3-small, text-embedding-3-large)\n- `OPENAI_BASE_URL`: Custom OpenAI endpoint URL (optional, for use with compatible APIs)\n- OpenAI Batch API indexing is configured by `zotero-mcp setup` and can be overridden with\n  `zotero-mcp update-db --openai-batch` or `--no-openai-batch`\n- `GEMINI_API_KEY`: Your Gemini API key (for Gemini embeddings)\n- `GEMINI_EMBEDDING_MODEL`: Gemini model name (gemini-embedding-001)\n- `GEMINI_BASE_URL`: Custom Gemini endpoint URL (optional, for use with compatible APIs)\n- `OLLAMA_EMBEDDING_MODEL`: Ollama embedding model name (qwen3-embedding by default)\n- `OLLAMA_BASE_URL`: Ollama server URL (default: http://localhost:11434)\n- `ZOTERO_DB_PATH`: Custom `zotero.sqlite` path (optional). When unset, the\n  database is located automatically: a data directory configured in Zotero's\n  preferences (read from the profile's `prefs.js`) is tried first, then the\n  default `~/Zotero` location.\n\n**Search backend:**\n- `ZOTERO_SEARCH_BACKEND=sqlite`: Route `zotero_search_items` and\n  `zotero_advanced_search` through direct SQL against `zotero.sqlite` instead of\n  fetching over the API and filtering in Python (default: `api`). Requires\n  `ZOTERO_LOCAL=true`, since it reads the database off disk. Substantially\n  faster on large libraries — an `advanced_search` that pages the whole library\n  over the API drops from minutes to well under a second. Any query the backend\n  doesn't cover falls back to the API path automatically, so the results are\n  either the same or better, never worse.\n\n**Global search across libraries:**\n\nWith the SQLite backend enabled, `zotero_search_items`, `zotero_advanced_search`\nand `zotero_semantic_search` accept `search_all_libraries=True` (`--all-libraries`\non the CLI). One query then covers your personal library and every group library\nat once, and each result is labelled with the library it came from:\n\n```\n**Library:** AI in entrepreneurship (groupID=6015547)\n```\n\nThis is deliberately gated on `ZOTERO_SEARCH_BACKEND=sqlite`. The Zotero API can\nonly search one library per request, so without direct SQL the best anyone could\ndo is replay a single-library search against each library in turn — a different\nand far slower operation. Rather than emulate global search badly, the tools\nrefuse and say so.\n\nTwo limits follow from how Zotero stores things. **Collections are per-library**\n(`collections.libraryID` is NOT NULL), so `collection_key` and `collection`\nconditions cannot be combined with a global search. **Tags are not** — Zotero\nkeeps one database-wide `tags` table shared by every library — so tag filters and\n`tag` conditions work globally and are the recommended way to slice a global\nsearch.\n\nDuplicates across libraries are returned as-is: the same paper filed in two\nlibraries is two items, and collapsing them would hide where each copy lives.\n\n**Tool surface:**\n- `ZOTERO_MCP_TOOLSETS`: Which optional tool groups to expose. Every tool the\n  server registers is sent to the model on *every* request, so the tool list is\n  a fixed cost on your context window. Groups that need an external service,\n  serve maintenance rather than research, or apply only to some users are off\n  by default. See [Tool Groups](#-tool-groups) below.\n\n**Item schema:**\n- `ZOTERO_MCP_SCHEMA_REFRESH=0`: Disable the weekly background refresh of\n  Zotero's item-type schema from `api.zotero.org`. The schema is what routes a\n  generic `title=` update to the field a type actually stores it under (a\n  statute's `nameOfAct`, a case's `caseName`). A copy ships with the package, so\n  disabling the refresh only means new item types added by Zotero after this\n  release won't be picked up until you upgrade. `zotero-mcp schema-refresh`\n  still refreshes on demand.\n- `ZOTERO_MCP_SCHEMA_CACHE`: Custom path for the refreshed schema cache\n  (default: `~/.cache/zotero-mcp/schema.json`).\n\n### Command-Line Options\n\n```bash\n# Run the server directly\nzotero-mcp serve\n\n# Specify transport method\nzotero-mcp serve --transport stdio|streamable-http|sse\n\n# Setup and configuration\nzotero-mcp setup --help                    # Get help on setup options\nzotero-mcp setup --semantic-config-only    # Configure only semantic search\nzotero-mcp setup-info                      # Show installation path and config info for MCP clients\n\n# Updates and maintenance\nzotero-mcp update                          # Update to latest version\nzotero-mcp update --check-only             # Check for updates without installing\nzotero-mcp update --force                  # Force update even if up to date\n\n# Semantic search database management\nzotero-mcp update-db                       # Update semantic search database (fast, metadata-only)\nzotero-mcp update-db --openai-batch        # Submit OpenAI embeddings through Batch API\nzotero-mcp update-db --no-openai-batch     # Force realtime OpenAI embeddings for this run\nzotero-mcp openai-batch-status             # Check latest OpenAI embedding batch status\nzotero-mcp openai-batch-import             # Import completed OpenAI batch embeddings\nzotero-mcp update-db --fulltext             # Update with full-text extraction (comprehensive but slower)\nzotero-mcp update-db --force-rebuild       # Force complete database rebuild\nzotero-mcp update-db --fulltext --force-rebuild  # Rebuild with full-text extraction\nzotero-mcp update-db --fulltext --db-path \"your_path_to/zotero.sqlite\" # Customize your zotero database path\nzotero-mcp db-status                       # Show database status and info\n\n# General\nzotero-mcp version                         # Show current version\n```\n\n## 🐳 Docker Images (GHCR)\n\nThis repository publishes multi-arch container images to GitHub Container Registry:\n\n- `ghcr.io/<owner>/zotero-mcp:<tag>-core` - lightweight install (no optional extras)\n- `ghcr.io/<owner>/zotero-mcp:<tag>-all` - full install with `[semantic,pdf,scite]`\n- Unsuffixed tags (for example `:latest`, `:vX.Y.Z`) point to the `all` flavor\n\nDetailed publishing and runtime notes are in `docs/docker-images.md`.\n\nTag strategy:\n\n- Release tags: `vX.Y.Z`, `vX.Y`, `vX` (plus `-core` and `-all` variants)\n- Main branch: `latest` (plus `latest-core` and `latest-all`)\n- Immutable SHA tags: `sha-<shortsha>-core`, `sha-<shortsha>-all` (and unsuffixed SHA for `all`)\n\n### Runtime modes in the container\n\nThe image supports both MCP server and standalone CLI modes.\n\n- **Server mode (default)**: runs `zotero-mcp serve --transport stdio`\n- **CLI mode**: set `ZOTERO_APP=cli` and pass normal `zotero-cli` arguments\n\n### Docker env vars and persistence\n\n- Container runtime vars: `ZOTERO_APP` (`server` or `cli`) and `ZOTERO_TRANSPORT` (default: `stdio`)\n- All standard Zotero MCP vars are supported in containers (`ZOTERO_LOCAL`, `ZOTERO_API_KEY`, `ZOTERO_LIBRARY_ID`, embedding provider keys, etc.)\n- ChromaDB persistence path in the container is `/home/app/.config/zotero-mcp/chroma_db/`\n- Persist config + ChromaDB by mounting `/home/app/.config/zotero-mcp`\n\nExamples:\n\n```bash\n# Default MCP server mode (stdio)\ndocker run --rm ghcr.io/<owner>/zotero-mcp:latest\n\n# MCP server mode with explicit transport\ndocker run --rm ghcr.io/<owner>/zotero-mcp:latest serve --transport streamable-http --host 0.0.0.0 --port 8000\n\n# Standalone CLI mode\ndocker run --rm -e ZOTERO_APP=cli ghcr.io/<owner>/zotero-mcp:latest search \"machine learning\"\n\n# Persist config + ChromaDB across runs\ndocker run --rm -v zotero-mcp-data:/home/app/.config/zotero-mcp --env-file .env ghcr.io/<owner>/zotero-mcp:latest\n```\n\n## ⌨️ CLI Mode (`zotero-cli`)\n\n`zotero-cli` is a standalone terminal interface to your Zotero library. It uses the same tools as the MCP server but without needing an AI assistant — useful for quick lookups, shell scripts, and automation.\n\nUse `zotero-mcp` when your AI client supports MCP (Claude Desktop, ChatGPT). Use `zotero-cli` for shell scripts, cron jobs, or agentic pipelines with shell access (e.g. Claude Code) — CLI commands cost far fewer tokens than MCP tool schemas and compose naturally with Unix pipes.\n\nBoth share the same configuration set up by `zotero-mcp setup`.\n\n### How much context each route costs\n\nThe MCP server sends every enabled tool's name, description and JSON parameter schema to the model on **every request**, before you type anything. The CLI route puts only a skill description in context until the model decides it is relevant. Measured on this repo with `python scripts/measure_context_cost.py`:\n\n| Route | Tokens in context | When it is paid |\n|---|---:|---|\n| MCP, default profile (38 tools) | 13,448 | every request |\n| MCP, `ZOTERO_MCP_TOOLSETS=none` (32 tools) | 11,761 | every request |\n| MCP, `ZOTERO_MCP_TOOLSETS=all` (50 tools) | 17,414 | every request |\n| CLI skill, frontmatter only | 98 | always |\n| CLI skill, body loaded | 1,368 | once the skill fires |\n| CLI skill + full command reference | 4,389 | worst case |\n\nThat is the *fixed* cost only. It does not measure task success, output size, or how many round trips each route takes to finish a job — a cheaper surface that gets the answer wrong is not cheaper. Numbers are `cl100k_base` tokens and are re-measured, not estimated; `tests/test_context_cost_claim.py` fails if the relationship stops holding.\n\n### 🪶 Agent skill: one command for any harness\n\n```bash\nzotero-mcp install-skill\n```\n\nRun it in your project. It detects which agent harnesses are set up there and installs to each one, in that harness's own format:\n\n| Harness | Detected by | Installs |\n|---|---|---|\n| Claude Code (project) | `.claude/` | `.claude/skills/zotero-cli/` |\n| Claude Code (user) | `~/.claude/` | `~/.claude/skills/zotero-cli/` |\n| Cursor | `.cursor/` | `.cursor/rules/zotero-cli.mdc` |\n| Windsurf | `.windsurf/` | `.windsurf/rules/zotero-cli.md` |\n| Codex, Amp, OpenCode, Jules … | `AGENTS.md` | a pointer block in `AGENTS.md` |\n| Gemini CLI | `GEMINI.md` or `.gemini/` | a pointer block in `GEMINI.md` |\n\n```bash\nzotero-mcp install-skill --list-targets      # what is detected here\nzotero-mcp install-skill --target cursor     # install one explicitly\nzotero-mcp install-skill --force             # overwrite an existing copy\n```\n\n**It will not overwrite your work.** A destination that exists and differs is reported, not replaced, unless you pass `--force`. For shared instruction files it is stricter: only the text between the `zotero-cli` markers is ever managed, so the rest of your `AGENTS.md` is untouchable by construction — re-running updates that block in place rather than appending a second one.\n\n**It keeps the context advantage.** Shared instruction files get a short pointer block, not the whole skill; the body lands beside it and the agent opens it only when it decides Zotero is relevant. Pasting 1,400 tokens into every agent's always-loaded context would spend exactly the advantage this exists for.\n\nThe skill teaches the find-keys-then-act loop, `--json`, how to pick among the six search modes, paging, reading a PDF by outline-then-page-range rather than whole, and when an empty result means \"the index is not built\" rather than \"you have no papers on that\".\n\n### Machine-readable output (`--json`)\n\nEvery command accepts `--json`, before or after the command name. Output is one object per invocation:\n\n```bash\nzotero-cli --json search \"attention\" --limit 5 --detail keys_only\n# {\"ok\": true, \"command\": \"search\", \"schema\": 1, \"data\": {\"count\": 5, \"items\": [...]}}\n```\n\nSuccess carries `data`; failure carries `error.message` and a stable `error.code`, also on stdout, so one stream carries both outcomes. Read commands (search, get, annotations list, notes list, config) return real structure; commands whose answer is a status line return `{\"text\": ...}`. Run `zotero-cli --json-schema` for the full contract.\n\n```bash\n# Item keys are the currency of every command — pipe them onward\nzotero-cli --json search \"diffusion models\" --limit 5 --detail keys_only \\\n  | jq -r '.data.items[].key' \\\n  | while read -r key; do zotero-cli --json get metadata \"$key\"; done\n```\n\n### Quick reference\n\n```bash\n# Search\nzotero-cli search \"machine learning\"           # keyword search\nzotero-cli s \"neural networks\" --limit 5       # short alias, limit results\nzotero-cli search --mode semantic \"attention mechanisms\"\nzotero-cli search --mode tag \"important,reviewed\"\n\n# Get item details\nzotero-cli get metadata ABC123                 # markdown metadata\nzotero-cli g metadata ABC123 --format bibtex  # BibTeX export\nzotero-cli get fulltext ABC123                 # full text\nzotero-cli get children ABC123                 # attachments and notes\n\n# Edit item metadata\nzotero-cli edit ABC123 --title \"New Title\"\nzotero-cli edit ABC123 --add-tags \"reviewed,important\" --date \"2024\"\n\n# Notes and annotations\nzotero-cli notes list ABC123\nzotero-cli notes create --item-key ABC123 --text \"My note\" --tags \"idea\"\nzotero-cli notes create --item-key ABC123 --text -   # read from stdin\nzotero-cli ann list --item-key ABC123         # annotations (short alias)\nzotero-cli ann list --item-key ABC123 --format json  # structured export\nzotero-cli ann search \"highlight text\"\n\n# Add items\nzotero-cli add doi 10.1038/s41586-021-03819-2\nzotero-cli add url https://arxiv.org/abs/2301.00001\nzotero-cli add file --filepath /path/to/paper.pdf --title \"Override Title\"\nzotero-cli add isbn 9780262046305\nzotero-cli add bibtex --file refs.bib                # or --bibtex '@article{...}'\nzotero-cli add bibtex --bibtex - < refs.bib          # stdin via -\nzotero-cli add csl-json --file refs.json             # or --json '...' / --json -\n\n# --collections accepts keys, names, or parent/child paths — resolved and\n# validated before the item is created (a typo fails the add, with suggestions,\n# instead of leaving an unfiled item)\nzotero-cli add doi 10.1038/s41586-021-03819-2 --collections \"Reading List\"\nzotero-cli collections manage --item-keys ABC123 --add-to \"_project/topic\"\n\n# Adds are idempotent by default (--if-exists file): if the item is already in\n# the library it is reused — filed into any missing collections, given any\n# missing tags — instead of duplicated. Re-running the same command is a no-op.\nzotero-cli add doi 10.1038/s41586-021-03819-2 -c \"Reading List\"   # run it twice: converges\nzotero-cli add doi 10.1038/s41586-021-03819-2 --if-exists skip       # never touch existing\nzotero-cli add doi 10.1038/s41586-021-03819-2 --if-exists duplicate  # old behavior\nzotero-cli add doi 10.1038/s41586-021-03819-2 -c \"New Topic\" --create-collections\n# -c/--collection is repeatable and never comma-split (names with commas work);\n# --collections remains the comma-separated form\n\n# Collections and tags\nzotero-cli coll list                          # list collections (short alias)\nzotero-cli coll search \"PhD Research\"\nzotero-cli tags list\n\n# Semantic search database\nzotero-cli db update\nzotero-cli db update --fulltext --force-rebuild\nzotero-cli db status\n\n# Library and duplicates\nzotero-cli library info\nzotero-cli duplicates find\n\n# Reading PDFs — find the section first, then read only those pages\nzotero-cli outline ABC123\nzotero-cli read ABC123 --start-page 42 --end-page 55\nzotero-cli path ABC123                        # where the file lives on disk\n\n# Attachments, deletion, bibliographies\nzotero-cli attach ABC123 --file /path/to/paper.pdf\nzotero-cli delete item ABC123\nzotero-cli export --item-keys ABC123,DEF456 --style apa\nzotero-cli export --collection COLL01 --format bibtex\n\n# Discovery and synthesis\nzotero-cli related 10.1038/s41586-021-03819-2 --direction citations\nzotero-cli coverage --collection COLL01\nzotero-cli synthesize --tag \"to-read\" --format json\n\n# Bulk edits across many items\nzotero-cli batch --item-keys ABC123,DEF456 --add-tags screened\nzotero-cli batch --query \"machine learning\" --add-tags survey --limit 100\n```\n\nPaging: listings cap at `--limit` and the response names the next offset.\n\n```bash\nzotero-cli --json get collection-items QS7TQPPA --limit 100 --offset 100\n```\n\n### Verbose mode\n\nAdd `-v` anywhere to see progress messages (e.g., which API calls are made):\n\n```bash\nzotero-cli -v search \"CRISPR\"\n```\n\n## 📑 PDF Annotation Extraction\n\nZotero MCP includes advanced PDF annotation extraction capabilities:\n\n- **Direct PDF Processing**: Extract annotations directly from PDF files, even if they're not yet indexed by Zotero\n- **Enhanced Search**: Search through PDF annotations and comments\n- **Image Annotation Support**: Extract image annotations from PDFs\n- **Seamless Integration**: Works alongside Zotero's native annotation system\n\nFor optimal annotation extraction, it is **highly recommended** to install the [Better BibTeX plugin](https://retorque.re/zotero-better-bibtex/installation/) for Zotero. The annotation-related functions have been primarily tested with this plugin and provide enhanced functionality when it's available.\n\n\nThe first time you use PDF annotation features, the necessary tools will be automatically downloaded.\n\n## 🔗 Managing Related Items\n\nZotero MCP supports managing relationships between items in your library. This is useful for linking related papers, tracking versions, or connecting preprints to their published versions.\n\n> These tools are in the opt-in `relations` group. Enable them with\n> `ZOTERO_MCP_TOOLSETS=relations` — see [Tool Groups](#-tool-groups).\n\n### View Related Items\n```\nzotero_get_item_related(item_key=\"ABCD1234\")\n```\n\n### Add a Relation\nCreate a bidirectional link between two items:\n```\nzotero_add_item_relation(\n    item_key=\"ABCD1234\",\n    related_item_key=\"EFGH5678\",\n    relation_type=\"dc:relation\"  # Optional, defaults to \"dc:relation\"\n)\n```\n\n### Remove a Relation\n```\nzotero_remove_item_relation(\n    item_key=\"ABCD1234\",\n    related_item_key=\"EFGH5678\",\n    remove_bidirectional=True  # Also remove the reverse relation (default: true)\n)\n```\n\n**Relation Types:**\n- `dc:relation` — General related items (default)\n- `owl:sameAs` — Items that are the same work (e.g., preprint and published version)\n\n## 🧰 Tool Groups\n\nEvery tool this server registers is sent to the model on **every** request, so\nthe tool list is a fixed tax on your context window before you type anything.\nTo keep that cost proportionate, optional capabilities are grouped into\n*toolsets* that you turn on when you need them.\n\nSet `ZOTERO_MCP_TOOLSETS` to control which groups are exposed:\n\n| Value | Effect |\n|---|---|\n| *(unset)* | Default profile — core tools plus `libraries`, `search-admin`, `pdf-geometry` |\n| `all` | Everything (the pre-0.9 behaviour) |\n| `none` | Core tools only — the smallest surface |\n| `scite,feeds` | Core plus the named groups |\n| `all,-scite` | Everything except the named groups |\n\nValues are case-insensitive and may be comma- or space-separated. An unknown\ngroup name is an error at startup rather than a silent no-op.\n\n| Group | Default | Contents |\n|---|---|---|\n| `scite` | off | Scite citation tallies and retraction checks (calls scite.ai; pairs with the `[scite]` extra) |\n| `duplicates` | off | Find and merge duplicate items — library maintenance |\n| `discovery` | off | `find_related_papers`, `library_coverage` — corpus-level exploration |\n| `feeds` | off | Zotero RSS feed subscriptions |\n| `relations` | off | Explicit item-to-item \"related items\" links |\n| `libraries` | **on** | List and switch between personal/group libraries |\n| `search-admin` | **on** | Build and inspect the semantic search index |\n| `pdf-geometry` | **on** | Page layout and PDF outline — pairs with area annotations |\n| `chatgpt-connector` | auto | The `search`/`fetch` pair required by ChatGPT deep research |\n\n`chatgpt-connector` is scoped by transport: it turns on automatically when the\nserver is served over `streamable-http` or `sse` (how ChatGPT reaches it) and\nstays off for `stdio`. Name it explicitly to override either way.\n\nAnything not listed above is **core** and always available.\n\n**Note:** a disabled tool is genuinely absent — not merely hidden — so the\nmodel cannot call it. If you rely on a capability, enable its group.\n\nExample (Claude Desktop / Claude Code):\n\n```json\n\"env\": {\n  \"ZOTERO_LOCAL\": \"true\",\n  \"ZOTERO_MCP_TOOLSETS\": \"scite,duplicates\"\n}\n```\n\n## 📚 Available Tools\n\n> Availability depends on your `ZOTERO_MCP_TOOLSETS` setting — see\n> [Tool Groups](#-tool-groups) above.\n\n### 🧠 Semantic Search Tools\n- `zotero_semantic_search`: AI-powered similarity search with embedding models\n- `zotero_update_search_database`: Manually update the semantic search database\n- `zotero_get_search_database_status`: Check database status and configuration\n\n### 🔍 Search Tools\n- `zotero_search_items`: Search your library by keywords\n- `zotero_advanced_search`: Perform complex searches with multiple criteria\n- `zotero_get_collections`: List collections\n- `zotero_get_collection_items`: Get items in a collection\n- `zotero_get_tags`: List all tags\n- `zotero_get_recent`: Get recently added items\n- `zotero_search_by_tag`: Search your library using custom tag filters\n\n### 📚 Content Tools\n- `zotero_get_item_metadata`: Get detailed metadata (supports `format=\"markdown\"`, `format=\"json\"` for complete raw Zotero metadata, and `format=\"bibtex\"`)\n- `zotero_get_item_fulltext`: Get full text content\n- `zotero_get_item_children`: Get attachments and notes for one item or many (pass an array of keys)\n\n### 📝 Annotation & Notes Tools\n- `zotero_get_annotations`: Get annotations (including direct PDF extraction); use `format=\"json\"` for normalized records suitable for scripts and other MCP tools\n- `zotero_synthesize_annotations`: Build a per-paper annotation/note digest; supports `format=\"json\"` for structured grouped output\n- `zotero_get_notes`: Retrieve notes from your Zotero library; pass `query` to search note and annotation text instead of listing\n- `zotero_create_annotation`: Create a highlight (`text=`) or an area annotation (`rect=[x, y, width, height]`)\n- `zotero_manage_note`: Create, update, or delete a note via `action=\"create\"|\"update\"|\"delete\"` (beta feature)\n- `zotero_get_page_layout`: Detect figure/table regions on a PDF page (with captions and normalized coordinates) for accurate area annotation placement — its reported `bbox` can be passed straight to `zotero_create_annotation(rect=...)`\n\n### 📊 Scite Citation Intelligence Tools\n\n> Opt-in group: enable with `ZOTERO_MCP_TOOLSETS=scite` — see [Tool Groups](#-tool-groups).\n\n- `scite_enrich_item`: Get Scite citation tallies and retraction alerts for a paper\n- `scite_enrich_search`: Search your Zotero library with Scite-enriched results (tallies + alerts inline)\n- `scite_check_retractions`: Scan items for retractions and editorial notices\n\n### 📦 Item & Collection Management Tools\n- `zotero_add_by_doi`: Add a paper by DOI with automatic metadata and open-access PDF attachment\n- `zotero_add_by_url`: Add a paper by URL (arXiv, DOI URLs, and general webpages)\n- `zotero_add_by_isbn`: Add a book by ISBN (Open Library + Google Books cascade)\n- `zotero_add_by_bibtex`: Add one or more items from BibTeX (inline or .bib file)\n- `zotero_add_by_csl_json`: Add one or more items from CSL JSON (inline or file)\n- `zotero_add_from_file`: Import a local PDF or EPUB file with automatic DOI extraction\n\nAll add tools take a `collections` parameter accepting collection keys, names, or `parent/child` paths — resolved and validated before the item is created, so unknown or ambiguous specs fail with suggestions instead of producing an unfiled item. They also take `if_exists` (`\"duplicate\"` — default — always creates; `\"file\"` reuses an existing item matching the DOI/arXiv ID/ISBN/URL, filing it into missing collections and adding missing tags; `\"skip\"` leaves a match untouched) and `create_missing_collections` (create unknown collection specs, including path chains, instead of failing). The `zotero-cli add` commands default to `--if-exists file`.\n- `zotero_attach_file`: Attach a local file or a PDF URL to an existing item by key (no new item created; returns the attachment key; idempotent per filename and content hash)\n- `zotero_set_item_parent`: Set, change, or clear an item's parent (`parent_key=null` makes it top-level)\n- `zotero_create_collection`: Create a new collection (folder/project) in your library\n- `zotero_search_collections`: Search for collections by name to find their keys\n- `zotero_manage_collections`: Add or remove items from collections (accepts keys, names, or `parent/child` paths)\n- `zotero_update_item`: Update metadata for an existing item (title, tags, abstract, date, etc.)\n- `zotero_find_duplicates`: Find duplicate items by title and/or DOI, paged with `limit`/`offset`\n- `zotero_merge_duplicates`: Merge duplicate items with dry-run preview; consolidates all child items. `auto=True` merges every high-confidence (same-DOI) group in one pass behind a two-call plan/confirm gate\n- `zotero_get_pdf_outline`: Extract the table of contents / outline from a PDF attachment\n- `zotero_search_by_citation_key`: Look up items by BetterBibTeX citation key (with Extra field fallback)\n\n### 🔗 Related Items Tools\n- `zotero_get_item_related`: Get all related items for a specific Zotero item\n- `zotero_add_item_relation`: Add a related item relationship (creates bidirectional link)\n- `zotero_remove_item_relation`: Remove a related item relationship\n\n## 🧪 Testing\n\n### Unit Tests\n```bash\nuv run pytest tests/     # 1596 tests, ~7 seconds\n```\n\n### Integration Test Plan\nA 45-point live integration test plan is included at `docs/integration-test-plan.md`. It's designed to be given to Claude in Claude Desktop, which will execute each test against your real Zotero library. Tests cover all tools, PDF attachment cascade, attach_mode, BetterBibTeX lookups, and multi-step showcase prompts. See the file for full instructions.\n\n## 🔍 Troubleshooting\n\n### General Issues\n- **No results found**: Ensure Zotero is running and the local API is enabled. You need to toggle on `Allow other applications on this computer to communicate with Zotero` in Zotero preferences.\n- **Can't connect to library**: Check your API key and library ID if using web API\n- **Full text not available**: Make sure you're using Zotero 7+ for local full-text access\n- **Local library limitations**: Some functionality (tagging, library modifications) may not work with local JS API. Consider using web library setup for full functionality. (See the [docs](docs/getting-started.md#local-library-limitations) for more info.)\n- **Installation/search option switching issues**: Database problems from changing install methods or search options can often be resolved with `zotero-mcp update-db --force-rebuild`\n\n### Semantic Search Issues\n- **\"Missing required environment variables\" when running update-db**: Run `zotero-mcp setup` to configure your environment, or the CLI will automatically load settings from your MCP client config (e.g., Claude Desktop)\n- **ChromaDB / stale embedding model errors**: If you changed embedding models and see 404 errors (e.g., `text-embedding-004 is not found`), run `zotero-mcp update-db --force-rebuild` to recreate the collection with your current model. If that doesn't work, delete `~/.config/zotero-mcp/chroma_db/` and rebuild.\n- **Database update takes long**: By default, `update-db` is fast (metadata-only). For comprehensive indexing with full-text, use `--fulltext` flag. Use `--limit` parameter for testing: `zotero-mcp update-db --limit 100`\n- **Semantic search returns no results**: Ensure the database is initialized with `zotero-mcp update-db` and check status with `zotero-mcp db-status`\n- **Limited search quality**: For better semantic search results, use `zotero-mcp update-db --fulltext` to index full-text content (requires local Zotero setup)\n- **OpenAI/Gemini API errors**: Verify your API keys are correctly set and have sufficient credits/quota\n\n### Update Issues\n- **Update command fails**: Check your internet connection and try `zotero-mcp update --force`\n- **Configuration lost after update**: The update process preserves configs automatically, but check `~/.config/zotero-mcp/` for backup files\n\n## ☕ Support\n\nZotero MCP is free and MIT-licensed.\n\nIf it saves you or your lab time, sponsoring helps cover the unglamorous parts: Windows and WSL2 edge\ncases, Zotero schema changes, group-library support, and the embedding/search infrastructure.\n\n<a href=\"https://github.com/sponsors/54yyyu\">\n  <img src=\"https://img.shields.io/badge/Sponsor-GitHub%20Sponsors-ea4aaa?style=for-the-badge&logo=githubsponsors&logoColor=white\" alt=\"Sponsor on GitHub\">\n</a>\n<a href=\"https://buymeacoffee.com/stevenyuyy\">\n  <img src=\"https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black\" alt=\"Buy Me a Coffee\">\n</a>\n\n**Labs and institutions:** the $50 and $200 tiers are meant to be expensable, and include priority\ntriage on the issues affecting your workflow.\n\n## 📄 License\n\nMIT\n",
  "bytes": 47260,
  "sha": "f7a165506659f60f002a2fa2d55a6a4614ae2aa9f09df2e2d8c9084c0779ffbd",
  "repo_slug": "54yyyu/zotero-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_54yyyu_zotero_mcp_d55fbf0e/readme"
}