Zotero-MCP-Server: Architekturanalyse einer instabilen Codebasis
Der zotero-mcp-server war unser Einstieg in den MCP-Weg für Zotero. In der Praxis fiel uns wiederholt eine gewisse Instabilität auf: Abstürze bei parallelen Anfragen, Timeouts und hängende Aufrufe. Eine Analyse des Quellcodes durch DeepSeek V4 Flash identifizierte mehrere architekturelle Faktoren, die dazu beitragen. Dieser Beitrag dokumentiert die Ergebnisse und vergleicht die Architektur mit der des Alternativservers Zoteus.
Architekturanalyse von DeepSeek V4 Flash (Qwen Code), August 2026. Michael Logies wählte DeepSeek V4 Flash basierend auf Benchmarks aus der Zotero-Sammlung AI-, KI-Modelle (artificialanalysis.ai, August 2026).
📋 Inhalt
- Vorgehen
- Der globale RLock – Flaschenhals Nr. 1
- pyzotero: harte Abhängigkeit, hartes Timeout
- Fehlendes Session-Management
- Kein HTTP-Connection-Pooling
- Die Zotero Local API als zusätzlicher Flaschenhals
- FastMCP-Framework: Startzeit und Lifecycle
- Vergleich: Zoteus-Architektur
- Zoteus: Aktuelle Grenzen und Patches
- Fazit
Vorgehen
Die Analyse basiert ausschließlich auf dem öffentlich zugänglichen Quellcode beider Projekte auf GitHub:
- Zotero-MCP: github.com/54yyyu/zotero-mcp (Python, FastMCP, pyzotero, 4.800 Sterne)
- Zoteus: github.com/oscardvs/zoteus (TypeScript, MCP SDK, Express, 28 Sterne)
Es wurden folgende Dateien untersucht (Zotero-MCP v0.9.x (inzwischen v0.10.0) / Zoteus v1.7.0, Stand 25.8.2026): src/zotero_mcp/client.py, src/zotero_mcp/_app.py, pyproject.toml (Zotero-MCP) sowie src/server.ts, src/transports/http.ts, src/features/search/ (Zoteus). Die Analyse ist eine Momentaufnahme vom 23. August 2026.
✅ Update 24.8.2026: Bug-Reports bestätigt und behoben
Die von uns gemeldeten GitHub-Issues #456 (advanced_search-Timeout) und #447 (Notes unlesbar) wurden vom Entwickler in PR #481 (v0.10.0) bestätigt und behoben. Die fünf in dieser Analyse beschriebenen Architekturprobleme (RLock, pyzotero-Timeout, Session-Management, Connection-Pooling, Shutdown) bestehen unverändert fort.
✅ Update 25.8.2026: Zoteus v1.7.0 – konfigurierbare Embeddings
Zoteus wurde auf v1.7.0 (PR #17) aktualisiert. Die drei hartcodierten OpenAI-Embedding-Parameter (Batch-Größe, Rate-Limit-Delay, Modell) sind jetzt über Umgebungsvariablen konfigurierbar: ZOTEUS_EMBEDDING_MODEL (text-embedding-3-large), ZOTEUS_EMBED_BATCH_SIZE (500) und ZOTEUS_EMBED_BATCH_DELAY_MS (6000). Die lokalen Patches aus Issue #15 sind damit obsolet. Der Test mit 50 Items war erfolgreich. Die fehlende inkrementelle Index-Update-Funktion (Issue #16) besteht jedoch weiterhin – der Zotero-MCP-Server (ChromaDB) übernimmt daher weiterhin die semantische Suche für die Vollbibliothek.
Der globale RLock – Flaschenhals Nr. 1
Die zentrale Entdeckung befindet sich in src/zotero_mcp/client.py, Zeilen 35–70:
_zotero_api_lock = threading.RLock()
_DEFAULT_LOCK_TIMEOUT = 45.0
class ZoteroApiBusyError(RuntimeError):
"""Raised when the per-process Zotero API lock can't be acquired in time."""Der Server verwendet einen globalen Reentrant Lock (RLock), der jede einzelne Zotero-Operation serialisiert. Während ein Tool auf die API wartet (z.B. PDF-Upload, große Query, hängende Cloud-Operation), blockiert es alle anderen Tools – auch reine Lesezugriffe.
Der 45-Sekunden-Timeout ist ein Pflaster, keine Lösung. Die Entwickler schreiben selbst im Kommentar:
"A single slow/stuck op (e.g. a hung cloud write or PDF upload) holds the lock and every other tool — reads included — blocks behind it until FastMCP's ~60s client timeout fires, surfacing as an opaque '-32001 Request timed out' on every queued call."
Das Problem: Der RLock verhindert nicht nur parallele Zugriffe, sondern auch die gleichzeitige Bearbeitung unabhängiger Anfragen. In einer Multi-Agenten-Umgebung (drei KI-Agenten parallel) ist das fatal.
pyzotero: harte Abhängigkeit, hartes Timeout
Der Server nutzt pyzotero als einzige Brücke zu Zotero. Diese Python-Bibliothek hat einen hartcodierten 30-Sekunden-Timeout für HTTP-Anfragen, den der Server nicht konfigurieren kann. Die einzige Gegenmaßnahme ist der globale RLock mit 45s-Timeouterwartung – ein unfreiwilliges Zusammenspiel zweier Timeouts.
Wenn pyzoteros 30s-Timeout greift, hält der RLock den Fehler für weitere 15s, bis er selbst timed out. In dieser Zeit sind alle anderen Tools blockiert. Das erklärte die immer wieder beobachteten "hängenden Aufrufe", die keinen Fehler zurückgaben, sondern einfach nichts taten.
Hinzu kommt: Der Server verwendet zwei HTTP-Bibliotheken – httpx für die lokale API und requests als allgemeine Abhängigkeit. Das ist historisch gewachsen und erhöht die Komplexität ohne erkennbaren Nutzen.
Fehlendes Session-Management
Der Zotero-MCP-Server hat kein Konzept von Sessions. Es gibt einen Client, eine Verbindung, einen globalen Zustand. Wenn die Verbindung abbricht (z.B. durch SSH-Timeout oder Server-Neustart), gibt es keine Wiederherstellungslogik – der Client muss die Session komplett neu starten.
Im server_lifespan-Handler in _app.py sieht das so aus:
yield {}
sys.stderr.write("Shutting down Zotero MCP server...\n") # das war'sKein Ressourcen-Cleanup, keine Connection-Closing, keine Thread-Synchronisation. Worker-Threads werden einfach fallen gelassen, mit dem Kommentar "left to finish on their own".
Kein HTTP-Connection-Pooling
Die Methode _make_local_http_client() erzeugt bei jedem Aufruf von get_zotero_client() einen neuen httpx.Client:
def _make_local_http_client() -> httpx.Client:
return httpx.Client(
transport=httpx.HTTPTransport(http1=True, http2=False),
follow_redirects=True,
)Kein Pooling, kein Reuse von TCP-Verbindungen. Jeder API-Aufruf baut eine neue HTTP-Verbindung zur lokalen Zotero-API auf. Bei vielen sequenziellen Aufrufen summiert sich der Overhead.
Die Zotero Local API als zusätzlicher Flaschenhals
Die lokale Zotero-API (Port 23119) ist ein Single-Thread-Server, der nur HTTP/1.0 spricht. Der Server muss httpx zwingen, auf HTTP/1.1 zu bleiben – ein expliziter Workaround für einen bekannten Bug (#160 im Zotero-MCP-Repo). Ohne diesen Workaround antwortet die lokale API mit 502 Bad Gateway.
Der HTTP-Transport (Port 8000, seit 17.8.2026) half gegen SSH-Abbrüche, änderte aber nichts an den grundlegenden Server-Problemen. Der Flaschenhals blieb die Zotero Local API + der globale RLock.
FastMCP-Framework: Startzeit und Lifecycle
Der Server ist eng an das FastMCP-Framework (v2.14+) gebunden. FastMCP hat einen 60-Sekunden-Timeout für den MCP-Initialize-Handshake. Der Server musste die semantische Suche und Schema-Aktualisierung in Hintergrund-Threads auslagern, weil sie sonst den Start blockierten. Das ist ein Zeichen dafür, dass die Lifecycle-Architektur nicht für die Aufgaben des Servers ausgelegt ist.
Vergleich: Zoteus-Architektur
Zoteus (TypeScript, Node.js) hat eine grundlegend andere Architektur:
| Aspekt | Zotero-MCP | Zoteus |
|---|---|---|
| Sprache | Python | TypeScript |
| Nebenläufigkeit | Globaler RLock (alles serialisiert) | Per-Session-Kontext (ContextCache, bis 50 Sessions) |
| Transport | FastMCP intern (HTTP/SSH) | Express + MCP SDK StreamableHTTP |
| Session-Management | Keins (ein Client, keine Wiederherstellung) | create → evict → close, max 50 Sessions |
| Shutdown | Nur Print | Drain-Hook + Timeout |
| Rate-Limiting | Keins | Express-rate-limit integriert |
| Health-Checks | Keine | Eigenes health-Modul |
| Sicherheit | Keine (API-Key im Klartext) | Loopback-only / OAuth |
| Connection-Pooling | Keins (neuer Client pro Aufruf) | Express hält Keep-Alive |
Zoteus wurde von Grund auf als modularer Server mit sauberen Schichten (transport → router → api → features → tools) entworfen, in einer Sprache ohne GIL, mit echtem Session-Management, Rate-Limiting und Graceful Shutdown. Die 28 GitHub-Sterne spiegeln das Alter wider, nicht die Qualität.
Zoteus: Aktuelle Grenzen (v1.7.0)
Trotz der überlegenen Architektur hat Zoteus in der aktuellen Version (v1.6.0) einige praktische Einschränkungen, die im GitHub-Issue #15 dokumentiert sind:
Semantische Suche mit OpenAI-Embeddings
Die semantische Suche (zotero_semantic_search) funktioniert. In v1.6.0 waren drei OpenAI-Embedding-Parameter hartcodiert und mussten per Patch angepasst werden. PR #17 (v1.7.0) hat alle drei als Umgebungsvariablen konfigurierbar gemacht:
- Batch-Größe:
ZOTEUS_EMBED_BATCH_SIZE(Standard 2048 → auf 500 gesetzt) - Batch-Verzögerung:
ZOTEUS_EMBED_BATCH_DELAY_MS(Standard 0 → auf 6000 gesetzt) - Embedding-Modell:
ZOTEUS_EMBEDDING_MODEL(vontext-embedding-3-smallauftext-embedding-3-largeumgestellt)
Die lokalen Patches aus Issue #15 sind damit obsolet. Der Index-Build wurde mit 50 Items erfolgreich getestet.
Fehlendes inkrementelles Index-Update
Der größte praktische Mangel in v1.6.0: Der Index-Build (zotero_index action: "build") führt immer einen vollständigen Rebuild durch – es gibt keinen Mechanismus, nur neue oder geänderte Items nachzuindexieren. Bei einer Bibliothek mit 5.000+ Items und Volltext (1 Mio. Zeichen pro Item) dauert ein Rebuild mit OpenAI-Embeddings 10+ Minuten. Das schließt regelmäßige Updates im laufenden Betrieb praktisch aus.
Ein Feature-Request (#16) für inkrementelle Updates wurde eingereicht. Der Zotero-MCP-Server (ChromaDB) zeigt mit zotero-mcp update-db --fulltext, wie es geht: Er scannt die lokale Zotero-SQLite-Datenbank, erkennt neue/geänderte Items und indexiert nur diese.
Fazit
Der Zotero-MCP-Server (v0.9.x, inzwischen v0.10.0) hat grundlegende Architekturprobleme, die durch HTTP-Transport oder Konfigurationsänderungen nicht behebbar sind:
- Globaler RLock – serialisiert alle Zugriffe, blockiert Lesevorgänge während Schreibvorgängen
- pyzotero-Abhängigkeit – hardcodiertes 30s-Timeout, nicht konfigurierbar
- Kein Session-Management – keine Wiederherstellung nach Verbindungsabbruch
- Kein Connection-Pooling – neue HTTP-Verbindung pro Aufruf
- Kein Graceful Shutdown – Ressourcen werden nicht freigegeben
Zoteus (v1.7.0) ist architektonisch überlegen. Die hartcodierten OpenAI-Embedding-Parameter (Batch-Größe, Rate-Limit-Delay, Modell) sind jetzt über Umgebungsvariablen konfigurierbar (PR #17). Die verbleibende Hürde ist das Fehlen eines inkrementellen Index-Updates (Issue #16). Ohne inkrementelles Update ist die semantische Suche für eine wachsende Bibliothek nicht praktikabel. Der Zotero-MCP-Server (ChromaDB) übernimmt daher weiterhin die semantische Indexierung, bis Zoteus diese Funktion bietet.
DeepSeek V4 Flash (0731), August 2026. Im Auftrag von Michael Logies. Der vollständige Quellcode beider Projekte ist auf GitHub öffentlich einsehbar.
