Zotero-MCP
An MCP server that lets Claude access your Zotero library and insert live, field-based Zotero citations directly into Word documents by writing the underlying OOXML, enabling proper style updates and bibliography management without manual intervention.
README
Zotero-MCP
An MCP server that gives Claude access to your Zotero library and lets it insert live Zotero citations into Word documents — real citation fields that Zotero owns, not text that merely looks like a citation.
That distinction is the whole point of this project, so it is worth being precise about it.
The problem this solves
Ask an assistant to "cite Vaswani et al. 2017" in a Word document and you
normally get a string of characters: (Vaswani et al., 2017). It looks right.
It is also dead. Zotero does not know it exists. Change your citation style
from APA to IEEE and it does not update. Add a source and the bibliography does
not renumber. Delete a reference and nothing tells you the citation is orphaned.
What you actually want is what Zotero's own Word plugin produces: a field that stays linked to the library item. This server writes those.
Why it works this way: the Word integration problem
Zotero's Word plugin talks to Zotero over a private bridge — a COM/DLL channel
on Windows, AppleScript on macOS. It is not a public API, it is not documented
for third parties, and it is not designed to be driven by anything other than
Zotero itself. Calling the plugin's macro (ZoteroInsertCitation) opens the
interactive citation picker and waits for a human to click. There is no
supported, headless way to ask the plugin to insert a citation.
So driving the plugin was off the table. Three options remained:
| Approach | Verdict |
|---|---|
| Automate the Word plugin via COM / macros | Not viable. The insert path is interactive by design; anything built on it breaks between Word and Zotero versions. |
| Generate formatted text with a CSL processor | Easy, but produces exactly the dead citations described above. Rejected. |
Write Zotero's field codes directly into the .docx |
✅ What this project does. |
The third approach works because a Zotero citation in a .docx is not magic.
It is an ordinary Word field whose instruction text looks like this:
ADDIN ZOTERO_ITEM CSL_CITATION {"citationID":"a1b2c3","properties":{…},
"citationItems":[{"id":"…","uris":["http://zotero.org/users/123/items/ABCD1234"],
"itemData":{…CSL-JSON…}}],"schema":"…csl-citation.json"}
Plus an ADDIN ZOTERO_BIBL … CSL_BIBLIOGRAPHY field for the bibliography, and
a set of hidden ADDIN ZOTERO_PREF_1…N fields recording which CSL style the
document uses.
Reproduce those payloads exactly — the item URIs, the CSL-JSON, the schema URL, the preference blob's chunking — and Zotero cannot tell the difference. It adopts the citations as its own. Refresh, Add/Edit Bibliography and switching styles all behave normally.
No Word automation. No COM. No open Word instance. Just the file.
What this costs you (read this part)
Being honest about the trade-offs, because they are real:
- The document must be closed in Word. Word holds a lock and would
overwrite our edits when it saves. The server detects the
~$file.docxlock and refuses rather than losing your work. A.docx.bakbackup is written beside the file on every change. .docxonly. Legacy.docand OpenDocument.odtare not supported. LibreOffice uses a different field representation (ReferenceMarks); support could be added but is not there yet.- The first Refresh in Word is what makes it exact. We pre-render the visible citation text so the document reads correctly straight away, but multi-source citations, numeric styles and bibliography ordering follow rules only a full CSL processor gets perfectly right. One click of Zotero → Refresh hands that job to Zotero and normalises everything.
- Footnote styles are a known limitation. Citations are inserted into the body text. For note-based styles (Chicago notes-bibliography and similar), Zotero will not migrate an in-text field into a real Word footnote. In-text styles — APA, MLA, Harvard, IEEE, Vancouver, Nature, AMA — work properly. The server warns you when it detects a note style.
- Google Docs is not supported. It stores citations as links, a different mechanism entirely.
Architecture
Claude ──MCP/stdio──▶ zotero-mcp
├── reads ──▶ Zotero 7 local API (localhost:23119) [fast, no quota]
│ └─ falls back to ──▶ api.zotero.org
├── writes ──▶ Zotero Web API v3 [needs API key]
└── Word ──▶ .docx OOXML, direct field injection [no Word needed]
Reads prefer the local API: it is instant, unmetered, and reflects changes you have not synced yet. It is read-only, so every write goes to the Web API. If Zotero is not running, reads transparently fall back to the web.
Quick start
Requires Python 3.10+ and Zotero 7.
1. Install
Windows
git clone https://github.com/RogerAylagas/Zotero-MCP.git
cd Zotero-MCP
.\scripts\setup.ps1
macOS / Linux
git clone https://github.com/RogerAylagas/Zotero-MCP.git
cd Zotero-MCP
./scripts/setup.sh
The script finds a suitable Python, builds the virtualenv, installs the
package, creates .env, and runs a health check.
| Flag | Effect |
|---|---|
-Register / --register |
Write the server into claude_desktop_config.json (see route B below). |
-ConfigPath / --config-path |
Target a different config file. |
-Force / --force |
Rebuild the virtualenv from scratch. |
-SkipChecks / --skip-checks |
Skip the health check. |
2. Add your Zotero credentials
Fill in .env — details in Configure below — then run the script
again, or python scripts/doctor.py, until every line is green.
3. Connect it to Claude
These are the values Claude needs, whichever route you take:
| Field | Value |
|---|---|
| Command | <checkout>/.venv/Scripts/python.exe (<checkout>/.venv/bin/python on macOS/Linux) |
| Arguments | -m zotero_mcp |
| Environment | ZOTERO_MCP_ENV_FILE = <checkout>/.env |
The setup script prints them with your real paths already filled in.
Route A — the app's settings (recent builds). Recent Claude Desktop builds manage MCP servers and extensions through their own settings UI and an extensions marketplace, not through a config file. Look for Extensions or Connectors in Settings and add a local MCP server there using the values above. This is the supported route: nothing external competes for the file.
Route B — claude_desktop_config.json (older builds). Older builds read
the server list from a config file. setup.ps1 -Register writes it for you; see
Register with Claude manually for the JSON.
Quit Claude before using route B. Claude keeps that file in memory and rewrites it periodically, so an edit made while it is running is silently discarded minutes later — it looks like it worked and then quietly undoes itself. The script detects a running Claude and refuses rather than pretending to succeed.
If the entry keeps disappearing even with Claude closed, your build does not read that file at all. Use route A.
Afterwards, restart Claude and ask it to run zotero_check_setup.
A note on "starting" the server
There is no server to start. An MCP stdio server is not a daemon: Claude spawns
it on demand and talks to it over stdin/stdout, then shuts it down. So the
setup script's job is installing it and handing Claude the launch command —
after that the server starts itself whenever Claude needs it. If you launch
python -m zotero_mcp by hand it will just sit there waiting for JSON-RPC on
stdin, which is correct but not useful.
To check the server's health at any time:
.venv\Scripts\python.exe scripts\doctor.py # Windows
.venv/bin/python scripts/doctor.py # macOS / Linux
It tests each layer separately — configuration, local API, Web API, server startup — so a failure points at one specific thing.
Configure
cp .env.example .env
Then fill in .env:
- API key — create one at https://www.zotero.org/settings/keys/new.
Tick Allow library access and Allow write access. Copy it into
ZOTERO_API_KEY. - Library ID — the numeric Your userID for use in API calls shown at
https://www.zotero.org/settings/keys. Into
ZOTERO_LIBRARY_ID. - Local API (recommended) — in Zotero: Settings → Advanced → tick "Allow other applications on this computer to communicate with Zotero".
Your API key is a credential. Keep it in
.env(which is gitignored) and never paste it into a chat.
Register with Claude manually
setup.ps1 -Register does this for you. To do it by hand, add to your MCP
client configuration:
{
"mcpServers": {
"zotero": {
"command": "C:\\path\\to\\Zotero-MCP\\.venv\\Scripts\\python.exe",
"args": ["-m", "zotero_mcp"],
"env": {
"ZOTERO_MCP_ENV_FILE": "C:\\path\\to\\Zotero-MCP\\.env"
}
}
}
}
Pointing at the .env file rather than copying ZOTERO_API_KEY into the config
keeps the credential in exactly one place — a gitignored file — instead of
duplicating it into a config file that is easy to share by accident. Individual
ZOTERO_* variables still work here if you prefer them; anything already in the
environment wins over the .env file.
Claude's config lives at:
| Client | Path |
|---|---|
| Claude Desktop (Windows) | %APPDATA%\Claude\claude_desktop_config.json |
| Claude Desktop (macOS) | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Claude Desktop (Linux) | ~/.config/Claude/claude_desktop_config.json |
Restart Claude completely afterwards, then ask it to run zotero_check_setup —
it reports exactly what is configured, what is reachable, and what is missing.
Installing without the script
python -m venv .venv
.venv/bin/pip install -e . # .venv\Scripts\pip.exe on Windows
cp .env.example .env
Tools
Library — reading
| Tool | Purpose |
|---|---|
zotero_check_setup |
Diagnose configuration and connectivity. Start here. |
zotero_search |
Search by text, type, tag or collection. Returns item keys. |
zotero_get_item |
One reference in full, with its notes and attachments. |
zotero_list_collections |
Browse collections and sub-collections. |
zotero_list_tags |
Tags in the library. |
zotero_list_saved_searches |
Saved searches defined in Zotero. |
zotero_get_fulltext |
Indexed full text of a PDF attachment. |
zotero_list_libraries |
Personal library plus group libraries. |
zotero_list_item_types / zotero_item_type_template |
Valid types and their fields. |
Library — writing
| Tool | Purpose |
|---|---|
zotero_import_identifier |
Add a source from a DOI, arXiv id or ISBN. The fast path. |
zotero_create_item |
Create a reference field by field. |
zotero_update_item |
Edit fields, with version checking against concurrent edits. |
zotero_delete_item |
Move to Zotero's trash (recoverable). |
zotero_add_note |
Child or standalone note. |
zotero_add_tags |
Add tags without clobbering existing ones. |
zotero_attach_link |
Attach a URL to a reference. |
zotero_create_collection / zotero_set_item_collections |
Organise the library. |
Citations
| Tool | Purpose |
|---|---|
zotero_format_citation |
Render citation/bibliography as plain text — for email, markdown, slides. Not for Word. |
zotero_list_styles |
Browse CSL styles. |
Word — live citation fields
| Tool | Purpose |
|---|---|
word_document_outline |
Paragraph-by-paragraph map with indices. Read before inserting. |
word_insert_citation |
Insert a live ZOTERO_ITEM field. |
word_insert_bibliography |
Build the ZOTERO_BIBL field from the document's citations. |
word_set_citation_style |
Set the document's CSL style. |
word_list_citations |
Inspect the Zotero citations already in a document. |
word_remove_citation |
Remove a citation field. |
Typical session
You: Add the Vaswani attention paper to Zotero and cite it in
thesis.docxafter the sentence about sequence tasks.
What Claude does:
zotero_import_identifier("10.48550/arXiv.1706.03762")→ the reference is in your library, with proper metadata.word_document_outline("thesis.docx")→ reads the real paragraph text.word_insert_citation(..., anchor_text="…changed how we approach sequence tasks.")→ a live field appears exactly there.word_insert_bibliography("thesis.docx", heading="References").
Then you open thesis.docx in Word and click Zotero → Refresh. The
citations are Zotero's now: switch the style to IEEE and everything renumbers,
including the bibliography.
Development
pip install -e ".[dev]"
pytest
The test suite builds .docx files from scratch, so it runs without Word or
Zotero installed. The tests that matter most are in
tests/test_docx_fields.py: they assert that the field codes we emit parse
back correctly, that the preference blob chunks and reassembles the way Zotero
expects, and that saving a document leaves every package part we did not touch
byte-identical.
Layout
src/zotero_mcp/
├── server.py MCP server and setup diagnostics
├── config.py Environment configuration
├── context.py Shared client, path safety
├── zotero/
│ ├── client.py Hybrid local/web facade
│ ├── web.py Zotero Web API v3
│ └── local.py Zotero 7 local API
├── docxfields/
│ ├── ooxml.py Word field plumbing (fldChar/instrText runs)
│ ├── zotero_fields.py ZOTERO_ITEM / ZOTERO_BIBL / ZOTERO_PREF payloads
│ └── document.py High-level document operations
└── tools/ MCP tool definitions
scripts/
├── setup.ps1 One-command setup + registration (Windows)
├── setup.sh One-command setup + registration (macOS/Linux)
└── doctor.py Layer-by-layer health check
Roadmap
- File attachment upload (the three-step Zotero upload protocol)
- LibreOffice Writer field support
- Real Word footnotes, unlocking note-based styles
- Bundled CSL processor so multi-source citations render exactly before Refresh
License
MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。