zotero-mcp
Provides tools for searching a Zotero library, listing items and collections, fetching item details, calling the Zotero Web API v3, uploading files to existing attachment items, and embedding Zotero Word fields into .docx files for use with the Zotero Word plugin.
Click on "Deploy 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., "@zotero-mcpsearch my Zotero library for recent papers on protein folding"
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.
zotero-mcp
zotero-mcp connects AI agents such as Claude, Cursor, VS Code, ChatGPT, and Codex to your Zotero library through the Model Context Protocol (MCP). Once it is set up, you can ask an agent to:
search your library and read item details, abstracts, and formatted references;
list collections and page through their items;
create, edit, tag, or delete items through the Zotero Web API v3;
upload PDFs and other files as attachments;
turn citation markers in a Word document into real Zotero citations that the Zotero Word plugin can refresh and restyle.
You set it up once: install one command, store your Zotero user ID and API key once, and point every agent at that command.
Contents
Related MCP server: zotero-mcp
How it works
flowchart LR
agent["AI agent"] -- "MCP over standard input and output" --> server["zotero-mcp"]
server -- "reads" --> desktop["Zotero desktop app (local API)"]
server -- "reads when the app is closed, and every write" --> web["api.zotero.org"]
web -- "Zotero sync" --> desktopEach agent starts
zotero-mcpby itself whenever it needs it and talks to it over standard input and output. You never run the server by hand, and it opens no network port.Reads go to the Zotero desktop app first, at
http://127.0.0.1:23119/api. That is fast and works offline. If the app is closed, or its library has no items yet, reads usehttps://api.zotero.orginstead. Writes always go toapi.zotero.org, and Zotero syncs them back to the desktop app.The server needs two values from you: your numeric Zotero user ID and a Zotero API key. You store them once, as environment variables, in Step 2.
Before you start
You need | Notes |
Windows, macOS, or Linux | Each step shows the commands for each system. |
The Zotero desktop app, version 7 or later | Step 1 turns on the setting this server uses. |
Needed for syncing and for the API key. | |
An AI agent that supports MCP | Claude Desktop, Claude Code, Cursor, VS Code with GitHub Copilot, the ChatGPT desktop app or Codex, Google Antigravity, opencode, or another MCP client. |
Git and uv | Step 3 installs them if you do not have them. |
Microsoft Word with the Zotero Word plugin | Only for Word citations. Step 1 shows how to install the plugin. |
Running commands. Several steps use a terminal:
Windows: open PowerShell: press Start, type
PowerShell, and press Enter.macOS: open Terminal from Applications → Utilities.
Linux: open your terminal app.
Copy one code block at a time, paste it into the terminal, and press Enter. Text that starts with YOUR_, such as YOUR_USER_ID, is a placeholder: replace all of it with your own value. Example paths that contain you, such as C:\Users\you\..., stand for paths on your computer; Step 3 prints yours.
Step 1: Set up Zotero
Install Zotero. Download it from zotero.org/download, install it, and open it.
Sign in and sync. Open Zotero's settings (Edit → Settings on Windows and Linux, Zotero → Settings on macOS), choose Sync, sign in with your zotero.org account, and let the library sync. If you have no account yet, create one at zotero.org/user/register.
Turn on the local API. In the same settings window, choose Advanced and select Allow other applications on this computer to communicate with Zotero. The server then reads your library from the desktop app, quickly and without the internet.
Create an API key. Sign in at zotero.org and open Create a new private key.
Enter a name you will recognize, such as
zotero-mcp.Under Personal Library, allow library access. Allow write access only if agents should be able to create, edit, upload, or delete items.
Save the key and copy it right away. Zotero shows it only once.
Copy your user ID. The API keys page shows your user ID for API calls. It is a number, not your username.
Optional: install the Word plugin. For Word citations, open Zotero's settings, choose Cite → Word Processor Plugins, and install the Microsoft Word add-in. Restart Word; a Zotero tab appears. Installing the word processor plugin has more detail.
Treat the API key like a password: it opens your library. Never paste it into a chat, an issue, a screenshot, or a file you share.
Step 2: Store your user ID and API key
The server reads two environment variables:
Variable | Value |
| The user ID you copied in Step 1 |
| The API key you created in Step 1 |
Store them once for your user account, and every agent can find them. On Windows, the key never has to be pasted into an agent's settings. On macOS and Linux, a few apps need the values in their own config; Step 4 says which. The server does not read .env files; .env.example only lists the names.
Windows
Open PowerShell.
Save your user ID. Replace
YOUR_USER_IDwith the number, and keep the quotation marks:[Environment]::SetEnvironmentVariable("ZOTERO_USER_ID", "YOUR_USER_ID", "User")Save your API key. This command asks for the key and hides it while you paste, so the key never appears on screen or in PowerShell's history. It is one long line; copy all of it:
$key = Read-Host "Paste your Zotero API key" -AsSecureString; [Environment]::SetEnvironmentVariable("ZOTERO_API_KEY", [System.Net.NetworkCredential]::new("", $key).Password, "User"); Remove-Variable keyCheck both values. The first line prints your user ID; the second prints
True:[Environment]::GetEnvironmentVariable("ZOTERO_USER_ID", "User") [bool][Environment]::GetEnvironmentVariable("ZOTERO_API_KEY", "User")
Prefer a window to commands? Press Start, type environment variables, open Edit environment variables for your account, and add both variables under User variables.
On Windows, the server also reads these two variables directly from Windows whenever an agent does not pass them. They therefore work in every agent at once, including Claude Desktop and apps installed from the Microsoft Store, without restarting anything.
macOS and Linux
Run steps 2 to 5 in the same terminal window.
Open a terminal.
Tell the next commands which startup file your shell reads. macOS uses zsh by default:
RC=~/.zshrcMost Linux systems use bash:
RC=~/.bashrcecho $SHELLshows your shell. If you use bash on macOS, runRC=~/.bash_profileinstead.Save your user ID. Replace
YOUR_USER_IDwith the number:echo 'export ZOTERO_USER_ID="YOUR_USER_ID"' >> "$RC"Save your API key. This command asks for the key without showing it, so the key never appears on screen or in your shell history. It is one long line; copy all of it:
printf "Paste your Zotero API key: "; read -rs key; echo; echo "export ZOTERO_API_KEY=\"$key\"" >> "$RC"; unset keyLoad the new values and check them. The second line prints your user ID; the third prints
API key is set:source "$RC" echo "$ZOTERO_USER_ID" [ -n "$ZOTERO_API_KEY" ] && echo "API key is set"
Agents started from a new terminal see these values. Apps opened from the Dock or an application menu usually do not read your shell's startup file; Step 4 shows what to do for those agents.
Step 3: Install zotero-mcp
Install Git
uv downloads zotero-mcp from GitHub with Git. Check whether Git is installed:
git --versionIf that prints a version number, continue with Install uv. Otherwise, install Git:
System | Command |
Windows |
|
macOS |
|
Debian, Ubuntu |
|
Fedora |
|
Close the terminal, open a new one, and run git --version again.
Install uv
uv installs Python programs in their own environments, and downloads Python itself if needed. Check whether it is installed:
uv --versionIf that does not print a version number, install uv.
Windows (PowerShell):
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"macOS and Linux:
curl -LsSf https://astral.sh/uv/install.sh | shClose the terminal, open a new one, and run uv --version again. The uv installation guide lists other ways to install it, such as WinGet and Homebrew.
Install the server
uv tool install git+https://github.com/Jayaram-Nambiar/zotero-mcp.gitThe output includes Installed 1 executable: zotero-mcp. uv may then warn that its bin folder is not on your PATH; that is fine, because agents use the full path from the next step. Run uv tool update-shell only if you also want to type zotero-mcp in a terminal.
Check the installed version:
uv tool listIt lists zotero-mcp v1.0.2 or newer.
Copy the full path of the command
Agents start the server by its full path. Print it.
Windows (PowerShell):
Join-Path (uv tool dir --bin) "zotero-mcp.exe"This prints a path such as C:\Users\you\.local\bin\zotero-mcp.exe.
macOS and Linux:
echo "$(uv tool dir --bin)/zotero-mcp"This prints a path such as /Users/you/.local/bin/zotero-mcp. Keep the path at hand for Step 4.
Step 4: Connect your agents
Add the server to every agent you use, always under the name zotero. Set up only the agents you have; skip the rest.
Paths in config files
The examples write the command as /path/to/zotero-mcp. Replace it with the full path from Step 3, written for the kind of file you are editing:
Where the path goes | Windows | macOS |
A terminal command |
|
|
A JSON file: double every backslash |
|
|
A TOML file: use single quotes on Windows |
|
|
On Linux, the path usually starts with /home/you/ instead of /Users/you/.
Edit a JSON config file safely
Most agents keep their servers in a JSON file, in a block named mcpServers; VS Code names the block servers, and opencode names it mcp. Each agent's section below gives the commands to open and to check its file. When you add zotero:
The file is empty or new: paste the agent's whole example.
The file has other settings but no server block: keep everything that is already there. Put a comma after the last setting, then paste the server block inside the outer braces:
{ "preferences": { "example-setting": true }, "mcpServers": { "zotero": { "command": "/path/to/zotero-mcp" } } }The file already has a server block: add only the
"zotero": { ... }entry inside it, with a comma after the entry before it:{ "mcpServers": { "other-server": { "command": "other-command" }, "zotero": { "command": "/path/to/zotero-mcp" } } }
Paste the examples rather than typing them: some editors turn straight quotation marks into curly ones, which breaks JSON. After saving, run the agent's check command. If it reports an error, a comma, quotation mark, or brace is missing. If it says it cannot find the file, the path in the command is wrong, not the JSON.
Claude Desktop
Claude Desktop, the Claude chat app, reads this file:
System | File |
Windows |
|
macOS |
|
Quit Claude Desktop completely before you edit this file. Closing the window is not enough: Claude keeps running in the background and can save its own copy of the file over your change.
Claude can also show you the file: open its Settings from the Claude menu, choose Developer, and click Edit Config. Claude creates the file if it does not exist yet. Then quit Claude before you edit it.
Quit Claude Desktop.
Windows: right-click the Claude icon in the notification area at the right end of the taskbar (click ^ if the icon is hidden) and choose Quit.
macOS: choose Claude → Quit Claude in the menu bar, or press ⌘Q.
Open the file.
Windows (PowerShell). If Notepad asks whether to create the file, choose Yes:
notepad "$env:APPDATA\Claude\claude_desktop_config.json"macOS (Terminal):
mkdir -p "$HOME/Library/Application Support/Claude" touch "$HOME/Library/Application Support/Claude/claude_desktop_config.json" open -e "$HOME/Library/Application Support/Claude/claude_desktop_config.json"Add the server, following Edit a JSON config file safely.
Windows. Claude Desktop does not pass your variables to servers, but on Windows the server reads them from Windows itself, so the entry needs only the command:
{ "mcpServers": { "zotero": { "command": "C:\\Users\\you\\.local\\bin\\zotero-mcp.exe" } } }macOS. The entry has to carry your two values, because Claude Desktop does not pass your variables to servers. Keep this file private:
{ "mcpServers": { "zotero": { "command": "/Users/you/.local/bin/zotero-mcp", "env": { "ZOTERO_USER_ID": "YOUR_USER_ID", "ZOTERO_API_KEY": "YOUR_API_KEY" } } } }Save the file and check it.
Windows (PowerShell):
Get-Content "$env:APPDATA\Claude\claude_desktop_config.json" -Raw | ConvertFrom-JsonmacOS (Terminal):
python3 -m json.tool "$HOME/Library/Application Support/Claude/claude_desktop_config.json"Close the editor and start Claude Desktop again.
Check the connection: in a chat, click + (Add files, connectors, and more) at the bottom left of the message box, point to Connectors, choose Manage connectors, and select zotero to see its tools.
Claude Code
Claude Code keeps its servers in its own settings, shared by the terminal, the Code tab of the Claude desktop app, and the IDE extensions.
Open a terminal.
Add the server for all your projects, with the full path from Step 3:
claude mcp add --scope user --transport stdio zotero -- "/path/to/zotero-mcp"On Windows, for example:
claude mcp add --scope user --transport stdio zotero -- "C:\Users\you\.local\bin\zotero-mcp.exe"Check the connection:
claude mcp get zoteroshowsStatus: ✔ Connected. Inside a Claude Code session,/mcplists the connected servers.
Claude Code passes its environment to the server, so the entry needs no env block.
Cursor
Cursor reads ~/.cursor/mcp.json (%USERPROFILE%\.cursor\mcp.json on Windows) for every project, or .cursor/mcp.json inside a single project. See Cursor's Model Context Protocol guide.
Open the file.
Windows (PowerShell):
New-Item -ItemType Directory -Force "$env:USERPROFILE\.cursor" | Out-Null; notepad "$env:USERPROFILE\.cursor\mcp.json"macOS (Terminal):
mkdir -p ~/.cursor && touch ~/.cursor/mcp.json && open -e ~/.cursor/mcp.jsonLinux:
mkdir -p ~/.cursor && nano ~/.cursor/mcp.jsonAdd the server:
{ "mcpServers": { "zotero": { "type": "stdio", "command": "/path/to/zotero-mcp", "env": { "ZOTERO_USER_ID": "${env:ZOTERO_USER_ID}", "ZOTERO_API_KEY": "${env:ZOTERO_API_KEY}" } } } }${env:NAME}copies each variable from Cursor's environment when the server starts, so the key is not stored in the file.Save the file and check it.
Windows (PowerShell):
Get-Content "$env:USERPROFILE\.cursor\mcp.json" -Raw | ConvertFrom-JsonmacOS and Linux:
python3 -m json.tool ~/.cursor/mcp.jsonRestart Cursor.
Check the connection: Customize in Cursor's sidebar lists zotero with its tools and lets you turn it on or off. If it does not connect, open the Output panel (Ctrl+Shift+U, or ⌘⇧U on macOS) and choose MCP Logs.
On macOS and Linux, ${env:...} finds the values only if Cursor has them, which is usually the case only when you start Cursor from a terminal. If the server reports Unconfigured, replace the two ${env:...} references with your values, and keep the file private.
VS Code with GitHub Copilot
Open the Command Palette (Ctrl+Shift+P, or ⌘⇧P on macOS) and run MCP: Open User Configuration.
Add the server. VS Code uses
servers, notmcpServers:{ "servers": { "zotero": { "type": "stdio", "command": "/path/to/zotero-mcp" } } }Save the file. VS Code underlines any JSON error in the editor.
Check the connection: run MCP: List Servers, choose zotero, and start it if it is not running.
On macOS and Linux, if the server reports Unconfigured, add an env object with your two values to this entry, as in the macOS example for Claude Desktop, and keep the file private. The MCP configuration reference describes every field.
ChatGPT desktop app and Codex
The ChatGPT desktop app, the Codex CLI, and the Codex IDE extension share ~/.codex/config.toml (%USERPROFILE%\.codex\config.toml on Windows). See OpenAI's MCP guide.
Open the file.
Windows (PowerShell):
New-Item -ItemType Directory -Force "$env:USERPROFILE\.codex" | Out-Null; notepad "$env:USERPROFILE\.codex\config.toml"macOS (Terminal):
mkdir -p ~/.codex && touch ~/.codex/config.toml && open -e ~/.codex/config.tomlLinux:
mkdir -p ~/.codex && nano ~/.codex/config.tomlAdd these lines at the end of the file:
[mcp_servers.zotero] command = "/path/to/zotero-mcp" env_vars = ["ZOTERO_USER_ID", "ZOTERO_API_KEY"] startup_timeout_sec = 30On Windows, write the path in single quotes, for example
command = 'C:\Users\you\.local\bin\zotero-mcp.exe'.Save the file and restart the app, or start a new Codex session.
Check the connection: in the Codex CLI,
codex mcp listshows the server. In any of the apps, the check in Step 5 works too.
env_vars forwards the two variables from the app's environment, so the key stays out of the file. On macOS and Linux, the app may not have the variables, for example when you start it from the Dock. If the server reports Unconfigured, add your values below the entry instead, and keep the file private:
[mcp_servers.zotero.env]
ZOTERO_USER_ID = "YOUR_USER_ID"
ZOTERO_API_KEY = "YOUR_API_KEY"ChatGPT in a web browser cannot start programs on your computer, so it cannot use this server.
Google Antigravity
Antigravity reads ~/.gemini/config/mcp_config.json (%USERPROFILE%\.gemini\config\mcp_config.json on Windows). See Antigravity's MCP guide.
Open the file.
Windows (PowerShell):
New-Item -ItemType Directory -Force "$env:USERPROFILE\.gemini\config" | Out-Null; notepad "$env:USERPROFILE\.gemini\config\mcp_config.json"macOS (Terminal):
mkdir -p ~/.gemini/config && touch ~/.gemini/config/mcp_config.json && open -e ~/.gemini/config/mcp_config.jsonLinux:
mkdir -p ~/.gemini/config && nano ~/.gemini/config/mcp_config.jsonAdd the server:
{ "mcpServers": { "zotero": { "command": "/path/to/zotero-mcp" } } }Save the file and check it.
Windows (PowerShell):
Get-Content "$env:USERPROFILE\.gemini\config\mcp_config.json" -Raw | ConvertFrom-JsonmacOS and Linux:
python3 -m json.tool ~/.gemini/config/mcp_config.jsonRestart Antigravity.
Check the connection: Additional Options (…) → MCP Servers lists zotero and its tools.
Antigravity passes its environment to the server, so the entry needs no env block. On macOS and Linux, if the server reports Unconfigured, add an env object with your two values, as in the macOS example for Claude Desktop. Antigravity's config does not expand variable references, so write the values themselves, and keep the file private.
opencode
opencode reads ~/.config/opencode/opencode.json (%USERPROFILE%\.config\opencode\opencode.json on Windows). If you already keep your settings in opencode.jsonc in the same folder, edit that file instead. See opencode's MCP servers guide.
Open the file.
Windows (PowerShell):
New-Item -ItemType Directory -Force "$env:USERPROFILE\.config\opencode" | Out-Null; notepad "$env:USERPROFILE\.config\opencode\opencode.json"macOS (Terminal):
mkdir -p ~/.config/opencode && touch ~/.config/opencode/opencode.json && open -e ~/.config/opencode/opencode.jsonLinux:
mkdir -p ~/.config/opencode && nano ~/.config/opencode/opencode.jsonAdd the server inside
mcp:{ "mcp": { "zotero": { "type": "local", "command": ["/path/to/zotero-mcp"], "enabled": true, "environment": { "ZOTERO_USER_ID": "{env:ZOTERO_USER_ID}", "ZOTERO_API_KEY": "{env:ZOTERO_API_KEY}" } } } }opencode replaces
{env:NAME}with the variable's value when it loads the file.Save the file and check it. (A
.jsoncfile that contains comments fails this check even when it is correct.)Windows (PowerShell):
Get-Content "$env:USERPROFILE\.config\opencode\opencode.json" -Raw | ConvertFrom-JsonmacOS and Linux:
python3 -m json.tool ~/.config/opencode/opencode.jsonRestart opencode, then run the check in Step 5.
Other MCP clients
Any client that can start a local MCP server needs these settings:
Setting | Value |
Name |
|
Transport | stdio |
Command | The full path from Step 3 |
Arguments | None |
Environment |
|
Perplexity's MCP documentation covers connecting other clients to Perplexity, not starting a local server from the Perplexity app.
Step 5: Check that it works
Open a new chat in an agent you set up. In VS Code, use Copilot Chat in Agent mode.
Ask:
Search my Zotero library for <a word from a title you know>.If the agent asks for permission to use a Zotero tool, allow it.
The agent calls
search_zoteroand lists matching items. Each comes with an eight-character item key, such asABCD1234.
If the agent does not list Zotero tools, or a tool reports Unconfigured, see Troubleshooting.
Use the tools
Ask in plain language; the agent picks the tool. Some examples:
You ask | The agent uses |
"Find papers by Vaswani in my Zotero library." |
|
"List my Zotero collections." |
|
"Show the items in my Thesis collection." |
|
"Give me the abstract of item ABCD1234." |
|
"Add the tag to-read to item ABCD1234." |
|
"Attach C:\papers\smith2020.pdf to item ABCD1234." |
|
"Turn the citation markers in C:\drafts\paper.docx into Zotero citations." |
|
Tool | What it does |
| Search title, creator, or year. The |
| Page through the library, or one collection when |
| List collections. |
| Fetch one item, including its abstract and a Vancouver line. |
| Send any other Web API v3 request. |
| Upload a file onto an attachment item that already exists. |
| Replace citation markers in a |
Search results and item lists come one page at a time. To get the next page, call again with start set to the returned next_start, until next_start is null; list_zotero_collections returns your collections in one result. Item keys are eight letters or digits. Zotero documents write requests in Write Requests and File Uploads.
The tools that take a file need its full path, such as C:\drafts\paper.docx on Windows or /Users/you/Documents/paper.docx on macOS. A path that starts with ~ is not expanded.
The tools are also plain Python functions. Install the package into a Python environment (python -m pip install git+https://github.com/Jayaram-Nambiar/zotero-mcp.git), then:
from zotero_mcp.server import search_zotero
page = search_zotero("vaswani attention")
for match in page.matches:
print(match.item_key, match.title)Create Word citations
Plain text such as (Smith, 2020) or a pasted reference is not a Zotero citation: the Word plugin works with Word fields. Zotero explains how it stores them in Why do I see ADDIN ZOTERO_ITEM CSL_CITATION?. This server writes those fields for you from simple markers.
Find the item keys. Ask your agent, for example:
Search my Zotero library for "Attention is all you need" and give me the item key.Write markers in your document where each citation belongs, and one marker where the bibliography belongs:
{{zotero:ABCD1234}} {{zotero:ABCD1234+EFGH5678}} {{zotero:ABCD1234|locator=12|label=page}} {{zotero:ABCD1234|prefix=see|suffix=.}} {{zotero:ABCD1234|suppress-author=true}} {{zotero:bibliography}}ABCD1234is an item key. A plus sign joins several items into one citation.locatoradds a page or other location.labelnames it and defaults topage; other CSL labels includechapter,figure,paragraph, andvolume.prefixadds text before the citation, andsuffixadds text right after it. Spaces at the start and end of an option's value are ignored.suppress-author=trueleaves the author's name out of the citation.{{zotero:bibliography}}marks where the reference list goes.Spaces are allowed just inside the braces, as in
{{ zotero:ABCD1234 }}. Writezoteroin lowercase, directly followed by a colon. A marker that does not follow this pattern stays in the document as plain text.
Save the document as
.docx.Ask the agent to convert it, with the document's full path, for example:
Turn the citation markers in C:\drafts\paper.docx into Zotero citations using the apa style.The agent callsembed_zotero_word_fields, which writes a new file,paper.zotero.docx, next to the original, and reports how many citations it wrote. If that number is smaller than the number of citation markers, a marker was mistyped and left as plain text.Open the new file in Word, go to the Zotero tab, and choose Refresh. Refresh formats every citation and the bibliography in the chosen style. Until then, the citations show a readable stand-in, such as
1or(Smith, 2020).Change the style later with Zotero → Document Preferences.
style is a Zotero style name such as vancouver (the default) or apa, or a full https://www.zotero.org/styles/... URL. locale defaults to en-US. From Python:
from zotero_mcp.server import embed_zotero_word_fields
print(embed_zotero_word_fields(r"C:\drafts\paper.docx", style="apa"))What to know before you convert:
A paragraph that contains a marker is rebuilt from its plain text plus the new citation fields. Everything else in that paragraph is lost:
formatting such as bold and italics;
hyperlinks (their text stays);
images, footnote and endnote references, and comments;
tracked changes and the text inside them;
content controls;
other fields. A citation inserted earlier with the Zotero plugin becomes plain text.
Accept or reject tracked changes first, and put markers in plain paragraphs of text.
Markers are found in body paragraphs, table cells, and each section's main header and footer. Footnotes, text boxes, and first-page or even-page headers are not scanned.
The fields are Word fields. LibreOffice stores Zotero citations as reference marks, which this server does not write.
Citations link to your personal library through your user ID.
zotero_apican still reach a group library with agroups/GROUPID/...path. A collaborator on another Zotero account sees the embedded citation data and can restyle it, but Refresh updates the live item data only for the account that owns the user ID.
Update, pin, or remove
Update
Update to the newest version on GitHub:
uv tool upgrade zotero-mcpThis installs the newest commit on the
mainbranch. To stay on a specific release instead, pin a version.Restart your agents so that they start the new version. Quit Claude Desktop from the notification area or menu bar, as in Claude Desktop.
Run
uv tool listto see the installed version.
On Windows, a running server keeps its files open. If the upgrade stops with Failed to install entrypoint or being used by another process, quit every agent, then install again:
uv tool install --force git+https://github.com/Jayaram-Nambiar/zotero-mcp.gitPin a version
Each release has a tag, such as v1.0.2; CHANGELOG.md lists them. To install one release and stay on it:
uv tool install --force git+https://github.com/Jayaram-Nambiar/zotero-mcp.git@v1.0.2uv tool upgrade leaves a pinned version alone. To move to another release, run the same command with that release's tag. To follow the newest version again, run the install command from Step 3 with --force.
Remove
Uninstall the server:
uv tool uninstall zotero-mcpDelete the
zoteroentry from each agent's config file from Step 4, and run that agent's check command afterwards. Quit Claude Desktop before you edit its file. For Claude Code, runclaude mcp remove zotero -s user.Optional: delete the two variables.
Windows (PowerShell):
[Environment]::SetEnvironmentVariable("ZOTERO_USER_ID", $null, "User") [Environment]::SetEnvironmentVariable("ZOTERO_API_KEY", $null, "User")macOS and Linux: delete the two
export ZOTERO_...lines from your shell's startup file, such as~/.zshrcor~/.bashrc.Optional: revoke the API key on the API keys page.
Keep a single installation of the server. Agents that point at different copies run different versions.
Troubleshooting
Problem | What to do |
The agent does not list Zotero tools | Check that the command is the full path from Step 3, that the config file passes its check command, and that the agent was restarted. Quit Claude Desktop from the notification area or menu bar; closing its window does not restart it. |
Claude Desktop loses the | Claude Desktop was running while the file was edited and saved its own copy over the change. Quit it completely, edit the file again, then start it. |
A tool reports | The server found no usable user ID or API key. Repeat Step 2. On Windows the next request picks the values up. On macOS and Linux, restart the agent, or put the values in its config as its section in Step 4 describes. |
Requests fail after you replaced the API key | Store the new key as in Step 2, update any config file that holds the key itself, and restart your agents. A value an agent passes takes precedence over the stored one. |
| Close the terminal and open a new one. |
The install fails with | Install Git, as in Install Git, then open a new terminal and repeat the install. |
The upgrade fails with | An agent is still running the old version. Quit every agent, then run |
An agent seems to run an older version | Run |
Reads are slow, or say the local Zotero app is not answering | Start the Zotero desktop app and turn on its local API (Step 1). Until then, reads use zotero.org. |
Search finds nothing, but the website shows the item | The desktop app has a library that does not contain the item yet. Sync Zotero. The server does not fall back to the website for an item that the desktop library is missing, because the desktop library is the copy you are editing. |
| The API key does not allow that request. Create a key with the permission it needs, such as write access, and store it as in Step 2. |
Converting a Word document fails on Windows | Word locks open files. Close the converted document, such as |
On macOS, the agent cannot read a document in Documents or Desktop | Allow the agent's access to that folder when macOS asks, or turn it on in System Settings → Privacy & Security → Files and Folders. |
Word shows | Word is showing field codes. Press Alt+F9 (Option+Fn+F9 on a Mac), or see the field-code article. |
Zotero asks you to choose a citation style | The document has no Zotero document preferences. Run |
Refresh does not pick up a title you changed in Zotero | The user ID in the citations does not match the account signed in to the desktop app, or the item is not in that library. |
Connection status and logs
Agent | Where to look |
Claude Desktop | The log file |
Claude Code |
|
Cursor | Output panel → MCP Logs |
VS Code | MCP: List Servers → zotero → Show Output |
Other agents | The agent's own MCP settings or documentation |
For more detail, add LOG_LEVEL with the value DEBUG to the server's environment in the agent's config. The server writes its log to standard error, never to the protocol stream.
Ask for help
Open an issue with your operating system, the agent, the output of uv tool list, and the exact error message. Never include your API key, and write YOUR_USER_ID in place of your user ID, which also appears in logs.
Security
The API key is the key to your library. Use a key with only the access agents need, and leave write access off unless they must change your library.
Prefer environment variables and config references (
${env:...},env_vars,{env:...}) over pasting the key into config files. Keep any file that holds the key itself private.The server sends the key only to
api.zotero.org, as a request header. It never returns the key in a tool result and removes it from error messages.If the key leaks, revoke it on the API keys page, create a new one, store it as in Step 2, and update any config file that holds the old key.
SECURITY.md explains how to report a vulnerability and lists the server's safeguards.
Contributing
Bug reports and pull requests are welcome. CONTRIBUTING.md explains how to set up a development environment, run the tests, and prepare a change. The test suite runs automatically on Windows, macOS, and Linux for every push to main and every pull request.
Acknowledgements
Zotero is developed by the Corporation for Digital Scholarship. The local API, Web API, and Word plugin are theirs.
The Model Context Protocol defines how this server talks to agents.
Citation formatting uses the Citation Style Language.
Word documents are written with python-docx.
Disclaimer
This project is not affiliated with, endorsed by, or supported by Zotero or the Corporation for Digital Scholarship. Zotero is a trademark of the Corporation for Digital Scholarship. Use of the Zotero API is subject to Zotero's own terms and documentation. You are responsible for the API key you create and for the library changes an agent makes with it.
The Word fields follow the field format that Zotero documents and that its Word plugin reads. Refresh the document in Word before you rely on the citation style.
License
MIT. Copyright (c) 2026 Jayaram Nambiar.
Available Tools
7 toolsembed_zotero_word_fieldsEmbed Zotero citation fields in a Word documentA
Replace citation markers with Word fields the Zotero plugin can refresh.
Markers: {{zotero:ITEMKEY}}, {{zotero:KEY1+KEY2}}, {{zotero:ITEMKEY|locator=12|label=page|prefix=see|suffix=.}}, and {{zotero:bibliography}}. Open the result in Word and choose Zotero, Refresh.
| Name | Required | Description | Default |
|---|---|---|---|
| style | No | CSL style short name or style URL. Default vancouver. | vancouver |
| locale | No | Citation locale, such as en-US. | en-US |
| docx_path | Yes | Absolute path of a .docx file containing {{zotero:ITEMKEY}} markers. | |
| output_path | No | Optional .docx path to write. Empty writes a sibling named <name>.zotero.docx. | |
| insert_bibliography | No | Append a Zotero bibliography field when the document has no {{zotero:bibliography}} marker. |
Output Schema
| Name | Required | Description |
|---|---|---|
| status | Yes | |
| message | Yes | |
| style_id | No | |
| output_path | No | |
| citation_count | No | |
| bibliography_inserted | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=false and openWorldHint=true, so the agent knows this mutates state and interacts with an external environment. The description adds valuable context beyond that: the exact marker syntax that will be replaced and the requirement to open the result in Word and refresh. It does not explicitly state whether the original docx is modified, but the schema's output_path describes a sibling output file.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core action and follows with necessary marker examples. Every sentence earns its place: the marker list is essential for correct invocation, and the final refresh instruction is a required post-step. No filler is present.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With a full output schema, 100% parameter description coverage, and annotations covering mutation and open-world behavior, the description is complete enough to invoke the tool correctly. It supplies the marker syntax and the Word refresh procedure, which are the key tool-specific details an agent needs. Nothing essential is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the parameter semantics baseline is 3 even without description-level parameter detail. The description adds marker syntax relevant to the docx_path input, but it does not explain style, locale, output_path, or insert_bibliography beyond what the schema already documents.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: replacing citation markers with Word fields the Zotero plugin can refresh. It names the marker syntax and the post-processing step, making its role clearly distinct from sibling tools like search_zotero or list_zotero_items. An agent can identify the tool without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage by explaining the marker format and telling the user to refresh in Word afterward. However, it does not explicitly say when to use this tool versus alternatives or when not to use it. The intended context is inferable but not spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_zotero_itemGet one Zotero itemBRead-only
Fetch one reference, including its abstract and a Vancouver bibliography line.
| Name | Required | Description | Default |
|---|---|---|---|
| item_key | Yes | 8-character Zotero item key from a search or list result. |
Output Schema
| Name | Required | Description |
|---|---|---|
| item | No | |
| source | No | |
| status | Yes | |
| message | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, so the read-only safety profile is covered. The description adds that the response includes the abstract and a formatted bibliography line, which is a bit of extra context, but says nothing about failure behavior for a missing key or library scoping.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no waste; the verb, cardinality, and payoff of the call all arrive immediately.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return value shape need not be explained, and annotations cover the safety profile; the one required parameter is documented. What is missing is usage routing and error/not-found behavior, though the core call is understandable.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the single item_key parameter is fully documented in the schema as an 8-character Zotero key. The description adds no parameter-level meaning beyond that, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (fetch) and resource (one reference) with useful scope detail — it delivers the abstract and a Vancouver bibliography line, which separates it from the list/search siblings. It does not name a sibling explicitly, but 'one ... reference' makes the single-item retrieval semantics clear.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives no when-to-use guidance, no prerequisites, and no alternatives. It never says to call this after search_zotero/list_zotero_items to resolve an item key, nor when a list or search tool would be preferable.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_zotero_collectionsList Zotero collectionsARead-only
List collections in the user's library, following Zotero's 100-item pages.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| source | No | |
| status | Yes | |
| message | Yes | |
| collections | No | |
| total_results | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, so the safety profile is covered. The description adds genuine behavioral context beyond that by disclosing that results are returned in Zotero's 100-item pages, which tells the agent to expect paginated output.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence with no filler that front-loads the operation and resource before adding the pagination note. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be described, and there are no parameters to document. Purpose plus the pagination behavior is sufficient for an agent to call this correctly, though a note on how to traverse subsequent pages would fully close the loop.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, which is the baseline-4 case under the rubric. The description correctly implies a no-argument, library-wide enumeration with no filtering to configure.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('List') and resource ('collections') scoped to the user's library, so the agent can distinguish it from list_zotero_items or search_zotero. It does not explicitly name a sibling or contrast itself, but the resource is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied by the purpose: an agent infers this is the enumeration call for collections, but there is no explicit when-to-use statement, no prerequisites, and no mention of search_zotero as an alternative for filtered lookup.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_zotero_itemsList Zotero library itemsBRead-only
Page through bibliographic items in the library or in one collection.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Page size. | |
| start | No | Zero-based offset. Use next_start from the previous page. | |
| collection_key | No | Optional 8-character collection key. Empty lists the whole library. |
Output Schema
| Name | Required | Description |
|---|---|---|
| limit | No | |
| start | No | |
| source | No | |
| status | Yes | |
| matches | No | |
| message | Yes | |
| next_start | No | |
| total_results | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, so the safety and openness profile is covered. The description adds the paging trait ('Page through'), which is useful context, but says nothing about ordering, rate limits, or what paging cursor to use (that lives in the schema).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with zero filler; the scope constraint (library vs. collection) is stated immediately.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and annotations covering safety, the description only needs to convey purpose and scope, which it does. It is adequate, though a brief note on ordering or how it relates to search_zotero would make it fully self-sufficient.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so limit, start, and collection_key are fully documented in the schema, including the next_start paging hint. The description's 'library or one collection' only loosely echoes collection_key, adding no syntax or format detail beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb ('Page through') and resource ('bibliographic items') plus scope ('in the library or in one collection'), which is clearer than the bare title. It doesn't explicitly contrast itself with search_zotero, so the sibling distinction is only implied by 'Page through' vs. search semantics.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no statement of when to choose this over search_zotero or get_zotero_item, nor any exclusions or prerequisites. The enumeration/paging use case is only implied by the words 'Page through'.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_zoteroSearch the Zotero libraryARead-only
Search the user's Zotero library by title, creator, or year.
Attachments, notes, and annotations are omitted. Results are one page; follow next_start until it is null.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Page size. | |
| query | Yes | Title, creator, or year. Zotero quick search treats this as one phrase. | |
| start | No | Zero-based offset. Use next_start from the previous page. |
Output Schema
| Name | Required | Description |
|---|---|---|
| limit | No | |
| start | No | |
| source | No | |
| status | Yes | |
| matches | No | |
| message | Yes | |
| next_start | No | |
| total_results | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish readOnlyHint=true and openWorldHint=true, so the safety profile is covered. The description adds genuine behavioral context beyond that: attachments, notes, and annotations are omitted from results, and results are paginated via next_start until null. It doesn't mention rate limits or auth, but the added scoping and pagination details are substantive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences with no filler; the search scope is front-loaded and the exclusions plus pagination contract follow immediately. Every clause carries information an agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be described, and the description covers result filtering and pagination adequately. The only gap is routing guidance relative to the sibling list/search tools, which keeps it from a 5.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so limit, query, and start are fully documented in the schema, including the phrase-matching caveat for query and the next_start pattern for start. The description adds no parameter detail beyond restating the searchable fields, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Search the user's Zotero library") plus the searchable fields (title, creator, year), so the agent knows exactly what this returns. It does not name or contrast with the sibling list_zotero_items, which is the closest alternative, so it stops short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage via "Search ... by title, creator, or year," but gives no explicit when-to-use or when-not-to-use guidance against siblings such as list_zotero_items or get_zotero_item. An agent must infer that this is the query-driven counterpart to the listing tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
upload_zotero_fileUpload a file to a Zotero attachmentA
Store a local file on an attachment item through the Web API upload flow.
Create the attachment first with zotero_api. A new child attachment uses itemType attachment, linkMode imported_file, parentItem, and contentType. Files already stored with the same MD5 are left unchanged. The upload limit here is 100 MB.
| Name | Required | Description | Default |
|---|---|---|---|
| item_key | Yes | 8-character key of an existing imported_file or imported_url attachment. | |
| file_path | Yes | Absolute path of the file on this computer. |
Output Schema
| Name | Required | Description |
|---|---|---|
| body | No | |
| status | Yes | |
| message | Yes | |
| http_status | No | |
| last_modified_version | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint=false and openWorldHint=true. The description adds genuinely useful behavior: idempotency via MD5 dedup ('Files already stored with the same MD5 are left unchanged') and a hard 100 MB upload limit. It does not describe auth requirements or what happens on partial/failed uploads, so it falls short of a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action, then prerequisites and limits in short chunks. The enumeration of creation fields ('itemType attachment, linkMode imported_file, parentItem, and contentType') is a slight tangent belonging to the sibling tool, but overall it is tight and well ordered.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation. For a two-parameter upload tool the description covers the workflow prerequisite, the size ceiling, and the dedup rule, which is everything an agent needs to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters are already documented in the schema, making 3 the baseline. The description's mention of itemType/linkMode/parentItem/contentType pertains to the prerequisite zotero_api call rather than to this tool's own arguments, so it adds little parameter-level meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence gives a specific verb (store/upload), resource (a local file), and target (an attachment item via the Web API upload flow). It is clearly distinguishable from sibling zotero_api, which is identified as the tool that creates the attachment instead of uploading the bytes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly names the prerequisite ('Create the attachment first with zotero_api'), which routes the agent correctly between the two tools. It lacks an explicit when-not-to-use condition (e.g. what to do for non-imported_file link modes), but the context provided is clear and actionable.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
zotero_apiCall the Zotero Web APIA
Send one Zotero Web API v3 request to the configured user library.
Reads may still use the local app through the bibliography tools. This tool always uses api.zotero.org, including GET, so the response matches the account the API key can change. Create items with POST items and a JSON array. Put collection keys inside each item's collections array. Update one item with PATCH items/ITEMKEY or PUT of the full item data, and send its current version. Delete with DELETE and confirm_delete true.
| Name | Required | Description | Default |
|---|---|---|---|
| body | No | JSON object or array. Item creates are an array of item data. Include version when updating one object. | |
| path | Yes | Relative Web API path. Examples: items, items/ITEMKEY, collections, collections/KEY/items, searches, tags, fulltext, deleted, groups/GROUPID/items. | |
| query | No | Query parameters. Values must be strings. Use itemKey for a multi-item delete. | |
| method | Yes | GET, POST, PUT, PATCH, or DELETE. | |
| write_token | No | Optional 32-character token. Repeating it prevents a successful create from running twice. | |
| confirm_delete | No | Must be true for DELETE. A delete without it is refused. | |
| if_unmodified_since_version | No | Library version precondition for updates and deletes. |
Output Schema
| Name | Required | Description |
|---|---|---|
| body | No | |
| status | Yes | |
| message | Yes | |
| http_status | No | |
| last_modified_version | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint=false and openWorldHint=true; the description goes further by disclosing that DELETE is refused without confirm_delete=true, that updates must carry the current version, that write_token guards against duplicate creates, and that responses reflect the API-key account rather than the local app. It does not mention rate limits or permission scopes, but it adds substantial behavior beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action, then a compact block of method-specific instructions. The sentences are terse but a few are telegraphic ('Create items with POST items and a JSON array'), trading a little polish for density.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a generic API passthrough with an output schema present, the description covers the essentials an agent needs: auth-account binding, method/path conventions, body shape, delete confirmation, and version preconditions. Return values are covered by the output schema, so nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3, but the description adds real meaning the schema lacks: body should be an array for item creates, collection keys belong in each item's collections array, and the version must be sent when updating. That is genuine parameter guidance rather than a restatement of the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
'Send one Zotero Web API v3 request to the configured user library' names a specific verb, resource, and target, and explicitly positions it against the bibliography tools that read the local app. An agent can distinguish it from search_zotero/list_zotero_items without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description states the key routing rule (local-app bibliography tools for reads vs. this tool which always hits api.zotero.org) and spells out concrete usage patterns for POST items, PATCH items/ITEMKEY, PUT, and DELETE. It stops short of naming individual sibling tools or giving explicit exclusions, so it is clear context rather than full when/when-not guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
7 tool updates
v1.0.2- First observed
embed_zotero_word_fields - First observed
get_zotero_item - First observed
list_zotero_collections - First observed
list_zotero_items - First observed
search_zotero - First observed
upload_zotero_file - First observed
zotero_api
TDQS
Scored across 7 tools
The read tools are clearly separated by action: search_zotero (query), list_zotero_items (paginate), list_zotero_collections (collections), and get_zotero_item (single fetch). The only fuzzy boundary is zotero_api, which is a raw catch-all that can technically do everything the other tools do, but its description frames it explicitly as the Web API escape hatch, so confusion is limited.
All names use snake_case with a verb-led pattern (search_, list_, get_, upload_, embed_) plus a domain noun. The placement of 'zotero' varies (search_zotero vs list_zotero_items vs zotero_api), which is a minor cosmetic inconsistency but nothing confusing.
Seven tools is well-scoped for a Zotero library server, with dedicated read tools plus file upload, word-field embedding, and a raw API fallback. Each tool earns its place without redundancy.
Reads (search, list items, list collections, get item), file upload, and word-field embedding are covered, and zotero_api provides the create/update/delete path so there are no hard dead ends. However, first-class create/update/delete and collection management are only reachable via the raw API, leaving minor ergonomic gaps.
Maintenance
Related MCP Connectors
Zotero MCP server for Claude and ChatGPT: search, citations, safe writes, PDF passages and pages.
Academic literature search, retrieval, and private library management on top of OpenAlex.
Federated search of books and papers, BibTeX/RIS citations, open-access retrieval and reading.
Remote MCP server for full read/write access to a Zotero library
Related MCP Servers
- AlicenseBqualityDmaintenanceIntegrates with Zotero's local API to search your reference library, retrieve bibliographic details, and extract full text from PDF attachments.41MIT
- AlicenseAqualityDmaintenanceIntegrates with Zotero's local API to search, retrieve, read PDFs, and add items by DOI from your Zotero library.53MIT
- AlicenseNot gradedqualityAmaintenanceEnables managing Zotero reference libraries with full CRUD operations, collections, notes, annotations, and attachments via the Zotero API.MIT
- AlicenseAqualityAmaintenanceEnables read and write access to a Zotero library via its Web API, allowing search, create, update, and delete of items, notes, and collections.121MIT