Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Pydantic Zotero

БесплатноНе проверен

An MCP server that gives AI agents read access to a Zotero library, enabling search, item metadata, collections, tags, notes, and full-text search of attached P

GitHubEmbed

Описание

An MCP server that gives AI agents read access to a Zotero library, enabling search, item metadata, collections, tags, notes, and full-text search of attached PDFs.

README

An MCP server that gives AI agents read access to a Zotero library — search, item metadata, collections, tags, the researcher's own notes, and the indexed full text of attached PDFs.

See PRD.md for requirements.

Status: M1 (read core) + M2 (full text) implemented. Citation formatting and export (M3), prompts (M4), and write tools (M5) are not built yet — see Not yet implemented.

Install

As a tool (pipx)

Installs the zotero-mcp command into its own isolated environment:

pipx install pydantic-zotero-mcp        # or: pipx install /path/to/checkout
zotero-mcp --help

Into another project's environment

uv add pydantic-zotero-mcp              # or: uv pip install pydantic-zotero-mcp

For development on this server

git clone https://github.com/jmlon/pydantic-zotero-mcp
cd pydantic-zotero-mcp
uv sync           # creates ./.venv from this project's own lock file
uv run pytest
uv run ruff check

Configure

Get a read-only API key and your numeric user ID from https://www.zotero.org/settings/keys. The library ID is the number, not your username.

export ZOTERO_API_KEY=...
export ZOTERO_LIBRARY_ID=123456      # numeric
export ZOTERO_LIBRARY_TYPE=user      # or group
Variable Default Purpose
ZOTERO_API_KEY Web API key (required unless ZOTERO_LOCAL=true)
ZOTERO_LIBRARY_ID Numeric user or group ID
ZOTERO_LIBRARY_TYPE user user or group
ZOTERO_LOCAL false Read the Zotero 7 desktop API instead: no key, no rate limit, read-only
ZOTERO_ALLOW_WRITES false Reserved for M5; no write tools exist yet
ZOTERO_FULLTEXT_MAX_CHARS 100000 Default full-text ceiling; per-call max_chars overrides it
ZOTERO_DEFAULT_STYLE chicago-note-bibliography Reserved for M3
ZOTERO_MAX_CONCURRENCY 4 Upstream request cap (Zotero asks for ≤ 4)
ZOTERO_MCP_TRANSPORT stdio stdio or http
ZOTERO_MCP_HOST 127.0.0.1 HTTP bind address
ZOTERO_MCP_PORT 8000 HTTP port
ZOTERO_MCP_PATH /mcp HTTP mount path
ZOTERO_MCP_AUTH_TOKEN Bearer token; required for HTTP

CLI flags override environment variables.

Run

Once installed, zotero-mcp is the entry point — no interpreter path, no python -m, no working directory to get right, which is what an MCP client's command: wants:

# stdio (default) — an agent launches this as a subprocess
zotero-mcp

# streamable HTTP — requires ZOTERO_MCP_AUTH_TOKEN
ZOTERO_MCP_AUTH_TOKEN=secret zotero-mcp --transport http --port 8000

# read the Zotero desktop app instead of the web API
zotero-mcp --local

From a checkout, without installing, python -m zotero_mcp still works:

uv run python -m zotero_mcp

Starting with --transport http and no token exits 2 rather than serving unauthenticated: this is a read channel into a personal library.

In-memory (embedded in an agent process)

No subprocess, no socket. Settings are injected, so the host never needs environment variables:

from fastmcp import Client
from zotero_mcp import ZoteroSettings, create_server

server = create_server(
    ZoteroSettings(
        api_key=key,
        library_id="123456",
        library_type="user",
    )
)

async with Client(server) as client:  # lifespan opens here
    result = await client.call_tool("search_items", {"query": "attention"})
    print(result.structured_content["items"])  # dict; result.data is a model

Importing zotero_mcp has no side effects — no config read, no client built, no network — which is what makes embedding possible. There is a test that enforces it.

Discovery via entry point

For host applications that discover bundled MCP servers through Python entry points, this package declares one in the deep_research.mcp_servers group:

[project.entry-points."deep_research.mcp_servers"]
zotero = "zotero_mcp:build_server"

build_server() takes no arguments and derives settings from the environment — install this package into the host's environment and the host can resolve and run the server in-process by the name zotero, without importing anything by path from a config file.

One tuning note for automated hosts: this server's default full-text ceiling is 100,000 characters (~25–30k tokens for a single get_item_fulltext call), which is generous for interactive use and far too large for an agent making many calls against a token budget — pass a smaller max_chars per call, or lower ZOTERO_FULLTEXT_MAX_CHARS.

Tools

Tool Purpose
get_library_info Size, mode, permissions. Cheap orientation call — use it first
search_items Primary entry point. mode="metadata" or "fulltext" (searches PDF text)
list_recent_items Recently added items, newest first
find_item_by_identifier "Do I already have this?" by DOI, ISBN, arXiv ID, or key
get_item Full metadata; include_children=True also lists attachments and notes
get_item_children Attachments and notes, with may_have_fulltext per attachment
get_item_notes The researcher's own notes, HTML stripped
get_item_fulltext Indexed attachment text; resolves parent → attachment
list_collections Nested collection tree
list_collection_items Items in one collection
list_tags Tag vocabulary, optionally prefix-filtered

