zotero-mcp
zotero-mcp
A Model Context Protocol server for Zotero. It lets Claude Code, Claude Desktop and any other MCP client search your library, read items, take notes, file things into collections and find papers by meaning rather than by exact words.
This is a ground-up rewrite of the earlier zotero-mcp server with a small,
typed tool surface, bounded responses, real error reporting and a semantic
search index that is chunk-level, incremental and reranked.
What it does
Read
Tool | Purpose |
| Keyword search across the library, paged, with type filter |
| One item's full record, children, tags and collections; flags items in the Trash |
| Browse the collection tree and its contents |
| Tags and a library overview |
| Is Zotero reachable, is the index built, which optional features are on |
Find by meaning
Tool | Purpose |
| Vector search over titles, abstracts and PDF fulltext, reranked by a cross-encoder. Each hit shows the passage that matched, whether it came from the metadata or the fulltext, and how many passages agreed. Filter by item type, year range or collection. |
Write (only when a web API key with write permission is configured)
Tool | Purpose |
| Create, edit and trash items. Delete is always "move to Trash", never permanent. |
| Add or remove tags and collection memberships |
| Attach a note to an item, or create a standalone one |
Every write tool defaults to dry_run=true and returns a preview of the change.
The client must pass dry_run=false to apply it. Updates carry Zotero's version
header, so a concurrent edit in the desktop app produces a clear conflict error
rather than a silent overwrite.
Install
Requires Python 3.10+ and uv.
uv tool install 'zotero-mcp-next[semantic,pdf] @ git+https://github.com/v-i-n-a-y/zotero-mcp-v2'The semantic extra pulls in ChromaDB and sentence-transformers for semantic
search; pdf adds PyMuPDF and ebooklib for fulltext extraction. Omit both for a
lean install with keyword search only.
Configure
The server reads environment variables (or a JSON file via ZOTERO_MCP_CONFIG).
Variable | Meaning |
|
|
| Web API key from https://www.zotero.org/settings/keys |
| Your numeric user ID (shown on the same page) |
|
|
|
|
|
|
| Sentence-transformers model; default |
| Where the index lives; default |
Modes. local talks to the running Zotero desktop app (port 23119): fast,
no key needed, read-only. web talks to api.zotero.org and needs a key.
hybrid reads locally and writes through the web API, which is the best
everyday setting when the desktop app is open. auto picks local if the app is
reachable, otherwise web.
Claude Code
claude mcp add zotero -s user \
-e ZOTERO_MCP_MODE=hybrid \
-e ZOTERO_API_KEY=... \
-e ZOTERO_LIBRARY_ID=... \
-- zotero-mcpClaude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"zotero": {
"command": "zotero-mcp",
"env": {
"ZOTERO_MCP_MODE": "hybrid",
"ZOTERO_API_KEY": "...",
"ZOTERO_LIBRARY_ID": "..."
}
}
}
}Semantic search
Build the index once from a terminal; it needs the desktop app open (or web mode) to read fulltext:
zotero-mcp index build # first run: roughly an hour per thousand PDFs
zotero-mcp index statusAfter that the running server refreshes the index on the schedule above, re-embedding only items whose Zotero version changed. Extracted fulltext is cached beside the index, so switching embedding models rebuilds in minutes.
How a query is answered: the vector index returns a pool of candidate chunks (metadata chunk plus fulltext chunks per item, with trailing reference lists stripped), a cross-encoder rescores the pool against the query, citation-dense chunks are penalised, and chunks collapse to one hit per item with a small bonus for items that matched in several places. Everything runs locally; on Apple silicon the models use Metal.
CLI
zotero-mcp # serve over stdio (what MCP clients run)
zotero-mcp health # can the configured library be reached?
zotero-mcp index build # build or incrementally refresh the semantic index
zotero-mcp index status
zotero-mcp index clear
zotero-mcp versionDesign
Every response is bounded. Lists are paged with opaque cursors; long text is truncated with an explicit marker.
Failures raise MCP tool errors with a stable code (
not_found,invalid_input,auth,write_conflict,backend_unavailable,unsupported) and a hint, never a success whose text starts with "Error:".Tools return both markdown for humans and structured content for clients that can use it.
Logging goes to stderr; stdout carries only the protocol.
More in docs/design.md.
Development
uv sync --extra dev
uv run pytest
uv run ruff check src testsLicence
MIT.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/v-i-n-a-y/zotero-mcp-v2'
If you have feedback or need assistance with the MCP directory API, please join our Discord server