The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the MCP Server Zotero Dev listing page.
Give your AI assistant superpowers for Zotero plugin development
Architecture · Getting Started · Available Tools
A Model Context Protocol (MCP) server that enables AI assistants like Claude, Cursor, and Windsurf to build, test, and debug Zotero 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.
| 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 |
Use install-mcp to add the server to your AI assistant:
Supported clients: claude-code, cursor, windsurf, vscode, cline, roo-cline, claude, zed, goose, warp, codex
Add to your MCP client config:
Version & updates: pin an exact version as shown above. A bare
npx <pkg>(no version) keeps running whatevernpxcached and won't pick up new releases, so always include a version and-y(without-y,npxhangs waiting for an install prompt). Bump the pinned version to upgrade, or use@latestto 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 thatinstall-mcpmay write a config without-yor a version, so the manual configuration above is the most robust path.
Restart your AI assistant after adding the configuration.
Download zotero-mcp-bridge.xpi and install:
.xpi fileThis 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).
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. 🎉
| 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
highlightSelectorto add a red border before capture.
| 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.
| 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_objectto explore APIs before writing code. Usezotero_search_prefsto discover preference keys.
| 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 |
| 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 |
| 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 |
| 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.
Why this approach?
| 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 |
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:
extensions.mcp-rdp.portextensions.mcp-rdp.port, and enter your portWatch the type. The Config Editor pre-selects Boolean. Creating the preference without switching to Number stores
trueinstead 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:
Change both or neither. Moving only one side disconnects the bridge: Zotero listens on one port while the client keeps dialing the other.
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:
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.portwas 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 asextensions.zotero.extensions.mcp-rdp.port— that name still works, but prefer the one above.
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.
Contributions are welcome. See CONTRIBUTING.md for setup, test conventions and the codebase-specific rules worth knowing before you start.
The short version:
npm run build, npm run typecheck, npm run lint and npm test yourself, and say in the PR which Zotero version you verified againstMIT © introfini