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`.
---
## πΈ 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