
Cite Caddy
dk.herbertkokholm.citecaddy由 citecaddy.herbertkokholm.dk 发布官方发布者v2.3.13更新于 Nov 3, 2026
用于对 Zotero 文库进行完整读写访问的远程 MCP 服务器
AI 翻译由 AI 模型根据下方的原文描述翻译,可能有误。
原文描述
Remote MCP server for full read/write access to a Zotero library
概览
Cite Caddy 的托管 MCP 服务(citecaddy.herbertkokholm.dk),由 citecaddy.herbertkokholm.dk 发布;需要 OAuth 登录;Streamable HTTP。
暂无 AI 概览:以下信息来自注册中心条目和服务本身。
- 发布者
- citecaddy.herbertkokholm.dk
- 端点
- citecaddy.herbertkokholm.dk
- 最近检查
安装
在 SourceWeft 中
- 打开 控制台中的 Cite Caddy,将其添加到工作区。
- 连接 所需的 凭据。它们会被私密存储,绝不会在公开页面上展示。
- 为需要使用其工具的对话启用该服务。
Web executable,通过 Streamable HTTP。 远程服务在工作区中配置后即可从网页运行时运行。
交给智能体安装
把这段提示词粘贴到 Claude Code、Codex、Cursor 等智能体,或 SourceWeft 对话中。智能体会读取该服务器的安装指南,先向你展示发布者、端点和登录方式,经你同意后再把它添加到你的客户端。密钥不会写进任何命令。
阅读 https://sourceweft.com/mcp/dk.herbertkokholm.citecaddy%2Fcite-caddy/install.md,并按其中说明把这个 MCP 服务器接入我的客户端。修改任何配置之前,先告诉我它的发布者、端点和登录方式,等我确认。不要把 API 密钥写进命令或我会提交的文件;问我密钥应从哪里获取。其他 MCP 客户端
把它添加到你客户端的 mcpServers 配置中。 客户端会在首次连接时完成 OAuth 登录。
{
"mcpServers": {
"cite-caddy": {
"type": "http",
"url": "https://citecaddy.herbertkokholm.dk/mcp"
}
}
}README
Cite Caddy
[cite-caddy MCP server] [smithery badge] [MCP Registry] [MCPBundles] [Lint] [Latest release] [License: MIT]
A reference-library bridge for AI assistants — a standalone, remote MCP server.
Independent, unofficial project. Not affiliated with, endorsed by, or sponsored by the Corporation for Digital Scholarship (Zotero) — see Zotero's trademark policy. Built on the Zotero Web API; today's backend is Zotero, but the name and tool surface are meant to support others later.
Cite Caddy gives full read/write access to a Zotero library — search, add, tag, update, delete, and move items; create, rename, and delete collections; upload/download attachments and read their extracted full text; read and write item notes; manage tags, trash, and saved searches library-wide; and look up Zotero's own item-type/field schema. 39 tools total — see Tools below for the full list.
Why this exists
Read-only tools that match findings against a Zotero library (e.g. by DOI/arXiv ID) can safely stop at reporting — they never need to write anything back. This project goes further on purpose: full CRUD against a Zotero library, including delete and move, so that tagging, adding, and cleaning up items can be automated too.
That's a deliberate scope choice, and it comes with a real risk: any write that changes an existing item's key (delete, move to another library, "clean library" reset) breaks Word documents that cite it via the Zotero Word plugin's live field codes — see "Key safety" below before touching delete/move.
Key safety (read this before implementing delete/move)
Any Zotero item cited in a Word document via the Zotero Word plugin is referenced by that item's key, embedded in a live field code. Operations that preserve an item's key (create, update fields, add/remove tags, add notes) are safe. Operations that don't (delete, and library-to-library move, which Zotero implements as delete+recreate) will break those citations silently — the Word document won't error, it'll just show stale/broken field text next time someone updates fields or opens Zotero the next time.
Full CRUD was chosen deliberately for this project despite that risk. When implementing delete/move tools:
- Make the destructive intent obvious in the tool name and docstring (an MCP client's model reads both before calling), not just in this README.
- Consider requiring the caller to pass back the item's current Zotero
version(optimistic concurrency) so a delete/update can't silently clobber a change made concurrently from the Zotero desktop app or another client. - A dry-run / confirmation step for delete is worth considering, but is an implementation decision for whoever builds that tool, not decided here.
Idempotency
delete_item_permanently, delete_collection, delete_tag,
delete_saved_search, move_item_to_different_library, and
update_publication_status all accept an optional idempotency_key. Pass
the same opaque string when retrying a call after a lost or ambiguous
response (e.g. a network timeout) and the original outcome — success or
error — is replayed instead of running the operation against Zotero again.
Reusing a key for a call with different arguments raises an error instead of
silently returning the old result, so it's safe to generate one key per
logical request and reuse it freely on retries of that same request.
This matters most for move_item_to_different_library: it recreates the
item in the target library, then deletes it from the source. If the create
succeeds but the delete then fails, a bare retry would redo the whole thing
— since the source item's version hasn't changed — creating a second
duplicate in the target library. With idempotency_key, the retry replays
the cached failure (and its "clean up manually" guidance) instead of
touching Zotero again.
The cache is in-memory per server process (per onboarded tenant in HTTP mode), with a 24h TTL — it survives retries within that window, not across a redeploy/restart.
Configuration
stdio mode (local, single-user — no $PORT): the library to connect to
comes from env vars.
HTTP mode (remote, multi-tenant — $PORT set): there's no single
configured library — each caller brings their own Zotero Library ID/Type/API
Key via the /login form (see "Deployment" below). Instead:
Optional in either mode:
Deployment
Ships as its own Docker container (docker-compose.yml), meant to sit
behind a reverse proxy that terminates TLS and forwards to the container's
port on localhost. Remote/hosted by default, not a local stdio server — an
MCP client just points at the URL, nothing to install or run locally.
Access is gated by a real OAuth 2.1 authorization server built into the
app itself (app/oauth_provider.py), not HTTP Basic Auth in front of it.
This is a deliberate design choice: Claude Desktop/claude.ai's "Add custom
connector" UI is OAuth-first — it always tries the OAuth discovery +
authorization-code dance against a new server, so a plain 401 in front of
the server (as Basic Auth would produce) gets read as "this server needs
OAuth" and fails once it hits a nonexistent /authorize endpoint.
Implementing a real (if minimal) OAuth server is what makes "Add custom
connector" work.
Multi-tenant and self-service: /authorize doesn't delegate to a
third-party identity provider — it shows a first-party login form asking
for a Zotero Library ID, Library Type, and API Key. Submitting the form
validates the key directly against the Zotero API; a successful
validation both grants access and registers ("onboards") that library as
a tenant of this server, all in one step — there's no separate sign-up
and no admin approval. Any MCP client can dynamically register itself
(RFC 7591), but completing the login form with a working Zotero key is
what actually gates access. Each caller's tool calls are then routed to
their own Zotero library, not a shared one. See
app/oauth_provider.py's module docstring for the full flow. Registered
clients, issued tokens, and onboarded tenants' credentials (API keys
encrypted at rest with MCP_TOKEN_STORE_KEY) persist to MCP_DATA_DIR
(a Docker volume) so redeploys don't log connected clients out or forget
onboarded tenants.
.env on the host (not in this repo) holds MCP_TOKEN_STORE_KEY/
MCP_PUBLIC_URL, consumed via docker-compose.yml's env_file:.
ZOTERO_LIBRARY_ID/ZOTERO_LIBRARY_TYPE/ZOTERO_API_KEY are not needed
for the HTTP deployment — those only apply to stdio mode.
.github/workflows/deploy.yml automates redeploying to an already
set-up host: manual trigger only (workflow_dispatch, never on push),
runs the test suite first, then syncs the repo over SSH and rebuilds the
container. It needs its own GitHub Actions secrets for the deploy SSH
key and target host/port/user — see the workflow file for the full list.
Use a dedicated deploy key (not whatever key you use for direct/manual
access), so it can be revoked independently if it ever leaks.
Monitoring
Four unauthenticated GET endpoints, HTTP mode only (all require $PORT,
same as /login):
-
/healthz— plain200 OK, for a load balancer/uptime check. -
/status— JSON snapshot of aggregate, process-level activity:tenantsisTokenStore.tenant_count()— just the number of onboarded Zotero libraries.tool_calls/tool_errorsare per-tool-name counts across all tenants combined, recorded by atools/callmiddleware (_track_tool_callinapp/mcp_server.py). Deliberately no per-tenant or per-library breakdown anywhere in this response — that's what keeps it safe to leave unauthenticated, unlike the 39 tools themselves. Counters live inapp/metrics.py, in-memory only: they reset to zero on every restart/redeploy, same as this isn't a metrics/analytics system, just a lightweight "is it up and roughly how busy is it" signal. -
/status.html— same data as/status, rendered as a small page (name + icon, one table of version/uptime/tenants, one table of per-tool call/error counts) for a human checking in a browser rather than a script. Icon only renders whenMCP_WEBSITE_URLis set, same as/login's. -
/.well-known/security.txt— RFC 9116 security contact, only whenMCP_SECURITY_CONTACTis set (404 otherwise).Expiresis computed per request as one year out, so it never goes stale. MCPBundles reads this to verify ownership when publishing the server to its directory. -
/.well-known/mcp/server-card.json— a pre-connection discovery document (server identity, auth requirements, and the full tool list with schemas), generated live from the actual tool registry on every request so it can't drift out of sync. Not a ratified standard: this is a pragmatic approximation of SEP-2127 ("MCP Server Cards — HTTP Server Discovery", superseding the withdrawn SEP-1649), which is still an open, unmerged proposal as of this writing — even its own well-known path has changed between drafts (some revisions use.well-known/ai-catalog.jsoninstead) — it's not a general claim of spec compliance, and may need to change if/when SEP-2127 (or a successor) actually ratifies with a different contract.
Tools
39 tools total, grouped by risk (see "Key safety" above before using any
Destructive tool). Tools marked ✓ under Version require the
item's/collection's current Zotero version (from
search_items/get_item/list_collections) as an argument and refuse
the call if it's stale, rather than silently overwriting a concurrent
change.
Testing
Tests never call a live Zotero library, even if .env has real
credentials: app/zotero_service.py (all Zotero read/write logic) is
exercised against tests/fakes.py's in-memory FakeZotero, and
app/mcp_server.py's tool functions are tested directly against a
ZoteroService backed by that fake (see configure_service()).
Status
v2.3 — deployed and in active use, with full CRUD coverage of the Zotero Web API's item/collection/tag/trash/saved-search/schema surface (39 tools; see Tools). Add it as a remote MCP connector directly (e.g. Claude Desktop/claude.ai's "Add custom connector" with just the server's public URL) — the OAuth flow described above prompts for your own Zotero Library ID/Type/API Key in-browser, no manually-configured headers needed, and no admin sign-up step.
Listed in the official MCP Registry
as dk.herbertkokholm.citecaddy/cite-caddy — metadata lives in
server.json, published via mcp-publisher and DNS-verified
against citecaddy.herbertkokholm.dk. Not (yet) part of GitHub's separate,
manually-curated github.com/mcp directory, which
doesn't sync automatically from the open registry.
Known limitations
Tracked gaps against the MCP 2026-07-28 specification
("stateless core, enterprise authorization, extensions framework"). None
are currently exploitable or user-facing — each is either inert until an
upstream mcp SDK change, or already mitigated — but are documented here
so they're visibly known rather than silently absent.
-
OAuth
issparam (RFC 9207) sent but not advertised. The final authorization redirect built incomplete_login()(app/oauth_provider.py) includesiss, which hardens the flow against mix-up attacks (RFC 9207 §2). The matchingauthorization_response_iss_parameter_supportedflag in/.well-known/oauth-authorization-serveris not set: that document is generated by themcpSDK, which has no setting for it as ofmcp2.3.0. Without the flag, clients don't have to validateiss, but clients that do validate it get the protection anyway. -
Dynamic Client Registration (RFC 7591) instead of CIMD. The same spec update formally deprecates Dynamic Client Registration in favor of Client ID Metadata Documents (CIMD), though DCR remains functional for backward compatibility. This server's client auto-provisioning (
_FlexibleClientInformation/register_client/get_clientinapp/oauth_provider.py) is built on DCR — needed because some MCP clients (observed: Claude Desktop/claude.ai) skip registration and send/authorizean unregisteredclient_iddirectly (see that class's docstring). No action needed while the installed SDK keeps DCR working without warning; will need a CIMD-based replacement if/when that changes. -
rename_tagcan block on large libraries — tracked as #6; candidate for the spec'stasksextension.mcp2.3.0 has a generic extension framework but no Tasks implementation yet.
Contributing
See CONTRIBUTING.md.
Security
See SECURITY.md for the threat model and how to report a vulnerability.
Privacy
See Privacy Policy for
what the server stores when you sign in at /login (Zotero Library
ID/Type/API key), how it's protected, and how to have it deleted.
License
来源:README.md,提交 555ca7d
工具
0版本历史
7- v2.3.13最新Nov 3, 2026
- v2.3.11Oct 12, 2026
- v2.3.9Oct 1, 2026
- v2.3.8Oct 1, 2026
- v2.3.7Sep 27, 2026
- v2.3.6Sep 17, 2026
- v2.3.5Sep 16, 2026
评分
已满足适用检查项的 85%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通必需25 分
- 已声明认证方式必需10 分
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分