zoty
zoty is a lightweight MCP server that connects AI agents to a local Zotero library, enabling search, browsing, metadata retrieval, and paper ingestion.
Search the library (
search_library): BM25-ranked keyword searches over titles, abstracts, and indexed full-text content, with optional filtering by collection or item type (e.g.,journalArticle,preprint,conferencePaper).List all collections (
list_collections): Retrieve all Zotero collections with their keys, names, parent collections, and item counts.Browse collection contents (
list_collection_items): List items within a specific collection by its key.Retrieve item metadata (
get_item): Fetch full metadata for a single item — title, creators, abstract, date, DOI, URL, tags, and collections.View recent additions (
get_recent_items): Get the most recently added items, sorted by date.Add new papers (
add_paper): Ingest a paper by arXiv ID or DOI — automatically fetches metadata, downloads the PDF, and optionally assigns it to a collection (requires Zotero desktop and the zoty-bridge plugin).
Allows for the ingestion of academic papers by arXiv ID, including automatic metadata retrieval and PDF downloads.
Supports adding research papers to a library using Digital Object Identifiers (DOIs) to automate metadata lookup and file attachment.
Connects AI agents to a local Zotero library to search items, browse collections, retrieve metadata, and manage academic papers.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@zotysearch my library for papers about reinforcement learning"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
zoty
Lightweight Zotero MCP server for AI agents.

