Skip to main content
Glama

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.

Related MCP server: zotero-mcp

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.docx lock and refuses rather than losing your work. A .docx.bak backup is written beside the file on every change.

  • .docx only. Legacy .doc and OpenDocument .odt are 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:

  1. 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.

  2. Library ID — the numeric Your userID for use in API calls shown at https://www.zotero.org/settings/keys. Into ZOTERO_LIBRARY_ID.

  3. 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.docx after the sentence about sequence tasks.

What Claude does:

  1. zotero_import_identifier("10.48550/arXiv.1706.03762") → the reference is in your library, with proper metadata.

  2. word_document_outline("thesis.docx") → reads the real paragraph text.

  3. word_insert_citation(..., anchor_text="…changed how we approach sequence tasks.") → a live field appears exactly there.

  4. 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

Install Server
A
license - permissive license
A
quality
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • The everything Zotero MCP server — Web API v3 + local API, safe writes, citations, search.

  • Remote MCP server for full read/write access to a Zotero library

  • Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer

View all MCP Connectors

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/RogerAylagas/Zotero-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server