Resources: zotero://library/info, zotero://collections, zotero://items/{key}, zotero://items/{key}/fulltext, zotero://collections/{key}/items, zotero://schema/item-types, zotero://schema/item-types/{type}/fields.

Design notes

Projection is the point. Raw Zotero JSON is ~1 KB per item of links, library, meta, and empty type fields. zotero_mcp/projection.py reduces a 25-item page from ~6,100 to ~2,400 estimated tokens (39% of raw), under the PRD's 4,000 budget. Null fields are dropped at serialization by CompactModel.

pyzotero is synchronous and stateful. Zotero.request and Zotero.links are overwritten by each call, and Total-Results is read back off the instance afterwards — so one shared client used concurrently would report another call's totals. gateway.py keeps a pool of up to ZOTERO_MAX_CONCURRENCY clients, checks one out per operation, and reads response metadata inside the same worker thread that holds it. Every call goes through anyio.to_thread.run_sync so the event loop never blocks.

Backoff is pyzotero's job. pyzotero ≥ 1.13 already honours Backoff / Retry-After and retries 429 internally, so the gateway does not reimplement it. It adds a bounded 3-attempt retry for transient transport and 5xx failures only.

Nothing is silently truncated. Searches report total_matched, truncated, and next_start; full text reports total_chars and truncated.

Results are candidates, not verdicts (PRD D3). find_item_by_identifier returns matched_on (key / doi / title / identifier / none) plus a confidence and all plausible candidates — a pre-print and its published version both survive. The caller filters.

Deviations from the PRD

Worth knowing about, since each was a judgment call made during implementation:

  1. No module-level mcp object. PRD 7.2 asked for both a module-level mcp = create_server() and no import-time side effects. Those conflict: building the server validates config, so a module-level instance raises ImportError on any machine without Zotero env vars, and breaks the in-memory path it was meant to support. Only create_server() / build_default_server() exist.

  2. Write tools will be registered conditionally, not enabled=False. PRD 5.5 specified @mcp.tool(enabled=False), but FastMCP 3.x has no enabled kwarg, and a disabled-but-listed tool still costs context. When M5 lands, write tools will simply not be registered unless ZOTERO_ALLOW_WRITES=true.

    This server targets FastMCP 3.x. Two 3.x specifics shape the code here: enabled is gone from the decorators, and result.data is a generated pydantic model while result.structured_content is the plain dict — the tests assert on the latter, which also verifies null-omission on the wire.

  3. has_fulltext is three-valued. PRD 6 typed it bool, but determining it for a parent item requires a separate children request per item, which would make a 25-item search 26 requests. It is False when an item has no children at all, True/False for attachments and after get_item(include_children=True), and null (omitted) when undetermined. ItemSummary.num_children gives the cheap signal.

  4. find_item_by_identifier returns CitationMatch, not ItemSummary | None. Follows from D3 — the old signature made exactly the identity call that decision moved to the client.

  5. matched_on gained key and identifier beyond the PRD's four values, to distinguish an exact key hit from a weak search hit.

  6. list_recent_items(since_days=...) filters locally. Zotero has no server-side date filter, so a narrow window can return fewer items than limit; the response hint says when that happened.

Tests

uv run pytest      # 80 passed

The suite uses FastMCP's in-memory transport against a FakeZotero that reproduces pyzotero's read-metadata-off-the-instance behaviour. No network, no subprocess, no real credentials. Coverage: schema surface, projection and token budget, pagination and truncation reporting, full-text ceiling and parent resolution, match recall (pre-print/published pairs both returned), error message quality, resource template validation including traversal attempts, config validation, CLI precedence, gateway retry/caching, and an import-purity check that fails if importing the package touches the network.

Not yet implemented

  • M3format_citation, format_bibliography, export_items
  • M4 — the four prompts (literature_review, find_related_work, check_citations, summarize_reading), Logfire instrumentation
  • M5 — write tools (create_item, update_item_fields, add_item_tags, add_items_to_collection, create_note) with version-checked PATCH semantics. Deletion is out of scope permanently.

from github.com/jmlon/pydantic-zotero-mcp

Установить Pydantic Zotero в Claude Desktop, Claude Code, Cursor

Рекомендуется · одна команда, все IDE
unyly install pydantic-zotero-mcp

Ставит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.

Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh

Или настроить вручную

Выполни в терминале:

claude mcp add pydantic-zotero-mcp -- uvx pydantic-zotero-mcp

Пошаговые гайды: как установить Pydantic Zotero

FAQ

Pydantic Zotero MCP бесплатный?

Да, Pydantic Zotero MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Pydantic Zotero?

Нет, Pydantic Zotero работает без API-ключей и переменных окружения.

Pydantic Zotero — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Pydantic Zotero в Claude Desktop, Claude Code или Cursor?

Открой Pydantic Zotero на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Pydantic Zotero with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории ai