What it does
MCP server that connects AI agents to your local Zotero library. Provides 8 tools: BM25-ranked search over titles, abstracts, and indexed attachment full text, within-item passage search, collection browsing, item lookup, BibTeX plus formatted citation export for item keys returned by search, and paper ingestion by arXiv ID or DOI with automatic PDF attachment.
Requirements
Python 3.10+
Zotero desktop running (Zotero 8 is the default target; Zotero 7 is also supported)
Zotero local API enabled: Zotero Settings > Advanced > Config Editor > set
extensions.zotero.httpServer.localAPI.enabledtotrueZoty Bridge plugin installed (for PDF attachment and collection assignment)
Add to Your Agent
Claude Code
Add from the command line:
claude mcp add zoty -- uvx zoty mcpAdd to your .mcp.json or ~/.claude/settings.json:
{
"mcpServers": {
"zoty": {
"command": "uvx",
"args": ["zoty", "mcp"]
}
}
}Codex
Add from the command line:
codex mcp add zoty -- uvx zoty mcpAdd to your ~/.codex/config.toml:
[mcp_servers.zoty]
command = "uvx"
args = ["zoty", "mcp"]Installation
Requires uv.
Run without installing (recommended for MCP setups):
uvx zoty mcpInstall persistently:
uv tool install zotyUpgrade an installed copy:
uv tool upgrade zotyIf you run zoty with uvx instead of installing it, refresh to the latest published version with:
uvx --refresh zoty --version
uvx --refresh zoty doctor
uvx --refresh zoty setupFrom a local checkout:
uv run zoty mcp
# Or install from source as a tool
uv tool install .The Python package provides the user CLI and MCP server through PyPI. The Zotero bridge plugin is distributed as a bundled XPI inside the Python package and as a GitHub Releases asset; future bridge updates are advertised through the Zotero update manifest published with each release.
PDF Reading Advice for Agents
For best results when coding agents open attachment filepaths from zoty, make sure poppler and the associated Poppler utilities are installed on the machine. In practice this usually means tools like pdftotext, pdfinfo, and pdftoppm are available on PATH.
This is especially important for Claude Code, which uses these utilities to read PDF pages efficiently. Without them, agents may still be able to open the PDF files themselves, but page extraction tends to be slower and less reliable.
Typical installs:
# macOS
brew install poppler
# Ubuntu / Debian
sudo apt-get install poppler-utilsZoty Bridge Plugin
A tiny Zotero 7/8/9 plugin that lets zoty execute JavaScript inside Zotero's privileged context. This is needed for operations that can't go through the REST API: PDF attachment and collection assignment both require writing to Zotero's SQLite database, which locks out external processes. The bridge sidesteps this by running JS inside Zotero itself.
Install the plugin
Locate the bundled XPI, download
zoty-bridge.xpifrom the latest release, or build it yourself:uvx --refresh zoty setup --download-onlyuvx --refresh zoty setupmake buildIn Zotero: Tools > Plugins, then drag
zoty-bridge.xpionto the Plugins window.Restart Zotero.
Confirm the bridge is running:
uvx --refresh zoty doctor
zoty setup is a guided, safe default flow. It checks Zotero's local API,
checks the bridge endpoint, points you at the packaged XPI, and tells you the
next concrete action. zoty setup --check is equivalent to diagnostics without
changes. Advanced local development can use zoty setup --install-profile, but
that command refuses to copy into the Zotero profile while Zotero is running.
Current bridge releases include a Zotero update manifest, so future bridge updates can be detected by Zotero after this XPI is installed.
If you upgraded to Zotero 9 with an older bridge capped at Zotero 8, Zotero may show the bridge as disabled. Install the latest zoty-bridge.xpi from the Plugins window, restart Zotero, and enable the bridge if Zotero leaves it disabled after reinstalling.
For local development only, you can also install the built XPI from the command line. Quit Zotero first, then run this from the repository root:
ZOTERO_PROFILE="$(
python3 - <<'PY'
from configparser import ConfigParser
from pathlib import Path
root = Path.home() / "Library/Application Support/Zotero"
profiles = ConfigParser()
profiles.read(root / "profiles.ini")
for section in profiles.sections():
if section.startswith("Profile") and profiles.get(section, "Default", fallback="0") == "1":
path = Path(profiles.get(section, "Path"))
print(root / path if profiles.get(section, "IsRelative", fallback="1") == "1" else path)
break
else:
raise SystemExit("No default Zotero profile found")
PY
)"
mkdir -p "$ZOTERO_PROFILE/extensions"
cp zotero-plugin/dist/zoty-bridge.xpi "$ZOTERO_PROFILE/extensions/zoty-bridge@zoty.dev.xpi"The bridge runs an HTTP server on localhost:24119 when Zotero is open. No configuration needed.
Tools
Tool | Description |
| Find which items in your Zotero library match a keyword query, ranked by BM25 over title, abstract, and indexed attachment full text, with optional plain-text snippets, attachment counts, collection filtering, collection key/name pairs, and case-insensitive item type values like |
| Find which passages within one or more known items match a keyword query, using |
| List all collections with keys, names, and item counts |
| List items in a specific collection, including collection key/name pairs on each item |
| Full metadata for a single |
| BibTeX plus formatted citation and bibliography text for a single |
| Recently added items, sorted by date, with collection key/name pairs on each item |
| Add a paper by arXiv ID or DOI with automatic PDF download and collection-scoped duplicate prevention |
Attachment payloads include linkMode as a descriptive string (imported_file, imported_url, linked_file, or linked_url) instead of Zotero's internal numeric codes.
How it works
Read operations still use pyzotero for collection/item APIs, but search now runs off a persistent sidecar index under ~/.cache/zoty/fulltext-index. zoty reads Zotero metadata from zotero.sqlite in immutable mode, reuses Zotero's extracted attachment text caches (.zotero-ft-cache) for PDF/EPUB/HTML full text, chunks that text locally, and rebuilds immutable BM25 snapshots in the background. At startup zoty loads the active snapshot synchronously if one exists, then queues a refresh when Zotero content changed.
Write operations use the Zotero connector endpoint (/connector/saveItems) to create metadata items. PDF attachment and collection assignment go through the zoty-bridge plugin, which executes JavaScript in Zotero's privileged context. The same bridge is used as a thin control plane to ask Zotero to generate missing full-text caches when needed; zoty does not add plugin-owned tables to zotero.sqlite or transfer raw attachment text through the bridge. This two-path design exists because Zotero's SQLite database uses exclusive locking -- external processes can read it (immutable mode) but not write to it while Zotero is running.
arXiv traffic is throttled internally to respect arXiv's access policy. Concurrent add_paper calls queue transparently: metadata requests serialize with a 3-second gap, and arXiv PDF downloads are rate-limited separately.
Development
make build # build zotero-plugin/dist/zoty-bridge.xpi and zoty-bridge-updates.json
make verify-build # rebuild plugin artifacts and fail if committed artifacts are stale
make test # run Python unit testsRelease authors should follow RELEASING.md. The bridge XPI and Zotero update manifest are deterministic build outputs and are checked by CI.
With Zotero running and zoty-bridge installed, run the local MCP smoke test:
uv run scripts/smoke_mcp.pyThe smoke test is intentionally not part of make test because it depends on
the local Zotero profile and library contents. See the script docstring for
environment variables that pin item/collection keys or opt into duplicate-only
add_paper testing.
License
MIT
Rate Limiting Across Sessions
zoty rate-limits arXiv traffic inside the running MCP server process. If several add_paper calls reach the same server at once, zoty queues them and drains metadata requests at arXiv-safe speed.
That limiter is not shared across separate zoty processes. If you start one zoty instance per agent, session, or editor window, each process will enforce its own limit and the combined request rate can still exceed arXiv policy.
If you expect multiple sessions to pull papers at the same time, start one long-lived zoty server and point all clients at that same instance.
Start one shared local server:
zoty mcp --transport streamable-http --host 127.0.0.1 --port 8000The shared MCP endpoint will be:
http://127.0.0.1:8000/mcpIf you want a different endpoint path:
zoty mcp \
--transport streamable-http \
--host 127.0.0.1 \
--port 8000 \
--streamable-http-path /zoty-mcpThen point every client at the same URL:
http://127.0.0.1:8000/zoty-mcpFor clients that support remote MCP servers by URL, the config should look like this:
{
"mcpServers": {
"zoty": {
"url": "http://127.0.0.1:8000/mcp"
}
}
}Avoid this pattern when multiple sessions may import papers in parallel, because it starts a separate zoty process per client:
{
"mcpServers": {
"zoty": {
"command": "zoty",
"args": ["mcp"]
}
}
}Recommended boot sequence:
Boot Zotero and make sure the Zotero connector and
zoty-bridgeplugin are available.Start one shared zoty server with
zoty mcp --transport streamable-http.Configure each agent or MCP client to connect to that existing server URL instead of launching its own copy.
Let the shared server serialize arXiv metadata lookups and rate-limit arXiv PDF downloads for everyone.
This keeps the agent-side behavior simple: tool calls may take a bit longer under load, but they will queue naturally instead of hammering export.arxiv.org.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/eric-tramel/zoty'
If you have feedback or need assistance with the MCP directory API, please join our Discord server