About Zotero MCP Claude Code
Zotero MCP Claude Code is an MCP server published by lricher7329 in the Developer Tools category: zotero MCP plugin with Claude Code compatibility - fork by lricher7329.
The repository has 45 stars and 4 forks, with the last commit 17 days ago. Recent commit activity is the strongest signal that reported issues will get attention.
Install
git clone https://github.com/lricher7329/zotero-mcp-claude-codeThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about Zotero MCP Claude Code
Powered by Claude · Grounded in docs
Security checks
Static checks derived from the repository 7 days ago. Heuristics on the source, not an audit.
- ✓README presentThe repository ships documentation.
- ✓License declaredUsable under a stated license.
- ✓Tests presentA test suite exists in the repository.
- ✓Dependencies pinnedA lockfile pins dependency versions.
- ✓No dynamic code executionSource scan found no eval, dynamic Function, or shell execution.
1 check has not been run yet.
Releases
README
Zotero MCP for Claude Code
A Zotero plugin that exposes your library to AI assistants via the Model Context Protocol (MCP). This fork adds Claude Code compatibility and write operations.
Note: This fork has been developed and tested with Claude Code, Claude Desktop, and Codex on Zotero 7–10 for macOS (latest macOS). The plugin manifest declares compatibility with Zotero 7, 8, 9, and 10. It uses standard MCP over Streamable HTTP, so it should work with any MCP-compatible client and platform, but other clients and operating systems have not been tested by this fork's author.
What it does
The plugin runs an MCP server inside Zotero that AI clients connect to over HTTP. No separate server process needed.
AI Client <--Streamable HTTP--> Zotero Plugin (integrated MCP server)
Your AI assistant can then search your library, read PDFs, extract annotations, create items, manage collections, and more.
Quick start
1. Install the plugin
Download the latest .xpi from the Releases page and install it in Zotero via Tools > Plugins → gear menu → Install Plugin From File….
Upgrading to Zotero 10? Zotero 10 disables plugin versions older than v1.8.7 (their manifest caps compatibility at Zotero 9), which leaves nothing listening on port 23120 and MCP clients failing with
ECONNREFUSED. Install v1.8.7 or later from the Releases page. Existing installs on Zotero 7–9 pick it up through auto-update.
2. Connect Claude Code
claude mcp add zotero-mcp http://127.0.0.1:23120/mcp -t http
Or add to your MCP config (~/.claude.json or .mcp.json):
{
"mcpServers": {
"zotero-mcp": {
"type": "http",
"url": "http://127.0.0.1:23120/mcp"
}
}
}
If you've enabled Require auth token on local connections (or Allow remote access — which forces auth regardless), copy the token from the prefs UI and add it as a header:
{
"mcpServers": {
"zotero-mcp": {
"type": "http",
"url": "http://127.0.0.1:23120/mcp",
"headers": {
"Authorization": "Bearer zmcp_<your-token>"
}
}
}
}
See Authentication below.
3. Verify
claude mcp list
You should see zotero-mcp with the available tools listed.
Supported clients
| Client | Connection | Tested |
|---|---|---|
| Claude Code | Native HTTP MCP (recommended) | Yes |
| Codex | Native Streamable HTTP MCP via ~/.codex/config.toml |
Yes |
| Claude Desktop | Streamable HTTP via mcp-remote (see Claude Desktop setup) | Yes |
| Cursor IDE | Streamable HTTP via mcp-remote | No |
| Cherry Studio | Native Streamable HTTP | No |
| Gemini CLI | Native HTTP MCP | No |
| Cline (VS Code) | Streamable HTTP via mcp-remote | No |
| Continue.dev | Streamable HTTP via mcp-remote | No |
| Qwen Code | Native HTTP MCP | No |
| Chatbox | Streamable HTTP via mcp-remote | No |
| Trae AI | Streamable HTTP via mcp-remote | No |
The plugin preferences include a Client Configuration Generator that produces ready-to-use config for each client.
Claude Desktop setup
Claude Desktop runs local MCP servers as stdio commands, so it reaches the plugin's HTTP endpoint through the mcp-remote bridge (requires Node.js). Custom Connectors (Settings → Connectors) can't be used for this: Anthropic's cloud makes those connections, so 127.0.0.1 is unreachable.
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (Settings → Developer → Edit config), then quit and reopen Claude Desktop:
{
"mcpServers": {
"zotero-mcp": {
"command": "/opt/homebrew/bin/npx",
"args": ["-y", "mcp-remote", "http://127.0.0.1:23120/mcp"],
"env": { "PATH": "/opt/homebrew/bin:/usr/bin:/bin" }
}
}
}
Use full paths: Claude Desktop launched from the Dock doesn't load your shell profile, so npx from nvm or other shell-managed installs isn't on its PATH. Point command at your npx (which npx) and include the directory containing node in env.PATH. With auth enabled, add "--header", "Authorization: Bearer zmcp_<your-token>" to args. Settings → Developer shows the server as Running; logs are in ~/Library/Logs/Claude/mcp-server-zotero-mcp.log.
MCP tools (50 max total)
Read tools (26 — always available)
| Tool | Description |
|---|---|
search_library |
Search with advanced filtering (title, creator, year, tags, item type, boolean operators, relevance scoring, pagination). On Zotero 10, structured conditions with nested and/or groups, resultLevel (items, attachments, notes, annotations), and isEmpty/isNotEmpty |
search_annotations |
Search annotations/highlights by query, color, or tags |
get_item_details |
Get full metadata for an item |
get_item_abstract |
Get an item's abstract |
get_annotations |
Get annotations for specific items |
get_content |
Extract content from PDFs, notes, and attachments |
get_collections |
List all collections |
search_collections |
Search collections by name |
get_collection_details |
Get collection metadata |
get_collection_items |
List items in a collection |
get_subcollections |
List child collections |
search_fulltext |
Full-text search across attachments with context snippets |
get_tags |
List all tags with optional filtering |
get_related_items |
Get items linked via Zotero's Related feature |
generate_bibliography |
Generate formatted citations using Zotero's citation engine |
search_by_identifier |
Find library items by DOI, ISBN, or PMID |
get_library_stats |
Library summary: item counts by type, tag/collection/trash counts |
get_item_types |
List all valid Zotero item types with localized names |
get_creator_types |
List valid creator types, optionally filtered by item type |
get_item_type_fields |
List valid fields for a given item type |
get_trash_items |
List items in the trash with pagination |
get_recently_modified |
Get items modified within N days |
semantic_search |
AI-powered semantic search using embedding vectors |
find_similar |
Find items similar to a given item |
semantic_status |
Check semantic index status |
fulltext_database |
Query the full-text content cache (list, search, get, stats) |
Write tools (24 — when enabled in preferences)
| Tool | Description |
|---|---|
create_item |
Create a new library item with fields, creators, tags, collections |
update_item |
Update fields and creators on an existing item |
add_note |
Create a standalone or child note |
update_note |
Update an existing note's content and tags |
trash_item |
Move an item to the trash |
add_tags |
Add tags to an item |
remove_tags |
Remove tags from an item |
rename_tag |
Rename a tag across all items in the library |
delete_tag |
Delete a tag from the entire library |
create_collection |
Create a new collection or subcollection |
rename_collection |
Rename a collection |
delete_collection |
Delete a collection (items optionally deleted) |
move_collection |
Move a collection to a new parent or root |
add_to_collection |
Add an item to a collection |
remove_from_collection |
Remove an item from a collection |
move_item_to_collection |
Atomically move an item between collections |
add_related_item |
Create a bidirectional Related link between items |
remove_related_item |
Remove a Related link between items |
import_attachment_url |
Import a file from a URL as an attachment |
batch_tag |
Tag multiple items at once (max 100) |
batch_add_to_collection |
Add multiple items to a collection (max 100) |
batch_remove_from_collection |
Remove multiple items from a collection (max 100) |
batch_trash |
Trash multiple items at once (max 100) |
restore_from_trash |
Restore a trashed item back to the library |
Write tools are gated by per-scope opt-in checkboxes in preferences (see Write scopes below). Tools whose scopes are disabled are hidden from the tools/list response — they don't just fail-on-call, they don't exist as far as the client can see.
Undo (Zotero 10): changes made through write tools land on Zotero's undo stack, so Edit → Undo (Cmd/Ctrl+Z in the items list) reverts them like Zotero's own edits — labelled "Undo Add Tag", "Undo Edit Metadata", and so on. A batch tool is one undo step. Trashing and restoring are undoable; creating items and permanently deleting are not (a Zotero limitation). The undo history lives in memory and clears when Zotero restarts. On Zotero 7–9 there is no undo.
Authentication
The plugin auto-generates a per-install bearer token on first start, stored in the extensions.zotero.zotero-mcp-plugin.mcp.server.authToken preference and surfaced in Settings → Zotero MCP for Claude Code → MCP Server → Authentication with Copy and Regenerate buttons.
| Pref | Default | Behavior |
|---|---|---|
mcp.server.allowRemote |
false |
When true, listener binds 0.0.0.0 and auth is mandatory regardless of requireAuth |
mcp.server.requireAuth |
false |
When true, loopback callers must also present the token |
When auth is required, send the token via either:
Authorization: Bearer zmcp_<48-hex-chars>
or
X-Zotero-MCP-Token: zmcp_<48-hex-chars>
X-Zotero-MCP-Token exists for browser-extension callers that can't set Authorization. Both headers force a CORS preflight, defeating drive-by simple-form CSRF.
Loopback default (no auth) keeps existing client configs working out of the box. Public-facing or shared-machine setups should enable requireAuth. The /ping, /capabilities, /help, /mcp/status, and GET /mcp endpoints stay open even when auth is on, so health checks and config generators don't need credentials.
Regenerating the token invalidates every existing client config — clients will get 401 until you update them.
Defense-in-depth checks (always on)
These run regardless of auth state:
Origin: null(sandboxed iframes,file://, cross-origin redirects) is rejectedPOST /mcprequiresContent-Type: application/json(defeatstext/plainCSRF via simple form POST)Hostheader must be loopback whenallowRemote=false- Duplicate
Hostheaders rejected Mcp-Session-Idvalidated against^mcp-[a-f0-9-]{8,80}$and capped at 256 active sessions with LRU eviction- Global + per-client token-bucket rate limits, with a stricter bucket on write tools. Remote clients are limited per IP; loopback clients are limited per active
Mcp-Session-Id(falling back to the address), so Claude Code, its subagents and Claude Desktop don't share one bucket.initialize,notifications/*,tools/listandpingget a small reserve so a client can still reconnect after a burst of tool calls - 10s wall-clock deadline on full request body read
Content-Length > 1MBrejected before reading- Zotero 9/10-compatible socket reads use Gecko readiness callbacks rather than
available()polling, avoiding accepted-but-unresponsive MCP connections
Write scopes
mcp.write.enabled (single boolean) was replaced in v1.8.0 by seven granular scopes. A tool is only exposed in tools/list when every scope it requires is enabled.
| Scope pref | Tools |
|---|---|
mcp.write.notes |
add_note, update_note |
mcp.write.tags |
add_tags, remove_tags |
mcp.write.collections |
add_to_collection, remove_from_collection, create_collection, rename_collection, move_collection, move_item_to_collection |
mcp.write.metadata |
create_item, update_item, add_related_item, remove_related_item |
mcp.write.delete ⚠ |
trash_item, restore_from_trash, delete_collection |
mcp.write.bulk ⚠ |
(combined with another scope on bulk operations — see below) |
mcp.write.import ⚠ |
import_attachment_url (SSRF risk) |
Scopes marked ⚠ are destructive and default to off. Multi-scope tools require all listed scopes:
| Tool | Scopes required |
|---|---|
batch_tag |
bulk + tags |
batch_add_to_collection |
bulk + collections |
batch_remove_from_collection |
bulk + collections |
batch_trash |
bulk + delete |
delete_tag |
delete + bulk (library-wide) |
rename_tag |
tags + bulk (library-wide rewrite) |
delete_collection with deleteItems: true |
delete + bulk |
Migration from mcp.write.enabled
On first run after upgrading from v1.7.0 or earlier:
- If the legacy
mcp.write.enabledwastrue, the safe scopes (notes,tags,collections,metadata) migrate on; the destructive scopes (delete,bulk,import) stay off. Re-enable them deliberately if you need them. - If
mcp.write.enabledwas unset orfalse, all scopes default to off.
Reading scope state programmatically
Zotero.Prefs.get("extensions.zotero.zotero-mcp-plugin.mcp.write.metadata", true)
// → true | false | undefined
The same pref keys appear in ~/Library/Application Support/Zotero/Profiles/<profile>/prefs.js.
Plugin preferences
Configure in Zotero > Settings > Zotero MCP for Claude Code:
- MCP Server -- Enable/disable, port (default 23120), remote access
- Authentication -- Bearer token (Copy / Regenerate), require-auth-on-loopback toggle
- Write Scopes -- Per-scope opt-in (notes, tags, collections, metadata, delete, bulk, import)
- Client Configuration Generator -- Generate config JSON for any supported AI client
- MCP Content Settings -- Content processing mode (minimal/preview/standard/complete/custom), max tokens
- Semantic Search -- Embedding provider (OpenAI, Ollama, etc.), model, dimensions, API key
- Semantic Index -- Build/rebuild/clear index, auto-update, progress monitoring
Semantic search
The plugin supports AI-powered semantic search using embedding vectors:
- Configure an embedding provider in preferences (OpenAI, Ollama, or any OpenAI-compatible API)
- Build the index (indexes item titles, abstracts, and full text)
- Use
semantic_searchfor natural language queries orfind_similarto find related items
The vector index is stored locally in SQLite with Int8 quantization for efficient storage.
Development
Prerequisites
- Zotero 7, 8, 9, or 10
- Node.js 22.8+ (required by
zotero-plugin-scaffold0.9)
Setup
cd zotero-mcp-plugin
npm install
npm run build # Production build
npm run start # Dev mode with auto-reload
Testing
npm run check # lint + build/type check + unit tests (the local gate)
npm run test:unit # Mocha unit tests in Node (no Zotero needed)
npm run lint:check # Prettier + ESLint
Endpoints
| Endpoint | Method | Purpose | Auth required when requireAuth=true |
|---|---|---|---|
/mcp |
POST | MCP JSON-RPC 2.0 requests | yes |
/mcp |
GET | Endpoint info (JSON) | no |
/mcp |
DELETE | Session termination (per MCP 2025-03-26 spec) | yes |
/ping |
GET | Health check (returns pong) |
no |
/mcp/status |
GET | Server status | no |
/capabilities |
GET | Server capabilities (also at /help, /mcp/capabilities) |
no |
Protocol versions supported: 2024-11-05 and 2025-03-26 (negotiated via initialize).
Fork changes
This fork (lricher7329/zotero-mcp-claude-code) adds the following on top of the upstream cookjohn/zotero-mcp:
- Claude Code compatibility -- Proper request body reading, Accept header validation, DELETE method, notification handling, batch requests, dual protocol version support (
2024-11-05+2025-03-26) - Write operations -- ~24 MCP write tools for creating/updating items, notes, tags, collections, relations, and attachments, plus batch operations and trash management — all gated by per-scope opt-ins
- Library introspection -- Schema discovery tools (item types, creator types, field lists), library stats, trash listing, recently modified items
- Security hardening (v1.8.0) -- Per-install bearer token auth; Origin/Content-Type CSRF protection; per-scope write opt-ins (destructive ops default off); global + per-IP + per-session rate limits; SSRF guard with IPv6-mapped-IPv4 and decimal/octal IPv4 detection; itemKey/collectionKey format validation; session ID hardening with LRU cap; body-read deadline; sanitized error messages
- MCP/JSON-RPC compliance fixes (v1.8.0) -- Tool execution failures returned as
result.isError(LLM can recover) rather than-32603; argument validation maps to-32602; parse-errorid: nullper spec;notifications/initializedcorrect; empty batch returns-32600; dropped falsetools.listChangedadvertisement get_item_detailsmode=complete -- Now enumerates all item-type fields viaZotero.ItemFields.getItemTypeFields, includingextra(PMID/PMCID/citation key per Zotero convention),collectionsmembership,dateAdded/dateModified/accessDate. Critical for downstream identifier extraction.- Per-session rate limits on loopback (v1.8.10) -- Every local client shared one
127.0.0.1bucket, so parallel Claude Code subagents could exhaust it and reconnects failed with "Rate limit exceeded". Loopback traffic is now limited per active session, reconnect handshakes draw on a small reserve, and the global cap burst rose from 120 to 240 - Zotero 10 native features (v1.8.9) -- MCP edits (metadata, notes, tags, collections, related items, restore) can be undone in Zotero with Cmd/Ctrl+Z, one undo step per call or batch; full text reads Zotero's cached index again (Zotero 10 removed the old API, so PDF text timed out) and
search_fulltextuses Zotero's search index;get_contenton long papers drops from minutes to about a second;search_libraryaccepts structured condition groups andresultLevel - Zotero 10 support (v1.8.7) -- Manifest and auto-update
strict_max_versionraised to10.*; attachment file-size lookup moved from the legacyOS.FileAPI toIOUtils - Codebase audit -- Typed errors, API validation, singleton fixes, batched queries, module refactoring, and a Node unit-test suite (
npm run test:unit)
License
Acknowledgements
- Zotero -- Open-source reference management
- cookjohn/zotero-mcp -- Original upstream project
- Model Context Protocol -- The protocol standard
- Zotero Plugin Template -- Plugin scaffolding
README fetched from the repository 7 days ago. The original is authoritative.