Skip to main content
Glama
README.md
# zotero-mcp

Portable MCP stdio server for Zotero Desktop. It keeps Local API reading, adds an authenticated in-process Bridge for Zotero 9.0.6 writes, and never edits Zotero SQLite or storage files directly.

This package intentionally excludes paper-reading queues, worker pools, reading-note templates, and the Codex `zotero-reader` skill.

## Requirements

- Windows, macOS, or Linux
- Zotero Desktop 7, 8, or 9 running locally
- Python 3.10+
- An MCP host that supports stdio servers

## Start the MCP server

Windows hosts can use `run.cmd`:

```json
{
  "mcpServers": {
    "zotero-mcp": {
      "command": "cmd.exe",
      "args": ["/d", "/c", "C:\\Tools\\zotero-mcp\\run.cmd"],
      "env": {
        "ZOTERO_LOCAL_BASE_URL": "http://127.0.0.1:23119",
        "ZOTERO_BRIDGE_CONFIG": "C:\\Users\\<user>\\AppData\\Roaming\\zotero-mcp\\bridge.json",
        "NO_PROXY": "localhost,127.0.0.1,::1",
        "PYTHONUTF8": "1"
      }
    }
  }
}
```

Unix hosts can run `run.sh` or use the Unix example configuration. Replace all paths for the target computer.

## Local API

Zotero normally exposes the read service at `http://127.0.0.1:23119`. See [LOCAL-API-GUIDE.md](LOCAL-API-GUIDE.md) for enabling local communication, checking the service, proxy handling, and read-only troubleshooting.

Local API reads do not need an API key. Zotero 9.0.6 may omit `Zotero-Server-ID`; that means Local API writes are unavailable, not that the MCP should bypass capability detection.

## Zotero Bridge

Zotero 9.0.6 can expose the Local API for reading without the older Local API write authorization header. The optional `zotero-bridge/` legacy bootstrap extension runs inside Zotero and calls Zotero internal object APIs (`saveTx`, `trashTx`, `eraseTx`, and `relinkAttachmentFile`). It is the write transport for this build.

### Install and pair

The current Bridge package is `1.4.4` and declares compatibility with Zotero `9.0.x`. If Zotero rejects an older XPI as incompatible, rebuild and install the newly generated `zotero-mcp-bridge.xpi`. Zotero Debug Bridge is a different extension and cannot replace this Bridge.

1. Keep Zotero open and run `powershell -ExecutionPolicy Bypass -File .\zotero-bridge\build-xpi.ps1` to create the XPI with `manifest.json`, `bootstrap.js`, `bridge-server.js`, and `PROTOCOL.md` at the archive root.
2. In Zotero choose **Tools -> Add-ons -> gear -> Install Add-on From File**, select the XPI, and restart Zotero if requested.
3. When the extension is running it creates `%APPDATA%\zotero-mcp\bridge.json` with a random token, loopback host, port `23120`, and protocol `zotero-bridge/1`.
4. Point `ZOTERO_BRIDGE_CONFIG` to that file only when using a non-default location. Do not commit or share the file: the token grants write access to the local Zotero instance.
5. Call `zotero_capabilities` or run `run.ps1 -Check`. A working Zotero 9 Bridge reports `bridgeOnline=true`, `bridgeWrite=true`, and `writeTransport=bridge`.

The Bridge binds only to `127.0.0.1`. It removes its config file on shutdown if the file still belongs to that instance. `GET /v1/capabilities` is unauthenticated for health detection; every PATCH, POST, and DELETE request requires `Authorization: Bearer <token>`.

### Bridge protocol

Implemented routes are documented in [zotero-bridge/PROTOCOL.md](zotero-bridge/PROTOCOL.md): item metadata, tags, collections, notes, attachment replacement, trash, restore, and permanent erase. The Bridge validates field whitelists, parent-child relationships, expected versions, confirmation phrases, attachment paths, and file sizes before calling Zotero.

## Capabilities and fallback

`zotero_capabilities` reports Local API and Bridge state separately: `localApiRead`, `localApiWrite`, `bridgeOnline`, `bridgeWrite`, `bridgeTrash`, `bridgePermanentDelete`, `bridgeAttachmentWrite`, `bridgeNoteWrite`, and the active `writeTransport`.

- Bridge online and writable: metadata, tags, collections, notes, attachments, trash, restore, and permanent deletion use the Bridge.
- Bridge unavailable but an older Local API exposes `Zotero-Server-ID`: existing Local API write paths remain available.
- Neither transport writable: only read tools are listed.
- `tools/list` and `tools/call` both re-check capabilities, so an old client cannot call a tool after the Bridge goes offline.

## Edit and delete safety

All mutating MCP tools require `confirm=true`. Metadata fields are whitelisted; creators, tags, collections, notes, and attachments use dedicated validation. Edit tools may resolve the current version when omitted; destructive delete and restore tools require the caller to supply `expectedVersion` and reject conflicts.

`zotero_delete_item` defaults to `mode="trash"`. A parent with child notes or attachments, including already-trashed descendants and PDF annotations, is rejected unless `cascadeChildren=true` is explicit. Trash cascades through the discovered descendants; permanent deletion additionally requires `mode="permanent"`, `confirm=true`, and the exact phrase `DELETE <itemKey>`. When targeting a child item through the item route, pass its `parentItem` key so the Bridge can verify the relationship. `zotero_restore_item` restores a trashed item and can explicitly restore descendants.

Attachment replacement accepts only an absolute path to a regular source file outside Zotero data storage, enforces `maxBytes`, checks optional MD5/version preconditions, and lets Zotero copy it into managed storage. MCP never receives a storage path operation.

## Tools

- Read: `zotero_status`, `zotero_capabilities`, `zotero_probe`, collections, inventory, search, batch item lookup, children, full text, file URLs, tags, groups, BibTeX, citations, and Connector selection.
- Edit: `zotero_update_item`, `zotero_update_tags`, `zotero_move_item`, `zotero_create_note`, `zotero_update_note`, `zotero_replace_attachment`.
- Delete/restore: `zotero_delete_note`, `zotero_delete_item`, `zotero_restore_item`.
- Legacy Local API only: `zotero_authorize_writes` and `zotero_import_records`.

List tools support `start`/`limit` pagination. `zotero_get_items` batches keys in chunks.

## Tests

```powershell
py -3.13 -m py_compile server.py zotero_client.py
node --check zotero-bridge/bridge-server.js
node --check zotero-bridge/bootstrap.js
py -3.13 -m unittest discover -s tests -v
git diff --check
```

Unit tests use mocks and never modify formal Zotero data. Real integration tests must use a disposable Zotero item. The live read-only checks are opt-in with `ZOTERO_MCP_LIVE=1`; Bridge write integration should be run only after pairing the extension and creating a temporary test item. The opt-in destructive MCP runner is `tests/live_bridge_smoke.py` and requires the acknowledgement `I_HAVE_A_TEMPORARY_ZOTERO_ITEM`.

## Limitations

The Bridge must be installed and Zotero must remain running for Zotero 9.0.6 writes. Group-library permissions, read-only libraries, locked files, linked URL attachments, and unsupported attachment types can still be rejected by Zotero. When the Bridge is offline, the MCP intentionally degrades instead of modifying SQLite or storage directly.

For setup details see [LOCAL-API-GUIDE.md](LOCAL-API-GUIDE.md), [zotero-bridge/PROTOCOL.md](zotero-bridge/PROTOCOL.md), and the Chinese guide [README.zh-CN.md](README.zh-CN.md).