github 54yyyu/zotero-mcp v0.15.0

2 hours ago

Added

  • The open-access PDF cascade asks OpenAlex for repository copies. When zotero_add_item or a batch import attaches a PDF for a DOI, it now tries every pdf_url OpenAlex lists after Unpaywall and before the publisher's citation_pdf_url, open-access locations first. OpenAlex knows institutional repository copies that Unpaywall does not count as open access. For Maraun 2013 in J. Climate, DOI 10.1175/jcli-d-12-00821.1, Unpaywall has nothing and the AMS page offers a 2-page preview, while OpenAlex lists the full 7-page PDF in the GEOMAR repository. A URL already tried under one source is not downloaded again under the next. Thanks @phil-c137.
  • Markdown and YAML files can be attached (#703). zotero-cli attach and add file accept .md, .yaml and .yml, stored as text/markdown and text/yaml so Zotero's own full-text indexer picks them up. .json stays a CSL-JSON import. Requested by @rizakardas.
  • zotero-cli --json get fulltext says when a read was cut short (#701). data.truncated is always present, and a capped read adds pages_read and page_count, so a script no longer has to parse the TRUNCATED heading out of data.text.

Changed

  • Semantic search is its own toolset, and hidden on installs without the [semantic] extra (#572, #689). zotero_semantic_search moves from core into a new semantic toolset that is on by default, so default installs see no change. ZOTERO_MCP_TOOLSETS=none now leaves it out too; use none,semantic to keep it. A spec that uses none without naming semantic logs one warning at startup saying so; none,-semantic leaves it out without the warning. semantic and search-admin are left out automatically when ChromaDB isn't installed, so a base install no longer advertises tools that can only reply with install instructions; naming them in ZOTERO_MCP_TOOLSETS keeps them. The connector search keeps its keyword fallback. Reported by @zhuziqi97; thanks @falcon1-1-code.
  • The literature-review and contradicting-evidence prompts fall back to zotero_search_items when semantic search is not usable (#693). zotero_literature_review and zotero_find_contradicting_evidence always told the model to call zotero_semantic_search, which fails without the [semantic] extra and is absent when a toolset spec leaves it out. They now check at render time (ChromaDB found with find_spec, not imported, and the tool's toolset enabled) and otherwise name zotero_search_items with qmode='everything', one key term per query. With semantic search usable the text is unchanged. Thanks @falcon1-1-code.
  • zotero_advanced_search rejects a field name it does not know (#694). A typo such as titel was a valid-looking condition that matched nothing, so the tool answered "No items found matching the search criteria" and the mistake went unnoticed. It now returns an error naming the field, up to three close matches (Did you mean: title?) and the supported operations. Thanks @falcon1-1-code.
  • zotero_advanced_search accepts a single condition sent as a bare object (#695). {"field": "title", "operation": "contains", "value": "x"} instead of a list of one was rejected with a raw pydantic "Input should be a valid list" error, and the same object as a JSON string answered "No search conditions provided". It is now read as a one-condition list. An empty object is still an error. Thanks @falcon1-1-code.

Fixed

  • A startup semantic update no longer holds up the MCP handshake (#706). With auto_update: true and update_frequency: "startup" (or a due daily run), the server ran the update in a worker thread of its own process, where native calls that hold the GIL (ChromaDB, loading or downloading the embedding model) and, on Windows, DLL loads stall the event loop as well: Claude Desktop's initialize went unanswered until the update finished, timed out, and shut the server down. The update now runs as a child zotero-mcp update-db process (with --fulltext in local mode), its output relayed to the server's log, and the server answers at once. The update lock still keeps it from racing a manual update-db or a second server, and an update unfinished at shutdown is stopped and resumes on the next start. Reported by @marius-ne.
  • The Zotero Agent plugin finds the agent CLIs on Windows (#700). Detection ran command -v through /bin/sh, so on Windows every backend failed with File at path "/bin/sh" does not exist, or is not executable. There the plugin now searches PATH with where.exe (PATHEXT aware, taking codex.cmd rather than npm's extension-less shim), reads the environment from cmd.exe /c set, runs npm's .cmd shims by the script or .exe they wrap, makes its folders with Node instead of mkdir, and stops agents with taskkill. macOS and Linux are unchanged. Translating with pi is still unavailable on Windows. Reported by @patrickjcrawford. This is untested on a real Windows machine; reports welcome on #700.
  • In local mode the DOI duplicate check reads zotero.sqlite (#704, #705). It ran an everything quick search per DOI, which against the local API is a full-text scan of the library and pinned Zotero at 100% CPU for minutes on a batch import. The DOI is now looked up in the database, about a millisecond per DOI, with the old search as the fallback when the database cannot answer. A candidate the API cannot fetch is skipped instead of failing the import. Thanks @mronkko.
  • zotero-cli --json search reports a failed search as an error (#691, #696). An error came back as ok: true, count: 0, the same as no matches; it is now ok: false with exit code 1, also for a failed semantic search or a collection that does not exist. Thanks @RizgarOzan.
  • url conditions work in SQLite advanced search (#701). The field was missing from the SQL map, so with --all-libraries, which has no API fallback, every url condition failed. Reported by @jstotero.
  • zotero-cli edit --collections help says it replaces (#701). It read "Add to collections", but the tool replaces the item's memberships, so following the help removed the item from its other collections. The help now says so and points at collections manage --add-to.
  • gemini-embedding-2 returns one vector per text (#697, #698). The batch went out as one list, which that model reads as a single multi-part input and answers with one aggregated vector, so an index built with it was wrong. Each text is now its own Content. gemini-embedding-001 is unchanged. Reported by @Faustoneiros; thanks @lots-o.
  • Restoring after an update no longer replaces the semantic index (#699). The restore step deleted the index and copied the backup back, so a partial backup replaced a complete index, along with anything else in a shared persist_directory. It now copies only when the index is missing. Thanks @lots-o.
  • YAML attachments are read as text (#703, #707). .yaml, .yml and application/yaml attachments were skipped by full-text extraction. Thanks @kskszq.

Don't miss a new zotero-mcp release

NewReleases is sending notifications on new releases.