Skip to main content
ClaudeWave

Standalone remote MCP server for full read/write access to a Zotero library

MCP ServersRegistry oficial0 estrellas0 forksPythonMITActualizado today
Install in Claude Code / Claude Desktop
Method: pip / Python · -e
Claude Code CLI
claude mcp add cite-caddy -- python -m -e
claude_desktop_config.json (Claude Desktop)
{
  "mcpServers": {
    "cite-caddy": {
      "command": "python",
      "args": ["-m", "-e"]
    }
  }
}
1. Run the command above in your terminal (Claude Code), or paste the JSON config into claude_desktop_config.json (Claude Desktop).
2. Replace any <placeholder> values with your API keys or paths.
3. Restart Claude. The MCP server and its tools appear automatically.
💡 Install first: pip install -e
Casos de uso

Resumen de MCP Servers

# Cite Caddy

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](https://www.zotero.org/support/terms/trademark).
> 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. 36 tools
total — see [Tools](#tools) below for the full list.

## Why this exists

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.

## Key safety (read this before implementing 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:
- Make the destructive intent obvious in the tool name and docstring (an MCP
  client's model reads both before calling), not just in this README.
- Consider requiring the caller to pass back the item's current Zotero
  `version` (optimistic concurrency) so a delete/update can't silently clobber
  a change made concurrently from the Zotero desktop app or another client.
- A dry-run / confirmation step for delete is worth considering, but is an
  implementation decision for whoever builds that tool, not decided here.

## Configuration

**stdio mode** (local, single-user — no `$PORT`): the library to connect to
comes from env vars.

```
ZOTERO_LIBRARY_ID      numeric library ID (user or group)
ZOTERO_LIBRARY_TYPE    "user" or "group" (default: user)
ZOTERO_API_KEY         from Zotero -> Settings -> Security -> Applications
                        (needs write permission, not just read)
```

**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:

```
MCP_TOKEN_STORE_KEY    Fernet key encrypting onboarded tenants' API keys at
                        rest; generate once at deploy time with:
                        python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
MCP_PUBLIC_URL          public HTTPS URL this server is reachable at
MCP_DATA_DIR            where OAuth clients/tokens/tenants persist (default: ./.data)
```

Optional in either mode:

```
MCP_WEBSITE_URL         public site reported as serverInfo.website_url; also used to
                        build serverInfo.icons[0].src as MCP_WEBSITE_URL + "icons/icon.svg"
                        (that file must actually be served there). Left unset, both
                        fields are simply omitted.
```

## Deployment

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.

## Tools

Read-only: `search_items`, `get_item`, `list_collections`, `list_tags`,
`list_trash`, `list_saved_searches`, `list_groups`, `list_item_types`,
`list_item_fields`, `list_item_type_fields`, `list_item_creator_types`,
`list_attachments`, `get_fulltext`, `download_attachment`, `list_notes`.
`list_groups` lists the groups the configured API key's account belongs
to; its `id` doubles as `target_library_id` (with
`target_library_type="group"`) for `move_item_to_different_library`.
`list_item_type_fields`/`list_item_creator_types` describe what
`create_item`/`update_item` will actually accept for a given
`item_type` -- check them instead of guessing when a validation error
comes back. `search_items`' `query` defaults to Zotero's quick search
(title/creator/year); pass `full_text=True` to also match the indexed
content of attached files and notes (Zotero's `qmode="everything"`),
requiring a non-empty `query`.

Safe, key-preserving writes: `create_item`, `create_collection`,
`create_saved_search`, `update_collection`, `update_item`,
`add_tags`/`remove_tags`/`set_tags`, `rename_tag`,
`add_to_collection`/`remove_from_collection`, `trash_item`,
`restore_from_trash`, `upload_attachment`, `create_note`, `update_note`.
`trash_item` is a reversible soft delete (undo with `restore_from_trash`)
-- unlike `delete_item_permanently` below, it doesn't break Word
citations unless the item is later permanently deleted or the trash is
emptied.

Destructive (see "Key safety" above): `delete_item_permanently`,
`move_item_to_different_library`, `delete_collection`, `delete_tag`,
`delete_saved_search`. The first three require the item's/collection's
current Zotero `version` (get it from
`search_items`/`get_item`/`list_collections` first) and refuse the call if
it doesn't match the server's current version, rather than silently
overwriting a concurrent change. `delete_collection` cascades to any
sub-collections (matching Zotero's own "Delete Collection" behavior) but
never deletes the items filed in them. `rename_tag`/`delete_tag` act on
every item in the library carrying that tag, not just one -- there's no
per-tag version to check, since tags aren't standalone versioned entities
in Zotero's API. `delete_saved_search` is destructive but low-risk: a
saved search is just a stored filter, so deleting one never touches items
or citations.

Attachment file content (`upload_attachment`'s `content_base64` argument,
`download_attachment`'s `content_base64` result) travels base64-encoded —
this server is remote and has no access to the caller's local filesystem, so
raw bytes can't be passed as a local path the way pyzotero itself expects.

## Testing

```
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
pytest
```

Tests never call a live Zotero library, even if `.env` has real
credentials: `app/zotero_service.py` (all Zotero read/write logic) is
exercised against `tests/fakes.py`'s in-memory `FakeZotero`, and
`app/mcp_server.py`'s tool functions are tested directly against a
`ZoteroService` backed by that fake (see `configure_service()`).

## Status

**v1.5** — deployed and in active use, with full CRUD coverage of the
Zotero Web API's item/collection/tag/trash/saved-search/schema surface
(36 tools; see [Tools](#tools)). Add it as a remo
mcpmcp-servermodel-context-protocoloauth2pythonzoterozotero-api

Lo que la gente pregunta sobre cite-caddy

¿Qué es herbertkokholm/cite-caddy?

+

herbertkokholm/cite-caddy es mcp servers para el ecosistema de Claude AI. Standalone remote MCP server for full read/write access to a Zotero library Tiene 0 estrellas en GitHub y se actualizó por última vez today.

¿Cómo se instala cite-caddy?

+

Puedes instalar cite-caddy clonando el repositorio (https://github.com/herbertkokholm/cite-caddy) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.

¿Es seguro usar herbertkokholm/cite-caddy?

+

herbertkokholm/cite-caddy aún no ha sido auditado por nuestro agente de seguridad. Revisa el repositorio original en GitHub antes de usarlo en producción.

¿Quién mantiene herbertkokholm/cite-caddy?

+

herbertkokholm/cite-caddy es mantenido por herbertkokholm. La última actividad registrada en GitHub es de today, con 0 issues abiertos.

¿Hay alternativas a cite-caddy?

+

Sí. En ClaudeWave puedes explorar mcp servers similares en /categories/mcp, ordenados por popularidad o actividad reciente.

Despliega cite-caddy en tu cloud

Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.

¿Mantienes este repo? Añade un badge a tu README

Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.

Featured on ClaudeWave: herbertkokholm/cite-caddy
[![Featured on ClaudeWave](https://claudewave.com/api/badge/herbertkokholm-cite-caddy)](https://claudewave.com/repo/herbertkokholm-cite-caddy)
<a href="https://claudewave.com/repo/herbertkokholm-cite-caddy"><img src="https://claudewave.com/api/badge/herbertkokholm-cite-caddy" alt="Featured on ClaudeWave: herbertkokholm/cite-caddy" width="320" height="64" /></a>

Más MCP Servers

Alternativas a cite-caddy