Local read-write control of a Zotero library — from your terminal or your AI agent. No cloud, no API key.
Project description
zotero-agent
Full local control of your Zotero library — from your terminal or your AI agent.
No cloud, no account, no API key. Your library never leaves your machine.
zot gives you full read-write control of your local Zotero library:
search, bulk-edit metadata, tag, deduplicate, import by DOI/ISBN/arXiv, export,
enrich missing fields, and summarize PDFs into notes — scriptable from the shell
or drivable by an AI agent. It ships as a CLI, an MCP server, a Claude
Code skill, and portable AGENTS.md instructions.
Why it's different. Zotero's local HTTP API is read-only, so the popular tools (zotero-mcp, pyzotero) can only write through the zotero.org web API — which needs an account, an API key, and sync. zotero-agent writes locally through a tiny token-protected bridge plugin. Local, offline, private — and it finally does the bulk metadata editing people have asked Zotero for since 2016. See the honest comparison.
flowchart BT
U(["👤 you / AI agent"])
subgraph SURF ["zotero-agent (one package)"]
direction LR
MCP["MCP server (zot mcp)"]
SKILL["agent skill"]
CLI["zot CLI"]
end
READ["Zotero local API<br/>GET /api/… — fast, read-only"]
WRITE["bridge plugin<br/>POST /zotero-agent<br/>privileged JS · token-gated"]
LIB[("your local<br/>Zotero library")]
U --> SURF
SURF -->|read| READ
SURF -->|write| WRITE
READ --> LIB
WRITE --> LIB
Works with your agent
Anything that speaks MCP or can run a shell command can drive your library:
| Tool | How |
|---|---|
| Claude Code | zot skill install (bundled skill), or claude mcp add zotero-agent -- zot mcp |
| Claude Desktop | add zot mcp to claude_desktop_config.json |
| Codex CLI | ~/.codex/config.toml MCP entry, or zot skill agents-md > AGENTS.md and it calls zot |
| Gemini CLI | ~/.gemini/settings.json MCP entry |
| Cursor | .cursor/mcp.json MCP entry |
| OpenCode / Windsurf / any MCP client | generic stdio server: command: "zot", args: ["mcp"] |
| Local models / custom agents | run the zot CLI directly (see AGENTS.md) — no MCP needed |
Per-client setup: docs/ai-agents.md · website.
Install
# 1) the CLI (pick one)
uv tool install zotero-agent # recommended
uv tool install "zotero-agent[mcp]" # + the MCP server (zot mcp)
uv tool install "zotero-agent[mcp,toc]" # + PDF outlines (zot toc)
pipx install "zotero-agent[mcp]"
brew install alex-roc/tap/zotero-agent # macOS/Linux; ships both extras
# 2) the bridge plugin in Zotero — download the XPI (this link is permanent):
# https://github.com/alex-roc/zotero-agent/releases/latest/download/zotero-agent-bridge.xpi
# Tools → Plugins → gear → "Install Plugin From File" → that .xpi
# One-click, no restart. Zotero auto-updates it from then on.
# 3) wire it up
zot init # generates a token, writes config, auto-detects your userID
zot ping # local API up? bridge answering? plugin version? userID known?
# 4) optional: the Claude Code skill (bundled in the package)
zot skill install # → ~/.claude/skills/zotero (--project for one repo)
Updating: uv tool upgrade zotero-agent (or brew upgrade zotero-agent) for the
CLI; the plugin updates itself (Zotero polls the release manifest), and zot ping
shows both versions.
Requires Zotero 7+ (tested through 9.x) running with the local API enabled
(the default), and Python 3.10+. Full guide: docs/install.md.
Use it from the shell
zot search "bolivia" --limit 10 # fast local read
zot missing abstract --collection SS5MVVB6 # items lacking a field
zot stats # library analytics
zot add doi 10.1371/journal.pmed.0020124 --pdf # import + attach an OA PDF
zot dedupe --by title --fuzzy # find near-duplicate titles
zot enrich --field doi --dry-run # fill missing DOIs from Crossref
zot apply edits.jsonl # declarative batch edit (undoable)
zot undo last # roll it back
zot tag normalize --dry-run # fold case/space tag variants
zot export "My Collection" --format bibtex --out refs.bib
zot bib ABCD1234 @smith2020 --style apa # formatted bibliography (Zotero key or citekey)
Every command takes --json for scripting. Writes refuse to run
non-interactively without --yes. Full reference: docs/commands.md.
Commands at a glance
| Group | Commands |
|---|---|
| Read / analyze | search get cite pdf collections tags export missing author stats recent bib annotations related notes lint |
| Edit / organize | add dedupe tag (add/rm/rename/purge/normalize) set move collection note |
| PDF outlines | toc (show/scan/set/auto/clear) — needs the [toc] extra |
| Batch (undoable) | apply undo enrich |
| Setup / escape | ping init skill backup sync exec mcp completion |
Batch edits are undoable
zot apply takes a JSONL script (one edit per line) and snapshots every touched
item first, so you can reverse it:
{"key":"ABCD1234","set":{"date":"2021"},"addTags":["review"]}
{"key":"@smith2020","addToCollection":"To Read","removeTags":["old"]}
zot apply edits.jsonl --dry-run # preview (runs NO JS — cannot write)
zot apply edits.jsonl # apply, snapshotting first
zot undo last # restore exactly the prior state
This is how agents do LLM-assisted cleanup safely: the model decides the values
and writes the JSONL; zot performs the writes. The CLI never calls an LLM itself.
Give a PDF a real table of contents
Zotero's reader has an Outline tab, but it can only display bookmarks a PDF
already has — and most scanned books and reports have none. zot toc builds one
and writes it into the file, so the sidebar works in Zotero and everywhere else.
uv tool install --force "zotero-agent[toc]" # the PDF engine is an extra
zot toc show ABCD1234 # what the file already has
zot toc scan ABCD1234 # what it could have, and from which evidence
zot toc auto ABCD1234 --dry-run # build one deterministically, preview it
zot toc set ABCD1234 --from toc.txt # write your own (title<TAB>page, indented)
zot undo last # restore the previous outline
Detection prefers the book's own contents page over guessing from fonts —
those titles and that nesting are the publisher's. When the contents page prints
page numbers rather than linking, zot toc maps them onto physical pages using
/PageLabels, the folios printed on each page, and a title search to confirm
each row. That matters more than it sounds: front matter is numbered i, ii, iii
and the body restarts at 1, so a single offset is wrong for half the book.
Same loop from an agent: zot toc scan --json hands over the evidence, the model
decides the hierarchy, zot toc set --from - writes it. Writes are guarded by
--yes, previewable with --dry-run, and reversible with zot undo.
Use it from an agent
Ask your agent things like "tag every abstract-less item #review and merge
duplicate titles in collection X", "fill in missing DOIs", or "summarize this
paper's PDF chapter by chapter and save it as a note." It drives zot and
follows a safe workflow (backup → sync-off → dry-run → small batch), and batch
edits stay undoable.
Security
The bridge runs arbitrary privileged JavaScript inside Zotero — deliberately,
because it's the only complete local write path. It's gated by a required token,
a browser-origin/CSRF guard, loopback-only binding, and an append-only audit log.
This is a real capability: read docs/security.md before
installing. Report issues privately via SECURITY.md.
Documentation
- Website: https://alex-roc.github.io/zotero-agent/ (quickstarts, cookbook, reference)
docs/install.md·docs/ai-agents.md·docs/security.md·docs/architecture.md·docs/commands.md- JS recipe book:
skill/references/recipes.md
Contributing / development
git clone https://github.com/alex-roc/zotero-agent.git && cd zotero-agent
./install.sh # dev shim on PATH + skill (symlinked) + XPI + zot init
python3 -m unittest discover -s tests # tests: fake Zotero server, no network
uvx ruff check src tests cli/zot scripts
uv build # wheel + sdist
bash plugin/build.sh # rebuild the bridge XPI
The core is stdlib-only; the optional surfaces ride behind extras ([mcp]
for the MCP server, [toc] for the PDF outline commands).
See CONTRIBUTING.md and CHANGELOG.md.
License: AGPL-3.0-or-later.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zotero_agent-0.5.1.tar.gz.
File metadata
- Download URL: zotero_agent-0.5.1.tar.gz
- Upload date:
- Size: 847.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
056d661a61d63200f303eeaf063d336c2f15b4f9a673c1b93ee2b62f425a5e89
|
|
| MD5 |
782f878b31fa4c70e0fa4b43ea046cdb
|
|
| BLAKE2b-256 |
768c1565bea1e92a1fa08ba5bc2e8a593a6a597f4fc601cdb3b73afa764c55f0
|
Provenance
The following attestation bundles were made for zotero_agent-0.5.1.tar.gz:
Publisher:
release.yml on alex-roc/zotero-agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zotero_agent-0.5.1.tar.gz -
Subject digest:
056d661a61d63200f303eeaf063d336c2f15b4f9a673c1b93ee2b62f425a5e89 - Sigstore transparency entry: 2256718511
- Sigstore integration time:
-
Permalink:
alex-roc/zotero-agent@aa544de74102d99194808f8110c33908c5d644aa -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/alex-roc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@aa544de74102d99194808f8110c33908c5d644aa -
Trigger Event:
push
-
Statement type:
File details
Details for the file zotero_agent-0.5.1-py3-none-any.whl.
File metadata
- Download URL: zotero_agent-0.5.1-py3-none-any.whl
- Upload date:
- Size: 102.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b3ce06b5942338dc6b249b3c34cb1e6edc614dd5d5c171b035fa6614884f6c75
|
|
| MD5 |
64b625eed05b5b49ba04b04c46a4e4ac
|
|
| BLAKE2b-256 |
29a283a728f88e90b667f1f70d8d4700ca6123cea93a78fbb814ad09dde810f0
|
Provenance
The following attestation bundles were made for zotero_agent-0.5.1-py3-none-any.whl:
Publisher:
release.yml on alex-roc/zotero-agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zotero_agent-0.5.1-py3-none-any.whl -
Subject digest:
b3ce06b5942338dc6b249b3c34cb1e6edc614dd5d5c171b035fa6614884f6c75 - Sigstore transparency entry: 2256718516
- Sigstore integration time:
-
Permalink:
alex-roc/zotero-agent@aa544de74102d99194808f8110c33908c5d644aa -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/alex-roc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@aa544de74102d99194808f8110c33908c5d644aa -
Trigger Event:
push
-
Statement type: