Remote MCP server for full read/write access to a Zotero library
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β or use 1-click editor setup below.
One-click editor setup isnβt available for this listing yet β we donβt have a confirmed install command, and weβd rather show nothing than point your editor at the wrong package or host. Follow the projectβs own setup instructions, linked above.
Inspect callable tools, capabilities, and parameters exposed to AI agents by Cite Caddy.
search_itemsquery` defaults to Zotero's quick search (title/creator/year); `full_text=True` also matches indexed content of attached files/notes (`qmode="everything"`), requires non-empty `query`. Each result's `creators` is a list of `{creatorType, firstName, lastName}` (or `{creatorType, name}` for single-fiβ¦
get_itemcreators` shape as above.
list_collectionsCallable MCP tool function
list_tagsCallable MCP tool function
list_trashcreators` shape as above.
list_saved_searchesCallable MCP tool function
A reference-library bridge for AI assistants β a standalone, remote MCP server.
Independent, unofficial project. Not affiliated with, endorsed by, or sponsored by the Corporation for Digital Scholarship (Zotero) β see Zotero's trademark policy. Built on the Zotero Web API; today's backend is Zotero, but the name and tool surface are meant to support others later.
Cite Caddy gives full read/write access to a Zotero library β search, add, tag, update, delete, and move items; create, rename, and delete collections; upload/download attachments and read their extracted full text; read and write item notes; manage tags, trash, and saved searches library-wide; and look up Zotero's own item-type/field schema. 39 tools total β see Tools below for the full list.
Read-only tools that match findings against a Zotero library (e.g. by DOI/arXiv ID) can safely stop at reporting β they never need to write anything back. This project goes further on purpose: full CRUD against a Zotero library, including delete and move, so that tagging, adding, and cleaning up items can be automated too.
That's a deliberate scope choice, and it comes with a real risk: any write that changes an existing item's key (delete, move to another library, "clean library" reset) breaks Word documents that cite it via the Zotero Word plugin's live field codes β see "Key safety" below before touching delete/move.
Any Zotero item cited in a Word document via the Zotero Word plugin is referenced by that item's key, embedded in a live field code. Operations that preserve an item's key (create, update fields, add/remove tags, add notes) are safe. Operations that don't (delete, and library-to-library move, which Zotero implements as delete+recreate) will break those citations silently β the Word document won't error, it'll just show stale/broken field text next time someone updates fields or opens Zotero the next time.
Full CRUD was chosen deliberately for this project despite that risk. When implementing delete/move tools:
version (optimistic concurrency) so a delete/update can't silently clobber
a change made concurrently from the Zotero desktop app or another client.delete_item_permanently, delete_collection, delete_tag,
delete_saved_search, move_item_to_different_library, and
update_publication_status all accept an optional idempotency_key. Pass
the same opaque string when retrying a call after a lost or ambiguous
response (e.g. a network timeout) and the original outcome β success or
error β is replayed instead of running the operation against Zotero again.
Reusing a key for a call with different arguments raises an error instead of
silently returning the old result, so it's safe to generate one key per
logical request and reuse it freely on retries of that same request.
This matters most for move_item_to_different_library: it recreates the
item in the target library, then deletes it from the source. If the create
succeeds but the delete then fails, a bare retry would redo the whole thing
β since the source item's version hasn't changed β creating a second
duplicate in the target library. With idempotency_key, the retry replays
the cached failure (and its "clean up manually" guidance) instead of
touching Zotero again.
The cache is in-memory per server process (per onboarded tenant in HTTP mode), with a 24h TTL β it survives retries within that window, not across a redeploy/restart.
stdio mode (local, single-user β no $PORT): the library to connect to
comes from env vars.
HTTP mode (remote, multi-tenant β $PORT set): there's no single
configured library β each caller brings their own Zotero Library ID/Type/API
Key via the /login form (see "Deployment" below). Instead:
Optional in either mode:
Ships as its own Docker container (docker-compose.yml), meant to sit
behind a reverse proxy that terminates TLS and forwards to the container's
port on localhost. Remote/hosted by default, not a local stdio server β an
MCP client just points at the URL, nothing to install or run locally.
Access is gated by a real OAuth 2.1 authorization server built into the
app itself (app/oauth_provider.py), not HTTP Basic Auth in front of it.
This is a deliberate design choice: Claude Desktop/claude.ai's "Add custom
connector" UI is OAuth-first β it always tries the OAuth discovery +
authorization-code dance against a new server, so a plain 401 in front of
the server (as Basic Auth would produce) gets read as "this server needs
OAuth" and fails once it hits a nonexistent /authorize endpoint.
Implementing a real (if minimal) OAuth server is what makes "Add custom
connector" work.
Multi-tenant and self-service: /authorize doesn't delegate to a
third-party identity provider β it shows a first-party login form asking
for a Zotero Library ID, Library Type, and API Key. Submitting the form
validates the key directly against the Zotero API; a successful
validation both grants access and registers ("onboards") that library as
a tenant of this server, all in one step β there's no separate sign-up
and no admin approval. Any MCP client can dynamically register itself
(RFC 7591), but completing the login form with a working Zotero key is
what actually gates access. Each caller's tool calls are then routed to
their own Zotero library, not a shared one. See
app/oauth_provider.py's module docstring for the full flow. Registered
clients, issued tokens, and onboarded tenants' credentials (API keys
encrypted at rest with MCP_TOKEN_STORE_KEY) persist to MCP_DATA_DIR
(a Docker volume) so redeploys don't log connected clients out or forget
onboarded tenants.
.env on the host (not in this repo) holds MCP_TOKEN_STORE_KEY/
MCP_PUBLIC_URL, consumed via docker-compose.yml's env_file:.
ZOTERO_LIBRARY_ID/ZOTERO_LIBRARY_TYPE/ZOTERO_API_KEY are not needed
for the HTTP deployment β those only apply to stdio mode.
.github/workflows/deploy.yml automates redeploying to an already
set-up host: manual trigger only (workflow_dispatch, never on push),
runs the test suite first, then syncs the repo over SSH and rebuilds the
container. It needs its own GitHub Actions secrets for the deploy SSH
key and target host/port/user β see the workflow file for the full list.
Use a dedicated deploy key (not whatever key you use for direct/manual
access), so it can be revoked independently if it ever leaks.
Four unauthenticated GET endpoints, HTTP mode only (all require $PORT,
same as /login):
/healthz β plain 200 OK, for a load balancer/uptime check.
/status β JSON snapshot of aggregate, process-level activity:
Factual signals from GitHub, npm, and our automated checks β not a rating.
No reviews yet β be the first to share how this listing worked for you.
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/cite-caddy)<a href="https://allmcps.com/mcp/cite-caddy"><img src="https://allmcps.com/api/badge/cite-caddy?style=directory" alt="Cite Caddy on AllMCPs" /></a>