MCP Server Zotero Dev
by introfini
README.md
<div align="center">
# MCP Server Zotero Dev
**Give your AI assistant superpowers for Zotero plugin development**
[](LICENSE)
[](https://www.zotero.org)
[Architecture](ARCHITECTURE.md) Β· [Getting Started](#quick-start) Β· [Available Tools](#available-tools)
<img src="docs/images/demo.png" alt="MCP Server Zotero Dev in action" width="800">
</div>
---
A **Model Context Protocol (MCP) server** that enables AI assistants like Claude, Cursor, and Windsurf to build, test, and debug [Zotero](https://www.zotero.org) 7, 8, 9, and 10 plugins. Screenshots, DOM state, debug logs, and JavaScript execution give the AI rich context to understand what's happeningβand tools to help you fix it.
## β¨ Features
| Category | Capabilities |
|----------|-------------|
| π― **UI Inspection** | Screenshots, DOM tree, element finding, computed styles |
| π±οΈ **UI Interaction** | Click elements and type text (shadow-DOM aware) |
| π» **JS Execution** | Run code in Zotero context, inspect APIs, test snippets |
| π§ **Build Tools** | Scaffold integration for build, serve, hot reload |
| π **Logs & Errors** | Stream debug output, error console, watch for issues |
| ποΈ **Database** | Read-only access to zotero.sqlite for debugging |
| π **Plugin Management** | Install, reload, list plugins |
---
## π Quick Start
### Prerequisites
- **Node.js** 20+ and npm
- **Zotero 7+** β Works on all Zotero 7, 8, 9, and 10 builds (release, beta, dev)
- For plugin development: [zotero-plugin-scaffold](https://github.com/windingwind/zotero-plugin-scaffold)
### 1. Install MCP Server
Use [install-mcp](https://www.npmjs.com/package/install-mcp) to add the server to your AI assistant:
```bash
npx -y install-mcp @introfini/mcp-server-zotero-dev --client claude-code
```
Supported clients: `claude-code`, `cursor`, `windsurf`, `vscode`, `cline`, `roo-cline`, `claude`, `zed`, `goose`, `warp`, `codex`
<details>
<summary><strong>Claude Code</strong></summary>
```bash
npx -y install-mcp @introfini/mcp-server-zotero-dev --client claude-code
```
</details>
<details>
<summary><strong>Cursor</strong></summary>
```bash
npx -y install-mcp @introfini/mcp-server-zotero-dev --client cursor
```
</details>
<details>
<summary><strong>VS Code / Copilot</strong></summary>
```bash
npx -y install-mcp @introfini/mcp-server-zotero-dev --client vscode
```
</details>
<details>
<summary><strong>Windsurf</strong></summary>
```bash
npx -y install-mcp @introfini/mcp-server-zotero-dev --client windsurf
```
</details>
<details>
<summary><strong>Manual Configuration</strong></summary>
Add to your MCP client config:
```json
{
"mcpServers": {
"zotero-dev": {
"command": "npx",
"args": ["-y", "@introfini/mcp-server-zotero-dev@1.1.1"],
"env": {
"ZOTERO_RDP_PORT": "6100"
}
}
}
}
```
</details>
> **Version & updates**: pin an exact version as shown above. A bare `npx <pkg>` (no version) keeps running whatever `npx` cached and won't pick up new releases, so always include a version and `-y` (without `-y`, `npx` hangs waiting for an install prompt). Bump the pinned version to upgrade, or use `@latest` to always fetch the newest at launch (auto-updates, but a bad release would run automatically and it adds a registry check on every start). Note that `install-mcp` may write a config without `-y` or a version, so the manual configuration above is the most robust path.
**Restart your AI assistant** after adding the configuration.
### 2. Install MCP Bridge Plugin in Zotero
Download [zotero-mcp-bridge.xpi](https://github.com/introfini/mcp-server-zotero-dev/releases/latest) and install:
1. In Zotero: **Tools β Plugins**
2. Click βοΈ β **Install Plugin From File**
3. Select the downloaded `.xpi` file
4. **Restart Zotero**
> This lightweight plugin enables the Remote Debugging Protocol when Zotero starts. It only needs to be installed once and works on all Zotero 7+ builds (release, beta, and dev).
### 3. Start Developing!
Just open Zotero normally and ask your AI assistant:
> "Take a screenshot of Zotero and list installed plugins"
That's it! No special launch flags, no configuration. π
---
## π§° Available Tools (28 total)
<details>
<summary><strong>UI Inspection</strong> β Screenshots, DOM, styles</summary>
| Tool | Description |
|------|-------------|
| `zotero_screenshot` | Capture window, element, or region screenshots |
| `zotero_inspect_element` | Find elements by CSS selector |
| `zotero_get_dom_tree` | Get DOM structure of a window/panel |
| `zotero_get_styles` | Get computed CSS styles for element |
| `zotero_list_windows` | List all open Zotero windows |
> **Screenshot Targets**: Main window, preferences, PDF reader, dialogs, or any element by selector. Use `highlightSelector` to add a red border before capture.
</details>
<details>
<summary><strong>UI Interaction</strong> β Click and type in the Zotero UI</summary>
| Tool | Description |
|------|-------------|
| `zotero_click_element` | Click an element by CSS selector (toolbar/menu button, preference control, list row). Pierces shadow DOM; `index` picks among multiple matches; `mouseEvents` synthesizes a full mouse sequence. |
| `zotero_send_keys` | Type text into an input/textarea/contenteditable (focuses it first, fires input/change). Optional `clear` and `pressEnter`. |
> Resolution tries light DOM first, then pierces open shadow roots (Zotero's XUL custom elements keep internals in shadow DOM). **Limitation**: cannot dismiss a *blocking* native modal dialog (`Services.prompt.confirmEx`) β its nested modal loop blocks the eval thread these tools run on.
</details>
<details>
<summary><strong>JavaScript Execution</strong> β Run code in Zotero context</summary>
| Tool | Description |
|------|-------------|
| `zotero_execute_js` | Execute JavaScript in Zotero's privileged context. Auto-wraps code with top-level `return` statements in IIFE. |
| `zotero_inspect_object` | Explore Zotero APIs - list methods and properties of any object (e.g., `Zotero.Items`) |
| `zotero_open_preferences` | Open Zotero's settings window, optionally to a specific pane (built-in or plugin) |
| `zotero_search_prefs` | Search/discover preferences by pattern (e.g., find all prefs containing "debug") |
| `zotero_get_pref` | Get a preference value |
| `zotero_set_pref` | Set a preference value |
> **Examples**: `Zotero.Items.getAll(1)`, `Zotero.Prefs.get('export.quickCopy.setting')`, `ZoteroPane.getSelectedItems()`
>
> **Tip**: Use `zotero_inspect_object` to explore APIs before writing code. Use `zotero_search_prefs` to discover preference keys.
</details>
<details>
<summary><strong>Build & Scaffold</strong> β Integration with zotero-plugin-scaffold</summary>
| Tool | Description |
|------|-------------|
| `zotero_scaffold_build` | Build plugin (dev or production mode) |
| `zotero_scaffold_serve` | Start dev server with hot reload |
| `zotero_scaffold_lint` | Run ESLint on plugin source |
| `zotero_scaffold_typecheck` | Run TypeScript type checking |
</details>
<details>
<summary><strong>Logs & Debugging</strong> β Error console and debug output</summary>
| Tool | Description |
|------|-------------|
| `zotero_read_logs` | Read debug output (Zotero.debug) |
| `zotero_read_errors` | Read error console entries |
| `zotero_watch_logs` | Stream logs in real-time |
| `zotero_clear_logs` | Clear log buffer |
</details>
<details>
<summary><strong>Plugin Management</strong> β Install, reload, inspect</summary>
| Tool | Description |
|------|-------------|
| `zotero_plugin_reload` | Hot reload your dev plugin |
| `zotero_plugin_install` | Install plugin from XPI path |
| `zotero_plugin_list` | List installed plugins with version/status |
</details>
<details>
<summary><strong>Database Access</strong> β Read-only SQLite access</summary>
| Tool | Description |
|------|-------------|
| `zotero_db_query` | Execute SELECT query on zotero.sqlite |
| `zotero_db_schema` | Get table schema information |
| `zotero_db_stats` | Get database statistics (items, attachments, collections, size) |
> **Note**: Database access is read-only and requires Zotero to be closed, or uses a copy of the database.
</details>
---
## ποΈ Architecture
```
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AI Assistant β
β (Claude, Cursor, Windsurf) β
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β MCP Protocol (stdio)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MCP Server (Node.js/TypeScript) β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββββββ β
β β Scaffold β β RDP β β Database β β
β β Integration β β Client β β Reader β β
β ββββββββββββββββ ββββββββ¬ββββββββ ββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ
β Firefox RDP (port 6100)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Zotero Application β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β MCP Bridge for Zotero β β
β β Starts DevToolsServer on launch β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Firefox DevTools Server (built-in) β β
β β JS Execution β’ DOM β’ Console β’ Screenshots β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Your Plugin (dev) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
```
**Why this approach?**
- β
**Lightweight plugin** β Just enables RDP, Firefox DevTools does the rest
- β
**Zero-config after install** β Just open Zotero normally, no special flags
- β
**Rich AI context** β Screenshots, DOM, and logs help the AI understand your plugin's state
- β
**Hot reload** β Integrates with zotero-plugin-scaffold for instant feedback
- β
**Full Zotero access** β Execute any Zotero API in the privileged context
- β
**Cross-platform** β Works on Linux, Windows, macOS
---
## π§ Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `ZOTERO_RDP_PORT` | Remote debugging port | `6100` |
| `ZOTERO_RDP_HOST` | Debugging host | `127.0.0.1` |
| `ZOTERO_DATA_DIR` | Path to Zotero data directory | Auto-detect |
| `ZOTERO_PROFILE_PATH` | Path to Zotero profile | Auto-detect |
---
## π Changing the RDP Port
The bridge listens on port `6100` by default. You only need to change it if you run **two Zotero instances at the same time** (a normal profile and a development one, say), or if another process already holds 6100.
The port lives on **both sides of the bridge**, and both have to agree on it.
**1. Zotero side** β set the plugin preference:
1. **Settings β Advanced β Config Editor**, and accept the warning
2. Search for `extensions.mcp-rdp.port`
3. If it doesn't exist, create it: select **Number**, name it `extensions.mcp-rdp.port`, and enter your port
4. **Restart Zotero** β the listener only opens at startup
> **Watch the type.** The Config Editor pre-selects **Boolean**. Creating the preference without switching to **Number** stores `true` instead of a port, and Zotero then opens the bridge on a local pipe rather than a TCP port β the debug log reports success while no MCP client can connect.
**2. Client side** β set `ZOTERO_RDP_PORT` to the same value in your MCP client config:
```json
{
"mcpServers": {
"zotero-dev": {
"command": "npx",
"args": ["-y", "@introfini/mcp-server-zotero-dev@1.1.2"],
"env": {
"ZOTERO_RDP_PORT": "6101"
}
}
}
}
```
> **Change both or neither.** Moving only one side disconnects the bridge: Zotero listens on one port while the client keeps dialing the other.
### Actually running two instances
Launching Zotero a second time hands you the window you already have β like Firefox, it forwards to the running instance instead of starting another. A second instance needs its own profile *and* `-no-remote`:
```bash
# macOS; adjust the binary path on Windows/Linux
MOZ_NO_REMOTE=1 "/Applications/Zotero.app/Contents/MacOS/zotero" -P <profile-name> -no-remote
```
Give that profile its own `extensions.mcp-rdp.port` and the two bridges stay out of each other's way. Verified with 9.0.6 on 6100 and 10.0-beta.22 on 6101 at the same time.
> **Requires MCP Bridge plugin 1.0.5 or later.** In 1.0.4 and earlier, `extensions.mcp-rdp.port` was read under the wrong preference branch and silently ignored, so the bridge stayed on 6100 no matter what you set. If you configured a custom port against an older build, it is stored as `extensions.zotero.extensions.mcp-rdp.port` β that name still works, but prefer the one above.
### Disabling the bridge
Set `extensions.mcp-rdp.enabled` to `false` (**Boolean**) in the Config Editor and restart Zotero. The plugin stays installed but opens no listener, and no MCP client can reach Zotero until you set it back to `true`.
### Checking what the bridge did
The plugin appends one line per lifecycle **transition** to `mcp-rdp-events.log` in your Zotero **profile** directory: startup, listener open, listener down, listener recovered, listener flapping, shutdown. It survives restarts and is readable without Zotero running, which makes it the first place to look when an MCP client reports `Cannot connect to Zotero RDP`:
```
2026-09-17T07:52:36.201Z startup v1.0.5 reason=1
2026-09-17T07:52:37.914Z listener DOWN on port 6177 - failed to open at startup: port 6177 does not answer (held by another process?)
2026-09-17T07:53:46.552Z listener RECOVERED on port 6177 after 6 failed checks, 69s down
2026-09-17T08:01:12.083Z shutdown v1.0.5 reason=2
```
What to read from it:
- **`listener DOWN β¦ failed to open at startup`** β something else holds the port: another Zotero instance, a previous one that has not released it, or a process that answers on the port without speaking RDP. The reason after the colon says which of the last two it is.
- **`listener DOWN β¦ stopped answering`** β the listener was up and then died. The health check reopens it; the next line tells you when that worked and how long the gap was.
- **`listener RECOVERED β¦ after N failed checks`** β the bridge came back on its own. A large N means the port was held for a long time; nothing is logged per attempt, so the file stays short no matter how long the outage.
- **`listener FLAPPING`**, later closed by **`listener STEADY β¦ after N flaps`** β the listener keeps dying and coming straight back on the first reopen. That is a different fault from an outage: the port is yours, something is tearing the listener down. The whole run costs these two lines however long it goes on, so N is the number to report if you open an issue.
- **A `startup` with no `shutdown` before it** β Zotero was killed or crashed rather than exiting cleanly. Usually the answer to "the bridge stopped working" is simply that Zotero is not running.
- **No new lines at all** β the plugin never started: disabled by preference, or not installed in the profile you are actually running.
`log()` output goes to `dump()` (lost unless Zotero was started from a console) and `Zotero.debug()` (a no-op unless debug output is enabled), so this file is the only durable record of a boot-time failure. A healthy session adds three lines (startup, open, shutdown); an outage adds two more, and a run of flaps two more, whatever their length. No failure mode writes per tick.
---
## πΈ Screenshot Examples
```typescript
// Capture main Zotero window
await zotero_screenshot({ target: 'main-window' });
// Capture your plugin's panel with highlight
await zotero_screenshot({
target: 'element',
selector: '#my-plugin-panel',
highlightSelector: '#my-plugin-button'
});
// Capture a specific window by ID (use zotero_list_windows to find IDs)
await zotero_screenshot({
target: 'window',
windowId: 12345
});
// Capture element after triggering UI action
await zotero_execute_js({ code: 'document.querySelector("#menu").click()' });
await zotero_screenshot({ target: 'element', selector: 'menupopup[state="open"]' });
```
---
## π§βπ» Development
```bash
# Clone and install
git clone https://github.com/introfini/mcp-server-zotero-dev.git
cd mcp-server-zotero-dev
npm install
# Build everything
npm run build
# Build individual packages
npm run build:server
npm run build:plugin
# Run tests
npm test
# Development mode (watch)
npm run dev
```
<details>
<summary><strong>Project Structure</strong></summary>
```
mcp-server-zotero-dev/
βββ packages/
β βββ mcp-server/ # MCP server (npm package)
β β βββ src/
β β β βββ index.ts # MCP server entry
β β β βββ rdp/ # RDP client
β β β βββ tools/ # Tool implementations
β β β βββ prompts/ # Slash commands
β β βββ package.json
β β
β βββ zotero-plugin-mcp-rdp/ # Tiny Zotero plugin (.xpi)
β βββ src/
β β βββ bootstrap.js # Starts RDP server (shipped verbatim)
β βββ addon/
β β βββ manifest.json
β βββ package.json
β
βββ docs/ # Documentation
βββ package.json # Monorepo root
```
</details>
---
## π Resources
- **[Architecture & Technical Learnings](ARCHITECTURE.md)** β Deep dive into RDP protocol, actor hierarchy, and common pitfalls
- **[Zotero Plugin Development](https://www.zotero.org/support/dev/client_coding/plugin_development)** β Official docs
- **[Zotero 10 for Developers](https://www.zotero.org/support/dev/zotero_10_for_developers)** β Migration guide for the latest major
- **[Zotero 7 for Developers](https://www.zotero.org/support/dev/zotero_7_for_developers)** β Migration guide
- **[zotero-plugin-scaffold](https://github.com/windingwind/zotero-plugin-scaffold)** β Build tooling
- **[zotero-plugin-template](https://github.com/windingwind/zotero-plugin-template)** β Starter template
- **[zotero-plugin-toolkit](https://github.com/windingwind/zotero-plugin-toolkit)** β API helpers
- **[Firefox RDP Protocol](https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html)** β Protocol docs
---
## π€ Contributing
Contributions are welcome. See **[CONTRIBUTING.md](CONTRIBUTING.md)** for setup, test conventions and the codebase-specific rules worth knowing before you start.
The short version:
1. Follow existing code patterns
2. Add tests for new features, and skip rather than fail when Zotero is not running
3. Update documentation
4. There is no CI, so run `npm run build`, `npm run typecheck`, `npm run lint` and `npm test` yourself, and say in the PR which Zotero version you verified against
---
## π License
MIT Β© [introfini](https://github.com/introfini)
---
## Acknowledgments
- Built for the [Zotero](https://www.zotero.org) plugin developer community
- Integrates with [zotero-plugin-scaffold](https://github.com/windingwind/zotero-plugin-scaffold) by [@windingwind](https://github.com/windingwind)
- Leverages Firefox DevTools RDP for reliable communication
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessResponsive