zotero-mcp

Full read/write access to your Zotero library —
for your own Claude, over MCP.

A standalone, remote MCP server that turns Zotero into something your AI client can act on directly: search, create, tag, update — even delete and move, with real safeguards. v1.0 covers the full library surface — items, collections, tags, trash, saved searches, and schema lookups — in 36 tools. Nothing to install locally, gated by a first-party OAuth 2.1 flow built into the server itself.

Connect via MCP Key safety model View on GitHub

How it works

One login, then tool calls go straight through to Zotero

There's exactly one resource owner, so /authorize doesn't delegate to a third-party identity provider — it's a first-party login form checked against a username/password configured on the server.

1. Add custom connectorThe MCP client points at the server's public URL and self-registers via dynamic client registration (RFC 7591) — no manual app setup.
2. First-party loginThe authorization screen is this app's own login form, not a third-party IdP. Logging in is what actually gates access.
3. Tool calls → ZoteroAuthenticated calls proxy straight to the Zotero Web API using a write-scoped key configured server-side.
Read-only
search_items(query)
Search the connected library by title, creator, tag, or identifier.
Safe write
add_tags(key, version, tags)
Key-preserving — never breaks a Word citation to this item.
Destructive
delete_item_permanently(key, version)
Refuses the call if version is stale — no silent overwrite.

MCP access

Point your own Claude at the server

Add it as a remote MCP connector directly — the OAuth flow prompts for username/password in-browser, nothing to configure by hand.

Claude Desktop
  1. Settings → Connectors → Add → "Add custom connector"
  2. Paste the server's public URL with /mcp appended
https://your-domain.example/mcp
Claude Code
claude mcp add --transport http zotero \
  https://your-domain.example/mcp
Available tools — 36 total
search_items / get_item / list_collections / list_tags / list_trashRead-only lookups against the connected library
list_saved_searches / list_groupsDiscover saved searches, and groups whose id can target move_item_to_different_library
list_item_types / list_item_fields / list_item_type_fields / list_item_creator_typesZotero's own schema — check before a create/update call, instead of guessing
list_attachments / get_fulltext / download_attachment / list_notesRead-only lookups for attachments and notes
create_item / update_itemAdd new items or edit fields — always key-preserving
add_tags / remove_tags / set_tags / rename_tagManage tags per-item or library-wide, without touching item keys
add_to_collection / remove_from_collectionReorganize safely, in-library only
create_collection / update_collectionCreate a collection, or rename/move (reparent) an existing one
create_saved_searchAdd a new saved search
trash_item / restore_from_trashReversible soft delete, and its undo
upload_attachment / create_note / update_noteAdd a file attachment or note, or edit a note in place
delete_item_permanently / move_item_to_different_libraryDestructive — requires the item's current version
delete_collection / delete_tag / delete_saved_searchPermanently remove a collection, a tag library-wide, or a saved search

Features

Built to be trusted with write access

Full CRUD, on purposeNot just search — create, tag, update, delete, and move, so tagging and cleanup can be automated too.
Optimistic concurrencyMutating calls on existing items require the current Zotero version; a stale one is refused, never silently overwritten.
First-party OAuth 2.1A real authorization server built into the app, not Basic Auth in front of it — so "Add custom connector" just works.
Dynamic client registrationAny MCP client can self-register (RFC 7591); completing the login form is what actually gates access.
Remote by defaultShips as its own Docker container behind a reverse proxy — nothing to install or run locally.
Library is a settingZOTERO_LIBRARY_ID/TYPE/API_KEY configure which library it talks to — never hardcoded.
Tested against a fakeAll read/write logic runs against an in-memory FakeZotero in tests — the suite never touches a live library.

Key safety

Why delete and move need care

Full CRUD was a deliberate scope choice, and it comes with a real risk this project is built around rather than around ignoring.

Keys are load-bearingZotero's Word plugin cites items by key via live field codes. Anything that changes an item's key breaks that citation silently.
Delete and move are the riskMoving between libraries is delete+recreate under the hood — both change the key, both are flagged destructive in the tool name and docstring.
Version-gated writesDelete/move require the item's current version. A mismatch means it changed since — the call is refused, not forced through.
Safe ops preserve keysCreate, field updates, tags, notes, and collection membership never touch an item's key — safe to automate freely.
Deliberate, not accidentalThe risk was accepted with eyes open, not overlooked — see the README before extending delete/move further.
OAuth over Basic AuthChosen because Claude's connector UI always tries OAuth discovery first; a bare 401 gets read as "needs OAuth" and fails at a nonexistent endpoint.