Zoteus
Zoteus is an MCP server that gives AI clients full access to a Zotero library for search, reading, citing, importing, writing, and review workflows.
Identify the Zotero account, library access, default library, backend availability, and semantic-search health with
zotero_whoami.Search items by keyword, filters, tags, item types, collections, and full text; run semantic/hybrid search across one or multiple library indexes.
Read item records, attachments, full texts, passages with page locators, PDF outlines, and rendered PDF pages/figures as images; optionally OCR scanned PDFs.
Format citations and bibliographies in any CSL style, export items in BibTeX/RIS/CSL-JSON/CSV/etc., and write Word documents with live Zotero citation fields.
Import items by DOI, arXiv ID, ISBN, PMID, URL, bibliography file, or PDF; attach files, fetch open-access PDFs, and detect duplicates.
Create, update, trash, restore, or permanently delete items; manage collections, tags, notes, annotations, saved searches, and related links; merge duplicates safely with previews and version checks.
Build, update, pause, resume, and inspect local semantic-search indexes, with local, Ollama, OpenAI, or Gemini embeddings.
Check drafts for invented citations and misquotes, render evidence tables, and save/gather structured paper extractions with quote verification.
Support systematic review teams with screening queues, decisions as Zotero tags, PRISMA flow numbers, reviewer agreement, duplicate finding, and recent activity.
Look up external scholarly context via OpenAlex/Crossref: references, citations, related works, open-access copies, and retraction/correction notices.
Work through the running Zotero desktop app when available, falling back to the Zotero Web API for sync, groups, and offline desktop scenarios; writes are versioned and trash is reversible by default.
Allows adding papers to Zotero using arXiv identifiers, fetching metadata automatically.
Allows adding papers by DOI, fetching metadata from DOI resolution.
Supports OpenAI embeddings for semantic search within your Zotero library.
Provides scholarly context graph integration via Semantic Scholar for following scholarship.
Provides complete access to your Zotero library – search papers, add by identifier, format bibliographies, semantic search over PDFs, and write back items.
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., "@Zoteus Find papers in my Zotero library about climate change"
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.
Zoteus
A Zotero MCP server. Your whole library, inside Claude and ChatGPT.
Zoteus gives Claude Desktop, claude.ai, ChatGPT, Claude Code, Cursor and any other MCP client access to a Zotero library: search by keyword or by meaning, passages from your PDFs with page numbers, citations in any CSL style, adding items, and safe writes.
A real session, 6 September 2026, against the maintainer's own library. Tool calls are shown as a time-lapse. The full recording is on zoteus.com.
Install
For most clients there is nothing to download: the client fetches Zoteus with npx the first time it runs. New to MCP servers? Start with docs/getting-started.md.
Client | How |
Claude Desktop | Download the bundle for your system from the latest release, |
Claude Code |
|
Claude Code, as a plugin |
|
Cursor, VS Code, Zed, Codex, Gemini CLI, any MCP client |
|
claude.ai in the browser | Add a custom connector pointing at a hosted Zoteus or at your own remote instance. |
ChatGPT | Hosted Zoteus is listed in ChatGPT's plugin directory: open Zoteus in ChatGPT (or search Plugins for Zoteus), click Install plugin, and sign in with your Zotero account. No Developer mode needed. Your own remote instance is added as a custom MCP server instead, which OpenAI offers on the Business, Enterprise and Edu plans. ChatGPT only connects to remote servers, so the |
Then run the Zotero desktop app. Reads, and on Zotero 10+ writes to every library the app holds (your own and the group libraries synced into it), need no cloud key. Add a Zotero API key for sync, for group libraries the app does not hold, and for when the app is closed:
claude mcp add --transport stdio zoteus -e ZOTERO_API_KEY=xxxxx -- npx -y @oscardvs/zoteusGet a key at zotero.org/settings/keys. In the desktop app, enable Settings → Advanced → "Allow other applications on this computer to communicate with Zotero". Step-by-step for each client, with screenshots: zoteus.com/docs/connect-claude-to-zotero.
Updating a desktop-extension install. A manually installed
.mcpb(or older.dxt) does not auto-update. Turn on Check for updates in the extension settings (or setZOTEUS_UPDATE_CHECK=true) and Zoteus asks GitHub once a day, then says so in-chat viazotero_whoamiwhen a newer version exists; download the new bundle for your system and reinstall. The check is off by default.npxinstalls always run the latest published version.
Related MCP server: zotero-mcp
What it does
Zoteus exposes research tools, namespaced zotero_*, that search the library by keyword or by meaning, return passages from your PDFs with page locators, show PDF pages and figures as images, format bibliographies with citeproc-js in any CSL style, add items by DOI or arXiv id, and create, edit, tag and organize items with versioned writes and a reversible trash. When the Zotero desktop app is running, reads and writes go to it directly for every library it holds and need no cloud API key; the Zotero Web API v3 is the fallback for sync, group libraries the app does not hold, and for when the app is closed. Zoteus is written in TypeScript, runs on your machine, and is MIT licensed.
Features
Search your own library. Hybrid keyword and semantic search over titles, abstracts, creators, and tags, plus full-text keyword search inside your PDFs and notes, with the matching passage returned together with its page number. Your own notes and PDF annotations are indexed under the item they belong to, so "where did I object to this?" is a question search can answer. Set
ZOTEUS_INDEX_FULLTEXT(or passfulltext:truetozotero_index) and indexed search also covers available PDF body text within configured caps, so a claim that never made it into an abstract is still findable.Format citations. Zoteus reads the citation data in your Zotero library and formats it with citeproc-js in any CSL style from the CSL styles repository.
Add a paper by identifier. Pass a DOI or arXiv id and Zoteus fetches the metadata and files the item. This works out of the box through built-in resolvers; a Zotero translation-server extends it to ISBN, PMID, and URLs (see
docs/resolver.md).Write back. Create items, edit, tag, and organize. Writes are versioned with optimistic-locking retries, trash is reversible by default, and permanent deletion is opt-in and confirmation-gated.
Write straight to the desktop app. Adding and editing items, tags, collections, saved searches, merges, attachments, annotations, and trash and restore go to your running Zotero with no cloud API key, in your own library and in any group library the app holds. On Zotero 10+ this uses the local API behind a key you grant once ("Always Allow", stored for your user account only; what that means); on Zotero 9 and earlier, whose local API is read-only, adding items and annotations uses the connector protocol the browser extensions use. The cloud Web API is the fallback for group libraries the app does not hold, and for when the app is not running.
Annotate PDFs and attach files.
zotero_annotateadds highlights, underlines, and notes, the same objects the Zotero PDF reader creates. Quote the passage and Zoteus locates it in the PDF and anchors the annotation to the lines it occupies, wrapping and hyphenation included, so no page coordinates are needed.zotero_attach_filestores a local file or a URL as an attachment under any item.Write notes and digest your annotations.
zotero_create_note,zotero_append_to_noteandzotero_replace_notewrite Zotero notes from Markdown (headings, lists, tables, code, math), and turn[@ITEMKEY, p. 12]into real Zotero citations that open the paper and become live fields in Word.zotero_annotation_digestlists the highlights, comments, colours and tags on a paper or a whole collection, read-only, andzotero_save_annotation_digestsaves them as a note laid out like Zotero's own "Add Note from Annotations", each highlight still linked to its spot in the PDF (docs/notes.md).Check a draft for invented citations and misquotes.
zotero_check_citationsreads a Markdown, LaTeX or plain-text draft, finds every citation (pandoc,\cite, author-year, numeric, DOIs,[@ITEMKEY]), says which ones are in your library, which are ambiguous and which are not, and looks up each direct quotation in the cited PDF: found with its page, close (with the source's real wording), or not found. It is read-only and checks wording, not meaning (docs/checking-a-draft.md).Screen with a review team.
zotero_screen_itemsrecords include, exclude and maybe decisions (with exclusion reasons, per reviewer for dual screening) as Zotero tags in the group library, so they sync to the whole team and outlive Zoteus.zotero_reviewgives each reviewer a blind queue, counts decisions and disagreements with Cohen's kappa, returns the PRISMA flow numbers with a ready Mermaid diagram, and finds duplicates inside the review collection;zotero_activityshows what was added, changed and annotated in the last N days, and by whom (docs/screening-and-review-teams.md).Ground claims in the PDF.
zotero_get_fulltextreturns the relevant passage with character offsets, the nearest heading, and a page locator. When Zotero has not indexed the PDF or EPUB, it extracts the text on the fly, from the running desktop app or from Zotero's own storage folder, so a file added a minute ago is readable immediately. It also returns a PDF's table of contents (outline:true) and any page range on demand, so working through a 400-page book costs a few small calls rather than one that returns the whole book.Look at the page.
zotero_pdf_imagesrenders any page of a PDF to an image the model can see, so a figure, a table, an equation, or a scanned page with no text layer is no longer out of reach, and extracts the figures embedded in a page, likepdfimages, each with its position on the page. It draws through the same pdfjs that reads the text, so it needs nothing that is not already installed.Follow the literature.
zotero_scholarlooks up a paper's references, citing works, and related works through OpenAlex, with Crossref as a fallback, and can flag which of them are already in your library.Import a bibliography, find duplicates, merge them.
zotero_importreads BibTeX, RIS and CSL-JSON as text or a file without a translation-server, recovers a DOI or arXiv id from a PDF's first pages, and can check the library for an existing copy before saving.zotero_merge_itemsfolds duplicates into one record: it previews by default, fills only what the master lacks, unions tags, collections and relations, reparents notes and attachments, and trashes the emptied copies (seedocs/importing-bibliographies.md,docs/duplicates-and-merging.md).Open-access copies and retraction notices.
zotero_scholarreports an open-access PDF when OpenAlex knows one andzotero_attach_filecan fetch and attach it from the DOI, recording which version it is;zotero_scholaralso reports retraction and correction records from Crossref and OpenAlex, as records rather than a verdict (docs/open-access-pdfs.md,docs/retraction-notices.md).Evidence tables and Word documents. A
zotero-evidence-tableprompt pluszotero_evidence_tablerender gathered passages as a Markdown or CSV table whose quotations cannot drift from the passage they cite, andzotero_word_documentwrites a .docx whose citations are live Zotero field codes (docs/evidence-tables.md,docs/word-documents.md; refreshing the fields in Word is not yet verified).Scans, several libraries, local embeddings.
zotero_get_fulltextreads a PDF with no text layer through an optional OCR engine (docs/ocr.md); one data directory holds one search index per library andzotero_semantic_searchcan search several at once (docs/multiple-library-indexes.md); andZOTEUS_EMBEDDINGS=ollamaembeds through a local Ollama daemon with no API key (docs/ollama.md).zoteus index buildruns a headless index build from the command line.Agent support. 47 tools with structured outputs, MCP Resources and Prompts, and a generated tool tree for the code-execution-with-MCP pattern.
Structured extraction and Related links.
zotero_save_extractionkeeps fields you extract per paper (sample size, method, country, outcome) as a child note that syncs, with each source quote checked against the paper's text, andzotero_extraction_tablegathers them for a collection into one table with the gaps listed (docs/extraction.md).zotero_suggest_relatedfinds library items that cite each other through OpenAlex, andzotero_relate_itemslinks them as Related on both items without dropping existing links (docs/related-items.md).
How it works
Install with one
npxcommand, or the one-click.mcpb.Connect by running the desktop app for key-free local access, or by pasting your Zotero API key.
Ask. Your MCP client can now search, cite, add to, and organize your library.
Zoteus detects a running Zotero desktop app and talks to it directly: the key-free local API for reads (full PDFs, saved-search results, the semantic-search index build), and the app itself for writes to every library it holds (imports, edits, tags, collections, merges, attachments, annotations, trash). The cloud Web API v3 is the fallback, and it is still required for sync, group libraries the app does not hold, and writes when the app is not running. Details: docs/writing.md.
Writing to a group library. A group the desktop app holds (Zotero 10+) is written through the app with no cloud key; any other group is written through the cloud, which needs
ZOTERO_API_KEYwith read/write access to that group. Either way, the group's settings must let you edit its library. Address the group by the numericlibrary_idfromzotero_groups(library_type:"group"alone is not enough), and take collection keys from that same group. See Group libraries.
Start with one verified passage. Choose a client and connection, check search readiness, then retrieve and verify a passage. A full library index is not required. For local-only, WebDAV or scanned PDFs, see PDF availability.
Semantic search setup. The first
zotero_semantic_searchbuilds the library index in the background. On very large libraries you can also runzotero_index(action:"build") yourself, then poll action:"status" until it is done. The build pages your library through the same local-first path as every other read, so it needs no cloud API key while the desktop app is running. That covers your personal library and, on Zotero 10+, any group library the app holds; a key is needed when the app is closed, and for a group the app does not hold.
Embedding through an API on a large library. A full-text build of a 10k-item library is tens of thousands of requests, and at the default pacing the rate rides at OpenAI's tokens-per-minute ceiling whatever your tier. A rate-limited request backs off and retries rather than failing the build, and a build that still ends short keeps everything it indexed: run
zotero_index action:"build"again and it resumes, embedding only the passages that have no vector yet (action:"refresh"is the one that starts over). To pace it up front, setZOTEUS_EMBED_BATCH_SIZE=256andZOTEUS_EMBED_BATCH_DELAY_MS=8000. Seedocs/semantic-search.md.
Vector ranking is opt-in. Keyword (BM25) search works out of the box everywhere. On-device vectors need
@huggingface/transformers, which the desktop-extension bundle cannot ship (the resolved dependency tree, onnxruntime's native binaries included, is about 700 MB): install it into a directory of its own (mkdir -p ~/.zoteus-deps && cd ~/.zoteus-deps && npm init -y && npm i @huggingface/transformers) and setZOTEUS_TRANSFORMERS_PATHto~/.zoteus-deps/node_modules. Notnpm i -g: Claude Desktop runs the server on its own built-in Node, so a global install under a version manager sits next to a Node the extension never executes. When vectors are unavailable Zoteus says so inzotero_indexstatus,zotero_whoami, andzotero_semantic_searchrather than quietly returning nothing. Seedocs/semantic-search.md.
Configuration
Variable | Default | Purpose |
| none | Cloud auth (sync, groups, writes without the desktop app; optional otherwise) |
|
|
|
| none | Pre-provision the Zotero 10+ desktop write key (else granted once, in-app) |
|
|
|
| provider default | The model that provider embeds with, |
|
| Weight precision of the on-device model: |
|
| Passages per embedding call. Lower it if an API provider rejects a whole request (OpenAI answers |
|
| Pause between embedding calls. Raise it if an API provider rate-limits a large build: |
|
| Index your own child notes and PDF annotations as searchable passages |
|
| Index PDF body text for semantic search (opt-in; costly) |
|
|
|
| none | Where to find |
| none | Append every log line to this file, for a server that runs without a terminal |
|
| Must be |
|
| Adds the generic read-only |
Full table in docs/configuration.md. To run a shared or remote instance, see docs/remote-oauth.md (self-host the OAuth remote on loopback or behind your own proxy).
Documentation
zoteus.com/docs · Connect Claude to Zotero · Connect ChatGPT to Zotero · Group libraries for review teams · Zoteus and zotero-mcp, side by side
In this repository: Getting started · Configuration · Import & resolver · Importing bibliographies · Duplicates and merging · Architecture · Safe writes · Notes and annotation digests · Threat model · Citations · Word documents · Evidence tables · Semantic search · Several library indexes · Ollama embeddings · OCR · Scholarly context · Open-access PDFs · Retraction notices · Command line · Lab setup · Screening and review teams · Code execution · Deployment · Uninstall In this repository: Getting started · Configuration · Import & resolver · Importing bibliographies · Duplicates and merging · Architecture · Safe writes · Notes and annotation digests · Threat model · Citations · Word documents · Evidence tables · Structured extraction · Related items · Semantic search · Several library indexes · Ollama embeddings · OCR · Scholarly context · Open-access PDFs · Retraction notices · Command line · Lab setup · Code execution · Deployment · Uninstall
Zoteus is listed in the MCP Registry as io.github.oscardvs/zoteus, on mcpservers.org, and in the Citation Styler overview of Zotero MCP projects.
Uninstall
Zoteus writes everything it derives (the search index, the on-device model weights, the update-check cache, the granted local-API key) into one directory: ZOTEUS_DATA_DIR if you set it, otherwise your OS's default application-data path. Stop the server, remove it from your MCP client's configuration, then delete that directory; your Zotero library lives elsewhere and nothing here touches it. Full steps and platform paths: docs/uninstall.md.
Privacy
Zoteus runs on your machine or the server you configure. Usage logging is off by default. Tool results go to your chosen AI client, and enabled features contact Zotero and the scholarly, PDF, or embedding services they require. Full policy: PRIVACY.md.
Contributing
Contributions are welcome; see CONTRIBUTING.md. Zoteus is MIT licensed.
Acknowledgements
Built on the Model Context Protocol, the Zotero Web API, citeproc-js, and the Citation Style Language. Not affiliated with or endorsed by the Corporation for Digital Scholarship / Zotero.
citeproc-js implements the Citation Style Language. (c) Frank Bennett, used under the Common Public Attribution License 1.0. https://citationstyles.org/ Dependency notices are collected in THIRD_PARTY_NOTICES.md.
Available Tools
47 toolssearch_toolsDiscover Zotero toolsARead-onlyInspect
Discover the available Zotero tools by keyword — useful for progressive disclosure when you do not want to load every tool definition up front (the code-execution-with-MCP pattern). Pass an optional query (matched against tool names, titles, and descriptions) and detail ("names" or "descriptions", default "descriptions"). Returns the matching zotero_* tools so you can pick the right one for a task. With no query, returns the full catalog.
Inputs: query, detail. Returns: count, tools
zotero_activityWhat was added, changed, read and marked recentlyARead-onlyIdempotentInspect
Read-only: what was added or modified in the last days days in a library or one collection, newest first, split into items (papers and attachments), notes and annotations (highlights with their text and comments). In a group library read from the cloud, every row says who created and who last changed it, and byUser totals it per person, so a review team sees who added and screened what this week; the desktop local API does not report who made a change, so without a cloud key a group answer carries no names and says so. In your own library it is a "what did I read and mark this week" view. Zotero's "Recently Read" list is not available: it lives only in the desktop's own database and neither Zotero API exposes it. Reads go to the running Zotero desktop app when it holds the library, otherwise to the cloud Web API.
Inputs: days, limit, library_id, library_type, +1 more. Returns: items, notes, since, byUser, +7 more
zotero_annotateAnnotate a PDF (highlights, notes)AInspect
Add or delete Zotero PDF annotations (highlights, underlines, notes), the same objects you create in the Zotero PDF reader. action:"add" needs parent (a regular item key OR a PDF attachment key) and annotations: each with type (highlight|note|underline, default highlight), text (the exact passage to highlight), optional comment, color, page (0-based page index). You do not need page coordinates: give the passage in text and it is located in the PDF and anchored to the exact lines it occupies, so quoting a passage is enough to highlight it. Pass page to disambiguate a passage that repeats, or occurrence to pick among repeats; pass position ({"pageIndex":N,"rects":[[x1,y1,x2,y2],...]} in PDF points, bottom-left origin) only to place a highlight yourself. action:"delete" trashes the annotations in annotation_keys. Writes go to the running Zotero desktop app for your personal library (via its local-API writes where available, or its connector protocol) and for any group library the app holds (local-API writes, Zotero 10+), otherwise to the cloud Web API.
Inputs: action, parent, library_id, annotations, +2 more. Returns: note, failed, target, created, +4 more
zotero_annotation_digestList the annotations on a paper or collectionARead-onlyIdempotentInspect
List the reader's PDF/EPUB annotations, grouped per paper: type, colour, page label, the highlighted text verbatim, the comment, and tags, in reading order. Give item_key for one paper, or collection_key for every top-level item in a collection (paged with start/limit, sorted by title). Filter with types, colors and tags. Each paper comes with citation ("Smith, 2019"), so a synthesis can cite it as [@ITEMKEY, p. 12] in zotero_create_note. Read-only: to keep the digest in Zotero as a note, call zotero_save_annotation_digest with the same arguments, which writes it without retyping a word. Reads go to the running Zotero desktop app when it serves the library, otherwise to the cloud Web API.
Inputs: tags, limit, start, types, +5 more. Returns: items, scope, nextStart, collection, +4 more
zotero_append_to_noteAppend Markdown to a Zotero noteAInspect
Add Markdown to the end of an existing Zotero note, keeping everything already in it, including its own citations and formatting. Markdown: headings, paragraphs, bullet and numbered lists, bold, italic, strikethrough, code and fenced code blocks, links, > blockquotes, tables, horizontal rules, and $inline$ or $$display$$ math, all stored as the note editor's own HTML. Raw HTML is shown as text, not interpreted. Cite a library item with its key in brackets: [@ABCD2345], [@ABCD2345, p. 12], [see @ABCD2345; @WXYZ6789, pp. 3-5]. Each becomes a real Zotero citation (clickable in Zotero, live when the note is added to a Word document), and a key that names nothing refuses the write. note_key is the note's own key (zotero_get_item with include_children lists an item's notes). To start a new note use zotero_create_note; to rewrite one, zotero_replace_note. If someone edits the note at the same moment, it is re-read and the text appended to the fresh copy. Writes go to the running Zotero desktop app (Zotero 10+) for every library it holds, otherwise to the cloud Web API.
Inputs: markdown (required), note_key (required), cite, library_id, +1 more. Returns: target, retried, note_key, citations, +1 more
zotero_attach_fileAttach a file (PDF, snapshot) to an itemAInspect
Add a stored file attachment (e.g. a PDF full text) under an existing item. Give parent (the item key) and one of url (Zoteus downloads it, then stores it), path (a file on the machine running Zoteus), or find_oa: true (Zoteus looks the parent item's DOI up in OpenAlex and attaches the open-access PDF, if there is one). find_oa finds only copies OpenAlex already knows about, which is arXiv, PubMed Central, DOAJ journals and institutional repositories: it is not a way past a paywall, and it says so plainly when there is no free copy. The copy it finds is often the author's accepted or submitted manuscript rather than the published version, so the source, the version and the licence come back in the result and go into the attachment's title. filename and content_type are inferred when omitted. Saves through the Zotero desktop app when one is reachable (Zotero 10+ local API; you may be asked once to allow Zoteus write access, choose "Always Allow"), and otherwise through the cloud Web API, which needs ZOTERO_API_KEY with file access and uses your Zotero file-storage quota. url works on every setup including a remote/hosted Zoteus that cannot see your desktop, so prefer it over path unless the file really is on the server. Returns the new attachment key.
Inputs: parent (required), url, path, title, +5 more. Returns: oa, bytes, parent, target, +4 more
zotero_attachmentZotero attachments (files)ADestructiveInspect
Upload, download, or inspect attachment files. action: "upload" stores a file as a Zotero attachment using the full File Storage protocol (provide url to have Zoteus fetch it, or file_path for a file on the machine running Zoteus; optional parent_item to attach it under an item, title, content_type) and returns the new attachment key; "download" fetches an attachment's file to a local path (provide item_key; optional save_path, default under the Zoteus data dir) and returns the path and byte count; "info" returns an attachment item's metadata. File bytes are written to / read from disk, never streamed through the conversation. Upload and download go through the running Zotero desktop app (Zotero 10+, no cloud key and no storage quota) for your personal library and any group library the app holds, and through the cloud Web API and your file-storage quota otherwise; target (upload) and readFrom (download) say which. When Zoteus runs on a different machine than Zotero, file_path refers to the server's disk, so use url instead.
Inputs: action (required), url, title, item_key, +7 more. Returns: key, bytes, exists, target, +5 more
zotero_bibliographyServer-rendered bibliography (library items)ARead-onlyInspect
Produce a formatted bibliography for items already in a Zotero library, rendered server-side by Zotero in a CSL style (by the desktop app for a library it serves, so no cloud key is needed there; otherwise by the Web API). Provide item_keys and optionally style (a name such as "apa" or "chicago author-date", or a CSL id; unset renders Zotero's default, chicago-shortened-notes-bibliography), locale, and linkwrap. Returns XHTML. Note: this endpoint is item-only and capped at 150 items. For arbitrary CSL-JSON or items not in the library, use zotero_format_bibliography instead.
Inputs: item_keys (required), style, locale, linkwrap, +2 more. Returns: note, style, entryCount, bibliography, +1 more
zotero_check_citationsCheck a draft's citations and quotations against the libraryARead-onlyIdempotentInspect
Check a draft (Markdown, LaTeX or plain text) for invented citations and misquotations, deterministically and without changing anything. Finds every citation: pandoc [@key, p. 12; @other], Zotero item keys [@ABCD2345], LaTeX \cite/\citep/\citet/\parencite/\textcite/\autocite{a,b}, author-year (Smith, 2020), (Smith & Jones, 2020, p. 4), Smith et al. (2020), numeric [3] (through the reference list) and bare DOIs. Each cited work is looked up in the library by item key, citation key (Zotero's Citation Key field, then Better BibTeX when it runs on this machine), DOI, or author + year (+ title words from the reference list) and reported as matched (item key, title, how), ambiguous (candidates) or not in the library. A direct quotation next to a citation (in quotation marks, or a block quote) is searched for in the cited item's full text, ignoring case, whitespace, line-break hyphenation, ligatures and quote/dash styles: found (with the page: exact when the PDF was read, else pageApprox), close match (similarity and the source's actual words), not found, or no full text. Paraphrased claims are NOT checked, because nothing here reads for meaning: read the passage with zotero_get_fulltext query before asserting support. Pass the reference list as references, or leave it at the end of the draft under a "References" heading. Work is paged: max_citations per call and max_fulltexts full texts read per call; call again with start: nextStart. Reads go to the running Zotero desktop app when it serves the library, otherwise to the cloud Web API. On a hosted or cloud-only server Better BibTeX keys cannot be looked up (only the Citation Key field), and quotations can be checked only in items whose full text Zotero has synced or whose file is in Zotero cloud storage.
Inputs: draft (required), start, library_id, references, +4 more. Returns: start, notices, summary, verdict, +7 more
zotero_create_itemsCreate or update Zotero itemsADestructiveInspect
Create new items or update existing ones in a single batch (the server auto-chunks into groups of 50). items is an ARRAY of item-data objects; each object has itemType as a plain string (e.g. "journalArticle", "book", "preprint", "report") plus its valid fields, creators (each {creatorType, firstName, lastName} or {creatorType, name}), tags ([{tag}]), and collections (array of 8-char collection keys). To UPDATE an existing item, also include its key and current version; to CREATE, omit both. Every item is validated against the Zotero schema before anything is sent — if any item is invalid, nothing is written and the problems are returned. Use zotero_schema to discover valid fields/creator types for an itemType. Writes to your personal library go to the running Zotero desktop app (Zotero 10+) when it is up, otherwise to the cloud Web API (requires ZOTERO_API_KEY). When updating, take version from a read made the same way (zotero_get_item), since the desktop and the cloud number versions independently. To write to a GROUP library, pass its numeric library_id (from zotero_groups) together with library_type:"group". library_type alone is not enough, and the key needs write access to that group. Collection keys are per-library, so take them from zotero_list_collections with the same library_id.
Example:
{"items": [{"itemType": "journalArticle", "title": "The Role of Metadata in Machine Learning", "creators": [{"creatorType": "author", "firstName": "Ada", "lastName": "Lovelace"}], "date": "2024-01-15", "DOI": "10.1234/example.5678", "tags": [{"tag": "ml"}], "collections": ["ABCD1234"]}]}Inputs: items (required), library_id, library_type. Returns: failed, target, created, libraryVersion
zotero_create_noteCreate a Zotero note from MarkdownAInspect
Create a note in Zotero from Markdown: a child note under an item (parent_item), or a standalone note (optionally filed in collections). Markdown: headings, paragraphs, bullet and numbered lists, bold, italic, strikethrough, code and fenced code blocks, links, > blockquotes, tables, horizontal rules, and $inline$ or $$display$$ math, all stored as the note editor's own HTML. Raw HTML is shown as text, not interpreted. Cite a library item with its key in brackets: [@ABCD2345], [@ABCD2345, p. 12], [see @ABCD2345; @WXYZ6789, pp. 3-5]. Each becomes a real Zotero citation (clickable in Zotero, live when the note is added to a Word document), and a key that names nothing refuses the write. Zotero titles a note by its first line; pass title to put a heading there. To add to a note that exists, use zotero_append_to_note; to rewrite one, zotero_replace_note. Writes go to the running Zotero desktop app (Zotero 10+) for every library it holds, otherwise to the cloud Web API (requires ZOTERO_API_KEY with write access).
Inputs: markdown (required), cite, tags, title, +4 more. Returns: key, failed, target, version, +3 more
zotero_delete_itemsPermanently delete Zotero itemsADestructiveInspect
PERMANENTLY and IRREVERSIBLY delete items by key (this purges them; it is NOT the trash). Prefer zotero_trash_items, which is reversible. This tool is disabled unless the server is started with ZOTEUS_ALLOW_DELETE=true, and additionally requires confirm: true on every call. For the personal library, and for a group library the running Zotero desktop app holds, it goes through that app when it supports local-API writes (Zotero 10+), otherwise the cloud Web API. The current library version is used as a precondition; the operation auto-chunks to 50 keys per request.
Inputs: item_keys (required), confirm, library_id, library_type. Returns: count, target, deleted
zotero_evidence_tableRender an evidence tableADestructiveInspect
Render rows of retrieved evidence as a Markdown or CSV table, deterministically, from passages you already retrieved with zotero_get_fulltext and zotero_semantic_search. This tool does NOT search and does NOT read the library: it formats what you pass it and echoes each row's quotation, locator and coverage back unchanged. It counts the coverage summary from the rows rather than taking your word for it, and it warns, naming the row, when a row contradicts its own evidence (a page locator with no quotation, coverage claiming a retrieved passage with no quotation, a quotation on a source marked unavailable, or any support verdict other than "unverified" on a row whose coverage is not a retrieved passage); it warns and still renders, and never rewords a cell. One byte is added and only in CSV: a cell whose text begins with =, +, -, @ or a tab is a formula to Excel, LibreOffice and Sheets, so the CSV writes it with a leading apostrophe, the spreadsheet's own marker for literal text, which the spreadsheet consumes so the passage still displays exactly as it was retrieved; every such cell is named in warnings, and a plain number like a page locator of -5 is left alone. Markdown output carries no such marker. Use the zotero-evidence-table prompt to gather the rows first. Each row records one study against one question: the finding, the verbatim quotation that supports it, the page locator, whether that locator is exact or approximate, how well the source was covered (a passage was retrieved, only the abstract was available, or nothing was), and the support status. Returns the rendered table as text; pass save_path to also write it to a file.
Inputs: rows (required), question (required), format, overwrite, +1 more. Returns: table, format, savedTo, coverage, +4 more
zotero_exportExport Zotero itemsARead-onlyInspect
Export items in a bibliographic format and return the raw text. Choose format (bibtex, biblatex, better-biblatex, ris, csljson, csv, mods, tei, coins, rdf_*, refer, wikipedia, bookmarks). Stock formats are rendered by Zotero itself: by the desktop app when it serves the selected library (no cloud key needed), by the Web API otherwise. biblatex is Zotero's STOCK translator; BBT-specific options (citation-key generation, sentence-case, biblatexExtendedNameFormat, unicode→LaTeX) are NOT available there. better-biblatex uses the local desktop Better BibTeX plugin (your configured BBT export options apply) and is only available when desktop Zotero + BBT are running; it degrades to built-in biblatex otherwise. Narrow with item_keys, collection_key, q, or item_type. A limit (default 50) is always applied. An export that renders no entries says so instead of returning a blank body: named item_keys that render none are an error, and any other selection that renders none comes back with empty: true. For styled human bibliographies use the bibliography tools.
Inputs: format (required), q, limit, item_keys, +4 more. Returns: text, empty, format, length, +3 more
zotero_extraction_tableGather saved extractions into one tableARead-onlyInspect
Read every extraction saved with zotero_save_extraction for a collection, for given items, or for the whole library, and lay them out as ONE table: a row per paper, a column per field. Read-only. Returns the rows as structured data (each value with its saved quote, page and quote check), plus the same table as Markdown and as CSV text (each field gets value, quote, page and quote-check columns there, so a spreadsheet can filter on them). Gaps are listed, not hidden: missingItems names papers in the collection or in item_keys with no extraction saved under this schema, and missingFields names, per paper, the columns it has no value for. Values whose quote was NOT found in the paper's full text are marked "[quote not found]" in the Markdown. Omit schema when the library holds only one; otherwise the refusal lists the schemas found. Child notes are found by their "zoteus-extraction" tag, so a note whose tag was removed is not read.
Inputs: fields, schema, item_keys, library_id, +2 more. Returns: csv, rows, schema, columns, +7 more
zotero_format_bibliographyFormat a bibliography (citeproc / any CSL style)ARead-onlyInspect
Render a formatted bibliography in any CSL style using citeproc-js — no Zotero library write required. Provide either items (an array of CSL-JSON objects, e.g. from zotero_import or external metadata) or item_keys (library items, which are exported to CSL-JSON first). Choose style (a name like "APA 7th" or a CSL id; default "apa"), locale (default "en-US"), and format (html/text/rtf; default html). The formatted bibliography text is returned. Use this for arbitrary items or styles; for items already in the library you can also use zotero_bibliography (server-rendered).
Inputs: items, style, format, locale, +3 more. Returns: entries, styleId, entryCount, bibliography
zotero_fulltextAttachment full-textADestructiveInspect
Not a search: to find which items contain a term, use zotero_search_items with qmode=everything. This reads, sets, or tracks one attachment's already-extracted full text by key. action: "get" returns the indexed text content plus indexing stats for an attachment item (only attachment items have full text; returns found:false if none); "set" stores extracted text for an attachment (provide content and the indexing counts); "since" returns the map of attachment keys whose full text changed after a given library version (useful for incremental indexing). Only attachment items support full text. All three go through the running Zotero desktop app when there is one (no cloud key needed; "set" needs Zotero 10+ and writes your personal library or a group library the app holds), otherwise the cloud Web API.
Inputs: action (required), since, content, item_key, +6 more. Returns: count, found, length, target, +7 more
zotero_get_fulltextGet attachment full text / passages / outline (read-only)ARead-onlyInspect
Retrieve an item's PDF or EPUB text for grounding. Pass a parent item_key (its best PDF/EPUB attachment is resolved automatically) or an attachment key. With query, returns the top relevant passages with locators (char offsets, nearest section, and a page); with page_range (e.g. "3-7"), returns just those pages, re-extracted from the PDF so the span is exact; with outline:true, returns the PDF's table of contents with page numbers (the cheapest way to decide which pages to read next); with none of them, returns a truncated head. Text comes from Zotero's full-text index when available; when the attachment is NOT indexed yet, the file itself is read and parsed on the fly (fallback, on by default; set fallback:false to disable), so a PDF added minutes ago still returns text (marked fulltextSource:"pdf" or "epub", with fileSource saying where the bytes came from). The file is read from the running Zotero desktop app, else straight out of the local Zotero storage folder, else downloaded from Zotero cloud storage. Page numbers are exact whenever the PDF was parsed, and otherwise an estimate (pageApprox) unless precise_pages:true. Read-only; the indexed text is served by the running Zotero desktop app when there is one, otherwise by the cloud Web API. Use this to cite a claim with a page after finding an item via zotero_search_items / zotero_semantic_search. A PDF with no text layer is reported as what it is, with its page count and what would read it, instead of failing vaguely, and one whose text layer covers only some pages says which pages lack one; ocr:true reads the pages that have no text layer by rendering them and recognising the text (a few a call), but only where the operator enabled OCR and installed the engine, and that text is a machine reading of a picture, never saved and never indexed. Text is all this returns: for a figure, a table, an equation or a scanned page with no text layer, zotero_pdf_images renders the page (or extracts the embedded figures) as images you can look at.
Inputs: item_key (required), ocr, query, outline, +7 more. Returns: mode, text, title, notice, +19 more
zotero_get_itemGet a Zotero itemARead-onlyInspect
Fetch one item by its key, returning the full item record (itemType, all bibliographic fields, creators, tags, collections, relations, version). Optionally set include_children to also return the item's child notes and attachments. Use include to additionally request rendered output: "bib" (formatted bibliography entry), "citation" (inline citation), or "csljson" (CSL-JSON for downstream formatting); combine with style (a style name such as "apa" or "chicago author-date", a CSL style id such as chicago-notes-bibliography, or the URL of a CSL file; unset renders Zotero's default, Chicago shortened notes and bibliography) and locale. When the desktop app serves the request, a style it has installed is used as-is and an unknown one is fetched from the Zotero style repository. The returned version is required if you later update or delete this item.
Inputs: item_key (required), style, locale, include, +3 more. Returns: item, children, provenance
zotero_groupsList Zotero groupsARead-onlyInspect
List the group libraries this server can reach, with each group's id and name. Use a returned group id with the library_id/library_type:"group" parameters of other tools to operate on that group library; library_type alone does not address a group. With a cloud API key each group the key can access is listed with its type, item count, description and edit permissions, plus canWrite: whether this key may write to that group, decided from the key's own access map without sending a write, and writeBlockedReason naming the remedy when it may not. Without a key the list falls back to the group libraries a running Zotero 10+ desktop app holds, which are exactly the groups still readable, key-free, from that app: those rows carry id, name, description and the desktop's own item count, and no type, edit permissions or canWrite, because the desktop does not store them. On every row numItems counts ALL the items the library holds, child attachments, notes and annotations and the trash included, so it is larger than a zotero_search_items count and is not a number of papers; numItemsNote says how to count top-level items instead. Where both are available every row says which it came from, in source: "cloud", "local", or "both" for a group the key can see and the desktop also holds. Rows also carry indexed: whether this data directory holds a search index for that group, which is what makes it searchable by meaning. Each library gets its own index file, so several rows can be true, and a false row becomes true after zotero_index action:"build" library_type:"group" library_id:. Writing to a group always goes through the cloud, even when the Zotero desktop app holds that group, and needs a key with write access to it; libraryEditing says whether the group itself lets ordinary members edit its library.
Inputs: no named parameters. Returns: note, groups, numItemsNote
zotero_importImport items by identifier, URL, bibliography file or PDFAInspect
Resolve bibliographic metadata to Zotero item-data and optionally save it to your library. action: "by_identifier" resolves a DOI, ISBN, PMID, arXiv id, or ADS bibcode (set identifier). action: "by_url" scrapes a web page (set url) and may return multiple choices to pick from. action: "by_file" imports a BibTeX, RIS or CSL-JSON bibliography: set text with the contents or path with a local file. Those three formats are parsed by Zoteus itself, so they need no translation-server, no Docker and no network; a reachable translation-server is used first when there is one, because it covers more formats (EndNote XML, MODS, RDF). action: "by_pdf" recovers metadata for a PDF: set path for a file on the machine running Zoteus, or attachment_key for a PDF already in the library (the only variant a hosted server can reach). It extracts the text of the first pages, looks for a DOI or an arXiv id, and resolves that; the result says which page the identifier was on, what introduced it, and whether the match was a labelled one or a bare string, because a first page often carries DOIs belonging to other works. It does not read scanned pages (no text layer means no identifier) and it never invents metadata: when nothing is found it says so. Set save_to_library:true (and optionally collection_key) to persist the resolved items, saved into the running Zotero desktop app when available and otherwise via the cloud Web API (requires ZOTERO_API_KEY); without it the metadata is returned and nothing is written, which makes a file import a free preview of what would be created. When a Zotero translation-server is reachable (ZOTEUS_TRANSLATION_SERVER_URL, default http://127.0.0.1:1969) it is the primary path for identifiers and URLs; with none running, DOI and arXiv ids fall back to built-in resolution (OpenAlex/Crossref and the arXiv API), and the result then carries a source field ("scholar" or "arxiv"). ISBN/PMID/bibcode and web URLs require a translation-server (a hosted Zoteus has none unless its operator attached one); without one the call fails with an error that names what the identifier was recognised as and what works instead. A save checks your library for the same work first (on by default with save_to_library; set check_duplicates:true to check a preview too): matching items are reported under duplicates (matched on normalised DOI, then ISBN, then normalised title plus year, all exact comparisons rather than similarity; a title match with no year on one side needs a title of at least four words or a creator surname both records share, and says so), and a save that would add a second copy is refused unless you pass allow_duplicate:true (which saves and still reports the match) or check_duplicates:false (which skips the scan). The scan reads up to 5000 top-level items, 100 per request; when it stopped early it saves and says so in the answer rather than refusing, so read duplicateScan.complete before treating "no match" as "no". To fold an existing pair of records together instead, call zotero_merge_items.
Inputs: action (required), url, path, text, +13 more. Returns: note, count, items, saved, +24 more
zotero_indexBuild the semantic search indexADestructiveInspect
Manage the local hybrid-search index used by zotero_semantic_search. Every job runs in the background on the server, so this tool returns immediately and never blocks on large libraries. THREE write actions, and picking the right one matters: action: "update" is the cheap one and should be the default for a library that is already indexed; action: "build" and action: "refresh" both rebuild the WHOLE index, which on a large library means many minutes and, with an API embedding provider, real spend (they differ in one thing: build resumes an interrupted build, refresh always starts over). action: "build"/"refresh" pages the library's top-level items (100-at-a-time, stopping at the server's item cap, ZOTEUS_INDEX_MAX_ITEMS, default 5000, or at a smaller limit if one is given), indexes their text (title, abstract, creators, tags) for BM25 keyword search and, if an embedding provider is configured, for vector search, persisting partial progress atomically as it goes; use it for the first build, after changing the embedding model, or to widen a previously capped build. It is ALSO the repair: if the index cannot be read at all, only action:"build" clears it, by deleting the unreadable file and opening a fresh one before rebuilding (nothing repairs it at startup or inside a query). action: "update" instead fetches only the items changed since the version the index recorded (Zotero's ?since=), re-chunks and re-embeds just those, and removes items the library no longer holds (diffed from a cheap keys-only ?format=versions census, since the deletion log is cloud-only); untouched items are never re-embedded, so adding a handful of items costs seconds instead of a full rebuild. Update falls back to a full rebuild by itself, and says so in updateNotice, when a delta would be wrong: no version stamp recorded yet, the library is now served by a different Zotero API (the desktop app and the cloud number their versions independently), or the embedding model changed. An update ALSO asks Zotero's full-text index what it has extracted since the build (that is a separate version sequence from item versions, so a PDF Zotero extracted when it was first opened changes no item version and appears in no delta) and indexes the new body text for items nothing else touched; on a library where nothing was extracted, that costs one request. A build or update interrupted by action:"stop", a crash or a restart leaves a checkpoint, and action: "build" RESUMES from it: the items already committed stay searchable and are never re-fetched or re-embedded, and only work since the last save is redone (resumedFrom on the status reports how many were inherited). action: "refresh" is the one that always starts over. A build also indexes the reader's OWN words by default: every child note, and every PDF annotation (its highlighted passage and its comment), as extra passages carrying the parent item's key — so zotero_annotate writes text that search can then find, an item with forty annotations still takes one result slot, and a hit whose snippet came from one is marked source:"note" or source:"annotation". That corpus is one paged crawl of hand-written text, orders of magnitude smaller than attachment bodies; turn it off with own_words:false or ZOTEUS_INDEX_OWN_WORDS=false. An action:"update" keeps it current for the cost of one request when nothing was written: notes and annotations are ordinary items carrying ordinary versions, so an edit, an addition and a deletion are all found by comparing the library's note/annotation keys against the ones the index holds — which is also how an index built before this existed fills its gap, once, on its first update. Set fulltext:true to ALSO index the body text Zotero extracted from each item's attachments, which is what makes semantic search match a claim buried in a PDF rather than only its title and abstract; it is off by default because it multiplies build time and index size (default cap: 40000 characters per item, tunable with fulltext_max_chars), and only attachments Zotero has already extracted are available. That pass used to be refused inside Claude Desktop, where a build that reached it killed the server process partway through with no error at all (#37); the cause was the on-device embedding model asking Electron's allocator for a block it will not serve, so the server now embeds fewer passages per call there and the build runs to completion. It is somewhat slower inside the app than in a terminal and produces exactly the same index, so a user who wants the fastest possible first build can still run one headlessly against the same ZOTEUS_DATA_DIR and let Desktop read the result. A build runs in TWO passes and reports which one it is on as phase: every item's metadata is indexed first, across the whole library, and only then are attachment bodies crawled (fulltextItemsScanned of fulltextItemsTotal). So the library is fully searchable on titles, abstracts, creators and tags long before a full-text crawl that can run for hours finishes — tell the user they can search already rather than asking them to wait for state:"done". Start a job, then POLL action: "status" every few seconds until state is "done" (or "error"); calling build or update again while one is running just returns current progress. action: "status" reports state (idle|building|done|error), operation (build|update), phase (metadata|fulltext), fetch/embed progress, itemsRemoved, index size, the active embedder, libraryVersion/libraryBackend (the version stamp an update diffs from), fulltextVersion (how far into Zotero's separate full-text sequence the index has read), fulltextPartial (present when the index's body text was gathered over an attachment map that never reached the end of the library, which is usually why that cursor is 0, though a delta can damage coverage an earlier pass had already earned a cursor for: an item holding body passages may still be missing an attachment's text, and the next update asked for full text re-reads every item Zotero's full-text census names, once, which on a large library costs a whole body crawl), resumedFrom (items inherited when a build resumed an interrupted one), itemsTotal/itemsAvailable (which differ, with a warning, when the cap stopped the crawl short of the library), ownWordsItems/ownWordsPassages (the notes and annotations indexed, with ownWordsReason if they could not be read), and (when full text was requested) fulltextItems/fulltextPassages plus fulltextReason if it produced nothing, or if an update could not read part of the body text and therefore withheld its version stamp (or its full-text cursor) so the next update retries. It also reports localApiDegradedAt when the job saturated Zotero's local API and the whole session fell back to the Zotero Web API: that fallback works, so nothing errors, but the Web API is slower and rate-limited and the rest of the build takes far longer than its start suggested, so tell the user rather than letting them watch an unexplained slowdown (the crawl also backs off to one attachment at a time by itself, to let the app recover). It reports where the index is stored (storage: sqlite or memory, set by ZOTEUS_INDEX_BACKEND), storageNotice when opening that store imported or refused an older JSON index, persistError when the index could not be written to disk at all, and how the last semantic query ranked vectors (vectorScan: "codes" for the two-stage path, "exact" for a full scan of every vector, with vectorScanNotice when that needs explaining). When the embedding provider is a paid API with a tokens-per-minute limit (ZOTEUS_EMBEDDINGS=openai or gemini), status also reports embedRate: the batch size, the pause between requests, the estimated tokens per request and the tokens per minute the build is actually sustaining, plus passagesWithoutVectors when the index holds passages nothing has embedded yet. A build whose embedder was rate-limited to a standstill keeps every passage it indexed and stays RESUMABLE: tell the user to run action:"build" again, which embeds only the passages that have no vector and re-fetches nothing, and NOT action:"refresh", which starts the whole crawl over and pays for every vector a second time. A rate-limited request already backs off and retries by itself; if a build reports it is riding the provider's tokens-per-minute limit, the fix is ZOTEUS_EMBED_BATCH_DELAY_MS (with ZOTEUS_EMBED_BATCH_SIZE), not a smaller library. action: "stop" cancels a running job (partial data is kept and stays searchable; a stopped update leaves the version stamp untouched so the next one repeats the delta, and a stopped build leaves a checkpoint the next action:"build" resumes from). stop is a one-shot cancel: the next action:"build" picks the checkpoint straight back up. action: "pause" is the durable form: it stops a running job the same way AND persists a hold that survives restarts, so build, refresh, update and zotero_semantic_search's automatic first build all refuse until action: "resume" clears it (queries keep working on what is indexed). resume clears the hold and starts nothing by itself, so follow it with build to continue a checkpoint or update for a delta; status reports paused. A partially built index is always usable for keyword search. Local embeddings are CPU-bound (see ZOTEUS_EMBEDDINGS), so large builds take a while: poll status rather than retrying build. ONE INDEX FILE HOLDS ONE LIBRARY, and library_type/library_id therefore pick the FILE, not just the crawl: naming a group builds, updates and reports THAT group's own index, kept beside the personal library's, and the two never mix (passage ids carry no library and Zotero item keys repeat across libraries, so one store holding two would alias them). Omitting them means the library this server is configured for. action: "libraries" lists every library that has an index in this data directory with its passage/item/vector counts, its stamp and where its file is; it starts nothing and creates nothing, and neither does action:"status" for a library that has no index yet (it says so instead). Only a bounded number of indexes are held open at once (ZOTEUS_INDEX_MAX_OPEN, default 4); the least recently used is saved and closed to make room, which costs a reopen and nothing else. To search across several libraries at once, use zotero_semantic_search's libraries argument, which fans out over their indexes and labels each hit with the library it came from.
Inputs: action (required), limit, fulltext, own_words, +3 more. Returns: items, phase, state, paused, +25 more
zotero_list_collectionsList Zotero collections (read-only)ARead-onlyInspect
List collections in a Zotero library (key, name, parent collection key, item count), sorted by name. Read-only: available even in read-only mode (unlike zotero_manage_collections, which also writes). Every collection in the library is read, then q filters by name and start/limit page the result; totalResults says how many there are, so a large library is never mistaken for its first page. Use the keys to scope zotero_search_items (collectionKey) or zotero_tag_audit (scope.collection_keys).
Inputs: q, top, limit, start, +2 more. Returns: start, complete, collections, totalResults
zotero_list_tagsList Zotero tags (read-only)ARead-onlyInspect
List tags in a Zotero library with their usage count and whether each was auto-applied by Zotero. Optional q substring filter and limit. Read-only: available even when the connector runs in read-only mode (unlike zotero_manage_tags, which also writes). For taxonomy hygiene use zotero_tag_audit. Served by the running Zotero desktop app for any library it holds, so it needs no cloud API key.
Inputs: q, limit, library_id, library_type. Returns: tags, totalResults
zotero_manage_collectionsManage Zotero collectionsADestructiveInspect
List, create, rename, reparent, or delete collections, and move items into or out of a collection. Set action to one of: "list" (collections with key/name/parent, sorted by name; q filters by name over the whole library, start/limit page, totalResults counts them all), "create" (needs name, optional parent_collection key; omit it for top-level), "rename" (needs collection_key + name), "reparent" (needs collection_key; parent_collection key, or omit to move to top level), "delete" (needs collection_key), "add_items" / "remove_items" (need collection_key + item_keys; collection membership lives on each item). Writes to your personal library go to the running Zotero desktop app (Zotero 10+) and otherwise to the cloud Web API; group libraries always write to the cloud. When the server sets a bulk-write threshold (ZOTEUS_CONFIRM_BULK_WRITES, off by default), removing more items than that from a collection in one call also needs confirm: true.
Inputs: action (required), q, name, limit, +7 more. Returns: start, failed, target, created, +7 more
zotero_manage_tagsManage Zotero tagsADestructiveIdempotentInspect
List tags, or add/remove tags on items. Set action to "list" (returns library tags; supports q substring filter), "add" (add tags to each of item_keys), or "remove" (remove tags from each of item_keys). Tags are stored on the parent item's tag array, so add/remove edits the items: in your personal library through the running Zotero desktop app (Zotero 10+) when it is up, otherwise through the cloud Web API; group libraries always write to the cloud. Tag names are case-sensitive. When the server sets a bulk-write threshold (ZOTEUS_CONFIRM_BULK_WRITES, off by default), editing more items than that in one call also needs confirm: true.
Inputs: action (required), q, tags, limit, +4 more. Returns: tags, failed, target, updated, +1 more
zotero_merge_itemsMerge duplicate itemsADestructiveInspect
Merge one or more duplicate items into a master item: fields the master is MISSING are filled from the duplicates, their tags, collections and relations are unioned onto it, their child notes and attachments are reparented to it, and the emptied duplicates are moved to the trash (recoverable). A field the master already has is never overwritten, and nothing is ever deleted outright. PREVIEWS BY DEFAULT: with dry_run unset or true nothing is written and the answer is the exact plan, field by field, so the caller can see what would change before it changes. Pass dry_run:false to execute. The merge runs on the running Zotero desktop app (Zotero 10+, no cloud key needed) for your personal library and any group library the app holds, and on the cloud Web API (ZOTERO_API_KEY with write access) otherwise; the preview reads the same copy the write will act on, so the two cannot describe different merges, and says which (readFrom). The preview also reports versions (the master's, each duplicate's and each child's): pass that block back as expect_versions with dry_run:false and the write runs only if every record is still at that version, otherwise NOTHING is written and the answer is the plan as it now stands, as a dry run, with changed naming what moved. Without expect_versions the write plans from the records as they are at call time. If the master changes between the read and the PATCH inside one call, the plan is rebuilt against the record as it then is rather than replayed, and the answer says so (replanned), because replaying it would overwrite exactly what changed. Find candidates with zotero_import check_duplicates:true, or by comparing records yourself with zotero_search_items and zotero_get_item. The master keeps its own key, so citations already pointing at it stay valid; the duplicates keep theirs in the trash until it is emptied, and the master records a dc:replaces relation naming each item it absorbed, which is the same relation Zotero's own merge writes. Put a duplicate back with zotero_trash_items action:"restore"; to edit one item rather than fold two together, use zotero_update_item.
Inputs: master_key (required), duplicate_keys (required), confirm, dry_run, +3 more. Returns: note, plan, dryRun, failed, +8 more
zotero_pdf_imagesLook at PDF pages and figures as images (read-only)ARead-onlyInspect
See a PDF the way a reader does. Text extraction (zotero_get_fulltext) loses figures, turns tables into run-together numbers, drops most equations, and returns nothing for a scanned page with no text layer; this tool returns pictures instead. Pass a parent item_key (its PDF attachment is resolved automatically, exactly as zotero_get_fulltext does) or an attachment key, a mode, and pages ("3" or "3-7", 1-based; default "1"). mode:"pages" renders whole pages and returns them as image content blocks you can look at, followed by a JSON block with each page's pixel size and byte count: the default resolution keeps body text legible (about 1568 px on the long edge, which is also as much as the model is shown), dpi (36 to 300) overrides it for small print, and format is "jpeg" (default, quality 80) or "png" (sharper line art, larger). mode:"figures" extracts the raster images embedded in those pages, the way pdfimages does: photographs, plots and diagrams stored as images, and on a scanned PDF the page image itself (reported with coversPage:true); each comes back with its page, pixel size, position on the page in points from the top left, and the image inline (inline, default true; very large ones as a 2000 px preview). A figure drawn as vectors (most matplotlib, TikZ and PDF-exported plots) is lines in the content stream, not an image, so it does not appear in figures mode; render the page with mode:"pages" to see it. Caps: max_pages per call (default 4, at most 8; a longer span is cut and the notice says how to continue), max_images (default 16, at most 40), images under min_size px on a side skipped (default 32: icons, rules, bullets), an image repeated across pages returned once, files above 20 MB not parsed, and about 5 MB of inline image data per response, beyond which pages or figures are left out with a notice naming them and the remedy (fewer pages, lower dpi, format:"jpeg", or the next span). A PDF whose encryption only restricts printing opens normally; one that needs a password to open is refused with a clear message; an EPUB has no pages to draw. The file is read from the running Zotero desktop app, else the local Zotero storage folder, else Zotero cloud storage. Read-only: it writes nothing, neither to the library nor to disk. Use it when a question is about a figure, a table, an equation, a diagram or a scanned document; use zotero_get_fulltext when the words are what matters.
Inputs: mode (required), item_key (required), dpi, save, +8 more. Returns: mode, pages, title, images, +12 more
zotero_relate_itemsLink items as RelatedAIdempotentInspect
Link pairs of library items as "Related", the link Zotero shows in an item's Related pane, on BOTH items of each pair, as Zotero itself does. Additive only: every relation an item already has (other Related links, merge records, any other predicate) is kept, and a pair already linked is reported as such and not written again. Both items must be in the same library. Every item is read before anything is written, so an unknown key refuses the whole call. Get candidates from zotero_suggest_related (pairs that cite each other), or pass any pairs you choose. To remove a link, edit relations with zotero_update_item. Writes go to the running Zotero desktop app (Zotero 10+) for every library it holds, otherwise to the cloud Web API. A personal library that has never synced to a zotero.org account cannot be linked from here: Zotero names it in the link by a local id neither API exposes.
Inputs: pairs (required), library_id, library_type. Returns: items, failed, linked, target
zotero_replace_noteReplace a Zotero note with MarkdownADestructiveIdempotentInspect
Replace the whole content of an existing Zotero note with new Markdown. The note keeps its key, parent item, tags and collections; only its text changes, and the old text is gone from Zotero (Zotero keeps no history of a note). The result returns the previous HTML as previousNote so it can be put back. Markdown: headings, paragraphs, bullet and numbered lists, bold, italic, strikethrough, code and fenced code blocks, links, > blockquotes, tables, horizontal rules, and $inline$ or $$display$$ math, all stored as the note editor's own HTML. Raw HTML is shown as text, not interpreted. Cite a library item with its key in brackets: [@ABCD2345], [@ABCD2345, p. 12], [see @ABCD2345; @WXYZ6789, pp. 3-5]. Each becomes a real Zotero citation (clickable in Zotero, live when the note is added to a Word document), and a key that names nothing refuses the write. Pass version (from zotero_get_item) to write only if nobody changed the note since you read it; without it the current text is replaced whatever it is. To add to a note instead, use zotero_append_to_note. Writes go to the running Zotero desktop app (Zotero 10+) for every library it holds, otherwise to the cloud Web API.
Inputs: markdown (required), note_key (required), cite, title, +3 more. Returns: target, retried, note_key, citations, +2 more
zotero_reviewScreening status, queue, PRISMA flow and duplicates for a review collectionARead-onlyIdempotentInspect
Read-only views for a systematic-review team screening a collection (works in group libraries). action: "queue" returns the next unscreened records for a stage (title, abstract, year, authors; pass reviewer for that reviewer's own blind queue, which hides the other reviewers' decisions); "status" counts decisions per stage, exclusion reasons, per-reviewer tallies, inter-rater agreement (percent and Cohen's kappa) and the records where reviewers disagree; "prisma" returns the PRISMA 2020 flow numbers with a ready text summary and a Mermaid diagram (pass records_identified or sources and duplicates_removed when you know them from the database exports); "duplicates" lists groups of records in the collection that are the same work by exact DOI, ISBN or normalised title plus year (never fuzzy), with a suggested record to keep, for zotero_merge_items. Decisions are Zotero tags of the form screen//[:][@], e.g. "screen/title-abstract/exclude:wrong-population@alice"; stage is title-abstract or full-text, decision is include, exclude or maybe, a reason goes only on exclude, and a tag with no @reviewer is the final (consensus) decision. They sync to the whole group and stay readable in Zotero without Zoteus. Record decisions with zotero_screen_items. Reads go to the running Zotero desktop app when it holds the library, otherwise to the cloud Web API; both answer the same.
Inputs: action (required), collection_key (required), limit, stage, +7 more. Returns: note, text, queue, stage, +17 more
zotero_save_annotation_digestSave a paper's or collection's annotations as a noteAInspect
Gather the annotations zotero_annotation_digest lists and save them as a Zotero note, laid out the way Zotero's own "Add Note from Annotations" lays one out: a paragraph per annotation with the highlighted passage, a citation with its page, the comment and tags. Each highlight stays linked to its spot in the PDF and each citation to its paper, so both open from the note in Zotero. For item_key the note is attached to that paper; for collection_key it is a standalone note filed in that collection, with a heading per paper (up to 200 items; the note says so if the collection holds more). Takes the same types, colors and tags filters. Nothing is written when no annotation matches. Writes go to the running Zotero desktop app (Zotero 10+) for every library it holds, otherwise to the cloud Web API.
Inputs: tags, title, types, colors, +5 more. Returns: key, items, failed, target, +6 more
zotero_saved_searchesManage Zotero saved searchesADestructiveInspect
List, create, or delete saved-search DEFINITIONS. NOTE: the Zotero cloud Web API stores saved searches but does NOT execute them — to get the items a saved search matches, run an equivalent zotero_search_items query (or use the desktop local API when available). Set action to "list" (all saved searches with their conditions), "create" (needs name and conditions, each {condition, operator, value}), or "delete" (needs search_key). Writes to your personal library go to the running Zotero desktop app (Zotero 10+) and otherwise to the cloud Web API; group libraries always write to the cloud.
Inputs: action (required), name, conditions, library_id, +2 more. Returns: target, created, deleted, searches, +1 more
zotero_save_extractionSave structured fields extracted from papersADestructiveIdempotentInspect
Save a structured extraction (named fields with values, such as sample_size, method, country, outcome) for one or many papers, as a child note on each paper: a readable table plus a machine-readable copy, tagged "zoteus-extraction". Zotero has no custom fields; a note syncs and anyone can read it in Zotero. YOU read the paper (zotero_get_fulltext) and supply the values: this server has no language model. Give each value the verbatim quote it rests on, and its page: when the paper's full text is available (Zotero's index, or the PDF or EPUB itself) the quote is checked against it and the verdict ("verified", "not-found" or "unchecked", with the page it was found on when known) is stored beside the value. A quote that was not found is still saved, and named in the result, so you can correct it. schema names the extraction (e.g. "rct-review"): one note per paper and schema. Saving again for the same paper and schema UPDATES that note in place: fields you send replace fields of the same name, a null value removes a field, fields you do not mention are kept (pass replace_all:true to drop them), and the result lists every replaced and removed value with what it was. Nothing else in the library is touched. Read the extractions back as one table with zotero_extraction_table. Writes go to the running Zotero desktop app (Zotero 10+) for every library it holds, otherwise to the cloud Web API.
Inputs: items (required), schema (required), library_id, replace_all, +2 more. Returns: items, saved, failed, schema, +1 more
zotero_schemaZotero data model (types & fields)ARead-onlyInspect
Return the Zotero data model so you never hardcode item shapes. With no arguments, returns the schema version and the list of all item type names. With item_type, returns the valid fields and creator types for that type (the "primary" creator type is listed first). Use this to validate an item before creating or updating it: notes, attachments, and annotations are item types too but bypass the normal field/creator model.
Inputs: item_type. Returns: fields, version, itemType, itemTypes, +1 more
zotero_scholarScholarly context (references, citations, related)ARead-onlyInspect
Explore the EXTERNAL scholarly graph around a paper (OpenAlex, Crossref fallback). This does NOT search, list, or read your Zotero library: it queries the open web, and results are works from the scholarly web, not your items. To search or inspect YOUR library use zotero_search_items, zotero_semantic_search, zotero_get_item, or zotero_list_tags instead. Provide a doi and an action: "lookup" (metadata + citation count, plus an oa block naming the open-access PDF OpenAlex knows of, when there is one), "references" (works this paper cites), "citations" (works that cite this paper, most-cited first), "related" (similar works), or "notices" (update notices deposited against the paper: retractions, corrections, expressions of concern, errata, withdrawals). "notices" asks Crossref, which redistributes the Retraction Watch database, and OpenAlex side by side and reports what each one says with its source and date; it never emits a verdict, there is no retracted field, a source that did not answer is reported as not reached rather than as "nothing found", and when the two sources disagree it says so. Set include_in_library: true to additionally flag which results your library already holds and hand back the item key for each (off by default because it scans the library); with action "citations" that key is the way into zotero_get_fulltext, whose query returns the passages where a citing paper you already hold discusses this one. Set library_scan: true with action "notices" to check the DOIs your library already holds against the same two sources and list only those with a record; it reports how much of the library it saw. limit caps results (default 20); every list answer also carries total, the size of the list the results were cut from, and truncated: true when the limit dropped some, so a review with 150 references never looks like one with 20. Read-only; calls external scholarly APIs. This is a thin citation-graph helper around a single DOI: for full OpenAlex querying (keyword search, filters, paging, select) call https://api.openalex.org directly, see the LLM quick reference in the OpenAlex help pages.
Inputs: action (required), doi, limit, library_scan, +1 more. Returns: oa, doi, mode, scan, +20 more
zotero_screen_itemsRecord screening decisions on Zotero itemsADestructiveIdempotentInspect
Record systematic-review screening decisions as Zotero tags on a batch of items, so the whole group library sees them. Decisions are Zotero tags of the form screen//[:][@], e.g. "screen/title-abstract/exclude:wrong-population@alice"; stage is title-abstract or full-text, decision is include, exclude or maybe, a reason goes only on exclude, and a tag with no @reviewer is the final (consensus) decision. They sync to the whole group and stay readable in Zotero without Zoteus. Pass reviewer to record your own independent decision (dual screening); omit it to record the final decision, e.g. after resolving a disagreement. Each item gets exactly one tag for this stage and reviewer: a decision recorded earlier for the same stage and reviewer is replaced, and every other tag (the user's own, other stages, other reviewers) is left as it was. Writes go to the running Zotero desktop app (Zotero 10+) for every library it holds, otherwise to the cloud Web API (requires ZOTERO_API_KEY with write access). Read the queue and the tallies with zotero_review.
Inputs: stage (required), decisions (required), confirm, reviewer, +2 more. Returns: stage, failed, target, recorded, +1 more
zotero_search_itemsSearch Zotero itemsARead-onlyInspect
Search or list items in a Zotero library or collection. Quick search via q (qmode: titleCreatorYear=default, matches title/creator/year only; everything=also searches notes & attachment full text). For presence checks ("is X in my library?"): a default-mode q that matches nothing auto-retries once in everything mode, so terms appearing only inside PDF text don't false-negative — pin qmode explicitly to disable. An empty everything result is reported as strong-but-not-conclusive, since un-indexed/scanned/un-synced PDFs aren't full-text searchable. Also supports boolean itemType filters (use || for OR, repeat or && for AND, leading - to negate, e.g. "journalArticle || book", "-attachment"), boolean tag filters (same syntax; escape a literal leading hyphen as "-"), since (version) for incremental queries, sort/direction, and limit/start paging. Set response_format to "detailed" to also return technical fields (version, tags, collections, DOI, url) needed before chaining a write; the default "concise" returns high-signal projections (key, itemType, title, creators, date). Reads are served from the fast desktop local API when available, otherwise the cloud Web API. Returns totalResults so you can tell when to page rather than assuming you saw everything. For conceptual/"papers about X" queries by meaning rather than exact fields, use zotero_semantic_search instead.
Inputs: q, tag, top, sort, +11 more. Returns: items, qmode, broadened, provenance, +2 more
zotero_semantic_searchSemantic / hybrid library searchARead-onlyInspect
Search the library by meaning, not just keywords. Combines BM25 keyword scoring with vector similarity (when an embedding provider is configured) via reciprocal-rank fusion, and returns the best-matching items with a snippet and score. By default it searches item metadata and abstracts; if the index was built with fulltext on (zotero_index fulltext:true, or ZOTEUS_INDEX_FULLTEXT=true) it also searches the body text of attachments, and a hit whose snippet came from a PDF body is marked source:"fulltext". It ALSO searches the words the reader wrote, child notes and PDF annotations (highlight text and comments), unless that was turned off (ZOTEUS_INDEX_OWN_WORDS=false); a hit from one is marked source:"note" or source:"annotation" and is attributed to the item it hangs off, so an item with forty annotations is one result rather than forty. mode: "auto" (hybrid, default), "keyword" (BM25 only), or "semantic" (vector only). "semantic" needs both vectors in the index and a running embedder to turn the query into one: when either is missing (embeddings switched off, or e.g. the on-device model runtime is not installed) it returns an error naming the cause instead of an empty result set, and "auto" keeps working as keyword search while saying so. The index must be built once before first use, with zotero_index action:"build": this tool only reads, so on an empty index it starts nothing and returns an error naming that call (or, while a build is running, its progress). ONE INDEX FILE HOLDS ONE LIBRARY, and a plain call answers from the default library's index: which library that is comes back as library on the result and is named in the summary (both absent only on an index built before that stamp existed, where the library is genuinely unknown). library_type/library_id name ONE library: when that library has an index of its own in this data directory, the search answers from THAT index; when it does not, you get an error naming which library the index that IS here holds, rather than a silent answer from rows belonging to a different library; zotero_index action:"build" with the same library arguments gives it one. Omitting them searches the default library's index, whatever it holds. To search SEVERAL libraries at once, build each one's index (zotero_index action:"build" library_type:"group" library_id:), then pass libraries: libraries:"all" searches every library that has an index here, libraries:["user","group:4523"] searches the ones you name, and zotero_index action:"libraries" lists what exists. A combined answer is MERGED BY RANK and never by score, because each index scores against its own library's statistics and may hold vectors from a different embedding model: score is therefore only comparable between hits from the SAME library, every hit carries library and libraryRank (its position in that library's own answer), and a mix of embedding models is reported as embedderMismatch rather than fused away. A library named in libraries that has no index is reported with the command that would build it; nothing there starts a build. For exact field/tag/itemType filtering use zotero_search_items instead; use this for conceptual/"papers about X" queries. To read the actual passages of a found item (with page locators) use zotero_get_fulltext.
Inputs: q (required), mode, limit, libraries, +3 more. Returns: hits, merge, library, embedder, +14 more
zotero_stylesResolve CSL citation stylesARead-onlyInspect
Resolve a human citation-style name to a valid CSL style id and confirm it is available, or list common style aliases. action: "resolve" maps names like "APA 7th", "IEEE", "Vancouver", "Chicago", "MLA", "Nature" to the correct CSL id (e.g. apa, ieee, modern-language-association) and verifies the style can be fetched; pass the returned styleId as the style argument to zotero_format_bibliography or zotero_bibliography. action: "list" returns the built-in common aliases (any id from the CSL styles repository also works). Dependent styles are resolved to their independent parent automatically when formatting. Long names resolve too, including the titles Zotero lists styles under ("Chicago Manual of Style 17th edition (author-date)", "American Psychological Association 7th edition"), and a past edition the CSL repository still carries (Chicago 16th and 17th, APA 6th, AMA 10th) resolves to that edition's own style rather than to the current one. A name it cannot read is tried as a CSL id as given.
Inputs: action (required), name. Returns: input, common, styleId, available
zotero_syncIncremental sync deltaARead-onlyInspect
Return what changed in a library since a given version, for efficient incremental sync. Provide since (a library version; 0 = everything). Returns, per object type (items/collections/searches/tags), the map of keys→version that changed after since, plus the deletion log (keys removed since since). This is the version-based delta the Zotero sync algorithm uses: fetch the changed keys, then pull only those with zotero_get_item/zotero_search_items. Follows the library route: a running Zotero desktop app serves the delta for any library it holds, with no cloud API key, and otherwise the cloud Web API does; backend says which one answered. The whole delta comes from that one API, because the two number library versions independently. The desktop app serves item and collection versions but no tag versions and no deletion log; those are reported in unavailable, naming what is missing and why, and never as an empty result.
Inputs: since, types, library_id, library_type, +1 more. Returns: since, backend, changed, deleted, +1 more
zotero_tag_auditAudit tags against a controlled vocabularyARead-onlyInspect
Audit a library against a controlled tag vocabulary with priority tiers. Provide the vocabulary inline as vocabulary (or a JSON file via vocabulary_path): { tags:[{name,tier?}], tiers?:[{name,required?}] }. Reports (1) off-taxonomy tags (library tags not in the vocabulary; Zotero auto-applied tags are bucketed separately unless include_auto), (2) items missing a tag from each required tier, and (3) optional per-collection coverage when scope.collection_keys is given. A key that none of these objects knows is refused and named, never dropped: a dropped scope, tier or required would change the question without changing the answer. Read-only. Tag and item enumeration both follow the library route, so a running Zotero desktop app serves the whole audit with no cloud API key.
Inputs: limit, scope, library_id, vocabulary, +3 more. Returns: autoTags, collections, offTaxonomy, itemsScanned, +3 more
zotero_trash_itemsTrash or restore Zotero itemsAInspect
Move items to the trash (the safe, REVERSIBLE default) or restore them. This sets the deleted flag (1=trash, 0=restore); it is NOT a permanent delete, so trashed items can be recovered here or in the Zotero app. Use this instead of zotero_delete_items unless you truly need irreversible removal. Provide item_keys and optional action (default "trash"). Writes go to the running Zotero desktop app (its local-API writes, Zotero 10+) for your personal library and for any group library the app holds, otherwise to the cloud Web API. When the server sets a bulk-write threshold (ZOTEUS_CONFIRM_BULK_WRITES, off by default), trashing more items than that in one call also needs confirm: true.
Inputs: item_keys (required), action, confirm, library_id, +1 more. Returns: failed, target, updated, libraryVersion
zotero_update_itemUpdate a Zotero itemADestructiveIdempotentInspect
Partially update one item (HTTP PATCH — only the fields you supply change; omitted fields are preserved). Provide item_key and a patch object of the fields to change (e.g. {"title":"New","extra":"note"} or {"tags":[{"tag":"reviewed"}]}). All field values are plain JSON strings/numbers/booleans/arrays — never wrapped in nested objects (e.g. "title": "New", NOT "title": {"title": "New"}). Optimistic concurrency is handled for you: if you pass the item's version it is used; otherwise the current version is fetched first. If the item changed on the server in the meantime (412), the update is automatically re-fetched and retried once. Writes to your personal library go to the running Zotero desktop app (Zotero 10+) when it is up, otherwise to the cloud Web API; the version is read from the same place the write goes. Set dry_run:true to preview the field-level before→after diff without writing (arrays like tags/collections are replaced wholesale by PATCH, not merged; a dry_run call performs no write).
Inputs: patch (required), item_key (required), dry_run, version, +2 more. Returns: diff, dryRun, target, retried, +4 more
zotero_whoamiZotero identity & accessARead-onlyInspect
Resolve the current Zotero identity (userID, username, display name) and per-library access scopes from the configured API key, report the running Zoteus version, and report which library backends are available (cloud Web API and/or the desktop local API). Call this first to discover the userID — never ask the user to type a numeric ID. It also reports which library every call defaults to and WHY (defaultLibrary.source: pinned by whoever runs the server, derived from the key, or the desktop app's own library), whether this caller has a context of their own or shares the one the server operator configured (context), and which single library this context's search index holds (searchIndex). For per-group write permission, call zotero_groups. If no API key is configured, the server runs in local-only read mode against the desktop library (users/0).
Inputs: no named parameters. Returns: cloud, access, update, userID, +12 more
zotero_word_documentWrite a Word document with live Zotero citationsADestructiveInspect
Write a .docx whose citations are LIVE Zotero fields, not plain text: Zotero's Word plugin is meant to refresh, restyle and add to them (never run here, so check your first document). Give body as paragraphs containing [[cite:ITEMKEY]] or [[cite:ITEMKEY,p. 12]] placeholders; [[cite:KEY1;KEY2]] puts several works in one field, which is how "(Wu, 2026; Devos, 2026)" is written. Each placeholder becomes a Word field carrying the item's CSL data, with the formatted citation as its visible text. A bibliography field is appended by default. For plain formatted references with no live fields use zotero_bibliography or zotero_format_bibliography instead. The file is written to disk and the path is returned; on a shared deployment it is confined to the server data directory. Refreshing needs Microsoft Word with the Zotero word-processor plugin, and Zotero running: nothing else re-renders the fields. What is verified is the package and the field codes, by unpacking the .docx and checking its XML; a refresh in Word has never been run, and LibreOffice's Zotero extension uses ReferenceMarks rather than Word fields, so whether it adopts this document is untested as well. This writes paragraphs and citation fields and nothing else: no headings beyond title, no tables, no images, and no footnotes, so a note style renders its notes inline in the body.
Inputs: body (required), style, title, locale, +5 more. Returns: bytes, style, linked, locale, +7 more
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 47 tools
Most tools target a distinct resource+action, but several pairs genuinely overlap: zotero_attachment (upload) vs zotero_attach_file, zotero_list_collections vs zotero_manage_collections action:"list", zotero_fulltext vs zotero_get_fulltext, and zotero_bibliography vs zotero_format_bibliography. The extremely long descriptions do explicitly steer between them, which keeps misselection recoverable, but the boundaries are not self-evident from names alone.
Nearly all tools use a consistent zotero_ snake_case prefix, with readable verb_noun or resource names (zotero_search_items, zotero_create_items, zotero_merge_items). Minor deviations exist: one unprefixed tool (search_tools), verbless names (zotero_whoami, zotero_schema, zotero_styles, zotero_activity), and asymmetries like list_tags vs manage_tags and fulltext vs get_fulltext.
At 47 tools the surface is far past a comfortable scope, and the size is partly driven by redundancy (list_* duplicates of manage_* list actions, two attachment-upload paths, three bibliography renderers). A search_tools discovery helper mitigates the load, but the set still reads as a bundle of several servers rather than one well-scoped toolset.
Coverage of the Zotero domain is unusually thorough: full item/collection/tag/note/annotation/attachment CRUD, search (keyword and semantic), full-text retrieval and PDF rendering, export and CSL formatting, sync deltas, plus systematic-review, extraction, citation-checking, and evidence-table workflows. Trash-vs-permanent-delete and note append/replace variants close the obvious lifecycle gaps.
Maintenance
Related MCP Connectors
Remote MCP server for full read/write access to a Zotero library
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Hosted MCP server connecting AI assistants to 9,000+ apps and 40,000+ actions via Zapier.
Read-only MCP server exposing a user ORANO library to their own AI agent.
Related MCP Servers
- AlicenseAqualityDmaintenanceAn MCP server that gives any MCP-compatible assistant access to your Zotero reference library, enabling search, citation, bibliography generation, and .docx processing while keeping Zotero as the ground truth for references.91MIT
- AlicenseAqualityBmaintenanceMCP server that lets AI assistants search, create, organize, and cite from a Zotero library.394MIT
- AlicenseNot gradedqualityDmaintenanceMCP server that connects AI assistants to your Zotero library, enabling full-text PDF extraction and metadata search.MIT
- AlicenseNot gradedqualityDmaintenanceThis MCP server enables AI assistants like Claude to read and search your local Zotero bibliographic database directly from your machine.8MIT