Skip to main content
Glama
harrig12

zotero-mcp

by harrig12
README.md
# zotero-mcp

MCP server that gives Cursor AI live read access to your Zotero library. Ask Cursor to look up papers by author, topic, or collection — it queries your real library on demand.

This projcet is 100% vibe coded

## Inspiration

The starting point was wanting Cursor to have context about academic papers alongside an analysis project . Result: a lightweight MCP server giving Cursor live read access to the Zotero web API on demand. The goal isn't to dump your whole library into context — it's to let the AI look things up, surface your notes, follow connections between papers, and reason about what you've been reading.

## How it works

Cursor calls these tools exposed by this server:


| Tool                          | Purpose                                                  |
| ----------------------------- | -------------------------------------------------------- |
| `zotero_search`               | Full-text search across your library (personal or group) |
| `zotero_get_item`             | Full metadata + abstract for one item                    |
| `zotero_list_collections`     | Browse your collections (personal or group)              |
| `zotero_get_collection_items` | Items within a collection (personal or group)            |
| `zotero_list_groups`          | List all group libraries you have access to              |


The server runs as a subprocess on stdio transport — Cursor spawns it, passes credentials via environment variables, and kills it when done.

## Setup

### 1. Get credentials

Go to [zotero.org/settings/keys](https://www.zotero.org/settings/keys):

- **API key** → "Create new private key", grant read-only library access
- **User ID** → shown on the same page as "Your userID for use in API calls" (a plain number)

### 2. Install dependencies

```bash
# in the zotero-mcp mamba/conda env
pip install -r requirements.txt
```

### 3. Add to Cursor (WSL + mamba)

Edit `%APPDATA%\Cursor\User\settings.json` and add to `mcpServers`:

```json
{
  "mcpServers": {
    "zotero": {
      "command": "wsl",
      "args": [
        "/home/cait/miniforge3/envs/zotero-mcp/bin/python",
        "/mnt/c/Users/Cait/Projects/zotero-mcp/server.py"
      ]
    }
  }
}
```

Credentials are read from `.env` in the project directory — no need to repeat them in `settings.json`.

Restart Cursor after saving.

### 4. Smoke test

In your WSL terminal:

```bash
conda activate zotero-mcp
ZOTERO_API_KEY=yourkey ZOTERO_USER_ID=yourid python server.py
# Should hang waiting for stdin — that means it started correctly. Ctrl+C to exit.
```

## Tool reference

### `zotero_search(query, limit=10, group_id=None)`

Full-text search across your library. Includes notes; excludes attachments. Pass `group_id` to search a group library.

```
zotero_search("attention mechanism", limit=5)
zotero_search("chromatin potential", group_id="12345")
```

Returns: `[{key, title, authors, year, abstract, item_type}, ...]`

### `zotero_get_item(item_key, group_id=None)`

Full metadata for one item. `item_key` is the 8-character Zotero key (e.g. `"ABC12345"`).

```
zotero_get_item("ABC12345")
zotero_get_item("ABC12345", group_id="12345")
```

Returns: `{key, title, authors, year, abstract, item_type, doi, url, publication, volume, issue, pages, publisher, tags, collections, date_added}`

### `zotero_list_groups()`

All group libraries you have access to.

Returns: `[{id, name, description, num_items}, ...]`

### `zotero_list_collections(group_id=None)`

All collections in your library. Pass `group_id` to list a group library's collections.

Returns: `[{key, name, item_count}, ...]`

### `zotero_get_collection_items(collection_key, limit=20, group_id=None)`

Items within a specific collection. Pass `group_id` to fetch from a group library.

```
zotero_get_collection_items("XYZ98765", limit=10)
zotero_get_collection_items("XYZ98765", group_id="12345")
```

Returns: `[{key, title, authors, year, abstract, item_type}, ...]`

## Security

- API key is passed only via environment variable — never in code or on disk
- The server validates credentials at startup and exits with a clear error if any are missing
- `.env` is gitignored; use `.env.example` as a template if you prefer local `.env` over Cursor's `env` block
- Server has read-only access to Zotero (enforced by the API key permissions you set)

## Changelog

### Recent changes

- **Group library support** — new `zotero_list_groups()` tool to discover group IDs; all existing tools now accept an optional `group_id` parameter to query a group library instead of your personal one
- **Notes included in search** — Zotero notes are no longer excluded from results; note content (HTML-stripped) is surfaced as title/abstract

## Potential improvements

- **Dedicated note search** — `zotero_search_notes(query)` tool that searches only within user notes, with full note content returned
- **Tag support** — expose `zotero_get_tags()` to list all tags in library, and `zotero_search_by_tag(tag)` to filter by tag
- **Tag colors** — Zotero supports colored tags (position 1–9); surface these in tag metadata so Cursor can reason about your personal tagging system
- **Auto-tag awareness** — distinguish between user-created tags and auto-generated ones (e.g. from Zotero connectors or imports); Zotero marks these with `type: 1` vs `type: 0`
- **Tag search in `zotero_search`** — include tags in search results by default so Cursor sees how you've categorised a paper