mcpField
C
Cite Caddy

Cite Caddy

远程服务
github.com/herbertkokholm生产力02 周前更新
代码仓库

关于 Cite Caddy

Cite Caddy 是供 AI 助手读写 Zotero 文献库的远程 MCP 桥接服务。其 39 个工具涵盖文献条目、分类、附件、笔记、标签和保存的搜索,并为版本冲突及重试操作提供保护。

主要功能

  1. 01搜索、创建、更新、删除和移动条目
  2. 02管理分类、标签和保存的搜索
  3. 03上传、下载附件并读取提取文本
  4. 04读写条目笔记
  5. 05部分写入操作支持版本检查和幂等键

适用场景

  1. 01研究时添加并标记参考文献
  2. 02读取文献库附件的提取文本
  3. 03整理分类和保存的搜索
  4. 04响应不明确时重试 Zotero 操作

MCP 相关

通过远程 HTTP 服务或本地 stdio 模式向 MCP 客户端提供 39 个 Zotero 文献库工具。HTTP 访问使用 OAuth 2.1,并将每位调用者的操作路由到其已接入的文献库。

配置

远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。

服务地址https://citecaddy.herbertkokholm.dk/mcp
一键复制安装命令
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。

工具

详细介绍

Cite Caddy

cite-caddy MCP server smithery badge MCP Registry MCPBundles Lint Latest release License: MIT

面向 AI 助手的参考文献库桥接工具——一个独立的远程 MCP 服务。

独立的非官方项目,与数字学术联盟(Zotero)无关联、未获其认可,也未受其赞助—— 请参阅 Zotero 的商标政策。 基于 Zotero Web API 构建;目前的后端是 Zotero,但项目名称和 工具接口旨在日后支持其他服务。

Cite Caddy 可对 Zotero 文献库进行完整的读写操作——搜索、添加、 标记、更新、删除和移动条目;创建、重命名和删除 分类;上传/下载附件并读取提取出的全文;读取和写入条目笔记;在整个文献库范围内管理标签、回收站和已保存的搜索, 并查询 Zotero 自身的条目类型/字段架构。共 39 个工具——完整列表请见下文的工具。

为什么要开发这个项目

只读工具可以将发现结果与 Zotero 文献库进行匹配(例如通过 DOI/arXiv ID),并安全地止步于报告结果——它们无需写回 任何内容。本项目有意更进一步:对 Zotero 文献库提供完整的 CRUD 操作,包括删除和移动, 以便也能自动完成标记、添加和清理条目。

这是有意做出的范围选择,但确实存在风险:任何会更改现有条目 key 的写操作(删除、移动到另一个文献库、 “清理文献库”重置)都会破坏通过 Zotero Word 插件的动态域代码引用该条目的 Word 文档——执行删除/移动前,请先阅读下文的“Key 安全性”。

Key 安全性(实现删除/移动前请先阅读)

在 Word 文档中通过 Zotero Word 插件引用的任何 Zotero 条目,都是通过该条目的 key 来引用的;这个 key 嵌入在动态域代码中。保留条目 key 的操作(创建、更新字段、添加/移除标签、添加笔记)是安全的。不保留 key 的操作(删除,以及 Zotero 通过删除并重新创建实现的跨文献库移动)会悄无声息地破坏这些引用——Word 文档不会报错,只会在下次有人更新域代码或再次打开 Zotero 时显示过时或损坏的域文本。

尽管存在这种风险,本项目仍有意选择提供完整的 CRUD 功能。实现删除/移动工具时:

  • 在工具名称和文档字符串中明确标示破坏性操作(MCP 客户端的模型在调用前会同时读取二者),不要只在本 README 中说明。
  • 可以考虑要求调用方传入条目当前的 Zotero version(乐观并发控制),以避免删除/更新操作悄无声息地覆盖通过 Zotero 桌面应用或其他客户端并发进行的更改。
  • 可以考虑为删除操作增加试运行/确认步骤,但这是工具构建者的实现决定,本项目不作规定。

幂等性

delete_item_permanently、delete_collection、delete_tag、 delete_saved_search、move_item_to_different_library 和 update_publication_status 均接受可选的 idempotency_key。在因响应丢失或不明确(例如网络超时)而重试调用时,传入相同的不透明字符串,即可重放原始结果(无论成功还是出错),而不会再次对 Zotero 执行操作。 如果使用同一个键调用时参数不同,则会报错,而不是静默返回旧结果,因此可以为每个逻辑请求生成一个键,并在重试该请求时放心地重复使用。

这对 move_item_to_different_library 尤为重要:它会在目标库中重新创建条目,然后将其从源库中删除。如果创建成功,但删除随后失败,直接重试就会重新执行整个操作——由于源条目的版本没有变化,因此会在目标库中再创建一个重复条目。使用 idempotency_key 后,重试会重放缓存的失败结果(以及“手动清理”的指引),而不会再次操作 Zotero。

缓存位于每个服务进程的内存中(HTTP 模式下按已接入的租户隔离),TTL 为 24h——它能保留此时间窗口内的重试结果,但在重新部署或重启后不会保留。

配置

stdio 模式(本地、单用户——未设置 $PORT):要连接的库由环境变量指定。

ZOTERO_LIBRARY_ID      numeric library ID (user or group)
ZOTERO_LIBRARY_TYPE    "user" or "group" (default: user)
ZOTERO_API_KEY         from Zotero -> Settings -> Security -> Applications
                        (needs write permission, not just read)

HTTP 模式(远程、多租户——已设置 $PORT):没有单一的预配置库——每位调用者通过 /login 表单提供自己的 Zotero Library ID/Type/API Key(见下文“部署”)。具体如下:

MCP_TOKEN_STORE_KEY    Fernet key encrypting onboarded tenants' API keys at
                        rest; generate once at deploy time with:
                        python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
MCP_PUBLIC_URL          public HTTPS URL this server is reachable at
MCP_DATA_DIR            where OAuth clients/tokens/tenants persist (default: ./.data)

两种模式下均可选:

MCP_WEBSITE_URL         public site reported as serverInfo.website_url; also used to
                        build serverInfo.icons[0].src as MCP_WEBSITE_URL + "icons/icon.svg"
                        and the /login page's privacy policy link as
                        MCP_WEBSITE_URL + "privacy.html" (both files must actually be
                        served there). Left unset, all three are simply omitted.
MCP_SECURITY_CONTACT    security contact (email or URI) served as /.well-known/security.txt
                        (RFC 9116), e.g. for MCPBundles' ownership verification. Left
                        unset, that path 404s.

部署

以独立的 Docker 容器形式提供(docker-compose.yml),设计为部署在反向代理之后,由反向代理终止 TLS 并将请求转发到 localhost 上容器的端口。默认采用远程/托管方式,而非本地 stdio 服务——MCP 客户端只需指向该 URL,无需在本地安装或运行任何内容。

访问权限由应用本身内置的真实 OAuth 2.1 授权服务器控制(app/oauth_provider.py),而非通过前置 HTTP Basic Auth 控制。这是经过深思熟虑的设计选择:Claude Desktop/claude.ai 的“Add custom connector”界面优先使用 OAuth——它总会尝试与新服务端进行 OAuth 发现和授权码流程,因此,服务端前置的普通 401(Basic Auth 会产生这种响应)会被解读为“此服务端需要 OAuth”,并在请求到不存在的 /authorize 端点时失败。正是通过实现真正的(虽然精简的)OAuth 服务端,才让“Add custom connector”能够正常工作。

支持多租户和自助服务:/authorize 不会将请求转交给第三方身份提供商——它会显示一个第一方登录表单,要求填写 Zotero Library ID、Library Type 和 API Key。提交表单后,系统会直接通过 Zotero API 验证密钥;验证成功后,既会授予访问权限,也会将该库注册(“引导入驻”)为此服务端的租户,一步完成——无需单独注册,也无需管理员审批。任何 MCP 客户端都可以动态注册自身(RFC 7591),但只有使用有效 Zotero 密钥完成登录表单,才能真正获得访问权限。之后,每个调用者的工具调用都会路由到其自己的 Zotero 库,而不是共享库。完整流程请参阅 app/oauth_provider.py 的模块文档字符串。已注册的客户端、已签发的令牌以及已入驻租户的凭据(API 密钥使用 MCP_TOKEN_STORE_KEY 加密后静态存储)都会持久化到 MCP_DATA_DIR(Docker 卷),因此重新部署不会使已连接的客户端退出,也不会遗忘已入驻的租户。

主机上的 .env(不在此仓库中)保存 MCP_TOKEN_STORE_KEY/MCP_PUBLIC_URL,并通过 docker-compose.yml 的 env_file: 加载。HTTP 部署不需要 ZOTERO_LIBRARY_ID/ZOTERO_LIBRARY_TYPE/ZOTERO_API_KEY——这些配置仅适用于 stdio 模式。

.github/workflows/deploy.yml 可自动将服务重新部署到已配置的主机:仅手动触发(workflow_dispatch,绝不会在推送时触发),先运行测试套件,然后通过 SSH 同步仓库并重建容器。该工作流需要单独配置 GitHub Actions 机密,用于提供部署 SSH 密钥和目标主机/端口/用户名——完整列表请参阅工作流文件。请使用专用部署密钥(不要使用直接/手动访问时所用的密钥),这样即使密钥泄露,也可以单独撤销。

监控

4 个无需身份验证的 GET 端点,仅限 HTTP 模式(都需要 $PORT, 与 /login 相同):

  • /healthz — 返回纯文本 200 OK,用于负载均衡器/运行状态检查。

  • /status — 聚合的进程级活动 JSON 快照:

    {
      "version": "2.1.0",
      "uptime_seconds": 41213,
      "tenants": 7,
      "tool_calls": {"search_items": 512, "add_tags": 41},
      "tool_errors": {"add_tags": 2}
    }
    

    tenants 是 TokenStore.tenant_count(),即已接入的 Zotero 文库数量。tool_calls/tool_errors 是所有工具名称的调用数/错误数,统计范围为所有租户,由 tools/call 中间件(app/mcp_server.py 中的 _track_tool_call)记录。此响应中不会显示任何按租户或文库细分的数据——这使该端点可以安全地保持无需身份验证,不像那 39 个工具。计数器位于 app/metrics.py 中,仅保存在内存里:每次重启/重新部署时都会重置为零。这不是指标/分析系统,只是一个轻量的“服务是否正常运行、大致有多忙”的信号。

  • /status.html — 与 /status 数据相同,但渲染为简洁页面,包含名称和图标、一张显示版本/运行时长/租户数的表格,以及一张显示各工具调用数/错误数的表格,供用户在浏览器中查看,而非供脚本读取。只有设置了 MCP_WEBSITE_URL 才会显示图标,与 /login 相同。

  • /.well-known/security.txt — RFC 9116 安全联系信息,仅在设置了 MCP_SECURITY_CONTACT 时提供(否则返回 404)。Expires 按请求计算为一年后,因此不会过期。MCPBundles 会读取此文件,在将服务发布到其目录时验证所有权。

  • /.well-known/mcp/server-card.json — 连接前的发现文档,包含服务身份、身份验证要求,以及带有架构的完整工具列表。每次请求时都会根据实际工具注册表实时生成,因此内容不会与注册表脱节。这不是已批准的标准:它是对 SEP-2127(“MCP Server Cards — HTTP Server Discovery”,取代已撤回的 SEP-1649)的务实近似方案。截至本文撰写时,该提案仍处于开放、未合并状态;甚至其 well-known 路径在不同草案中也有所变化(部分版本使用 .well-known/ai-catalog.json)。这并非对符合规范的一般性声明;如果/当 SEP-2127(或后续提案)以不同约定正式获批时,此处可能需要调整。

工具

共 39 个工具,按风险分组(使用任何破坏性工具前,请参阅上文“关键安全事项”)。在 Version 下标记 ✓ 的工具要求将条目/集合当前的 Zotero version(来自 search_items/get_item/list_collections)作为参数;如果版本已过期,就会拒绝调用,而不是静默覆盖并发更改。

工具类别版本说明
search_items只读query 默认为 Zotero 的快速搜索(标题/创作者/年份);full_text=True 也会匹配附件和笔记的已索引内容(qmode="everything"),且要求 query 非空。每个结果的 creators 都是由 {creatorType, firstName, lastName}(或单字段/机构创作者对应的 {creatorType, name})条目组成的列表;格式与 create_item/update_item 接受的格式相同,并保留角色(作者、编辑、译者等)。
get_item只读creators 格式如上。
list_collections只读
list_tags只读
list_trash只读creators 格式如上。
list_saved_searches只读
list_groups只读id 也可作为 move_item_to_different_library 的 target_library_id(配合 target_library_type="group")使用。
list_item_types只读
list_item_fields只读
list_item_type_fields只读在 create_item/update_item 之前检查特定 item_type 接受哪些字段,而不是猜测。
list_item_creator_types只读同上,检查 creators 条目的 creatorType。
list_creator_fields只读可用于 creators 条目本身的名称格式字段(firstName、lastName、name 等);这与 list_item_creator_types(角色)不同。
list_attachments只读
get_fulltext只读
download_attachment只读内容以 content_base64 返回;服务无法访问调用方的本地文件系统。
list_notes只读
export_bibliography只读为一组条目键导出格式化的 HTML 参考文献/引文条目(采用指定的 CSL style),或可移植的导出数据(csljson、bibtex)。未知键会被静默忽略。
create_item安全写入
create_collection安全写入
create_saved_search安全写入
update_collection安全写入✓
update_item安全写入✓
update_publication_status安全写入✓预印本 → 已发表:就地修改字段,并且这是唯一会就地修改 item_type 的操作。接受 idempotency_key——参见上文“幂等性”。
add_tags安全写入✓
remove_tags安全写入✓
set_tags安全写入✓
rename_tag安全写入作用于整个文库——会修改所有带有该标签的条目,而非仅一个条目;没有按标签划分的版本。大型文库上可能会阻塞:#6。
add_to_collection安全写入✓
remove_from_collection安全写入✓
trash_item安全写入✓可撤销的软删除——使用 restore_from_trash 撤销;除非之后被永久删除或清空回收站,否则不会破坏 Word 引文。
restore_from_trash安全写入✓
upload_attachment安全写入内容以 content_base64 发送;服务无法访问调用方的本地文件系统。
create_note安全写入
update_note安全写入✓
delete_item_permanently破坏性操作✓会破坏 Word 引文。接受 idempotency_key——参见上文“幂等性”。
move_item_to_different_library破坏性操作✓在目标文库中以全新键重新创建条目,然后删除原条目——会破坏 Word 引文。接受 idempotency_key,强烈建议在此使用——参见上文“幂等性”。
delete_collection破坏性操作✓级联删除子集合(与 Zotero 自身的“Delete Collection”一致);绝不会删除其中归档的条目。接受 idempotency_key。
delete_tag破坏性操作作用于整个文库——会处理所有带有该标签的条目,而非仅一个条目;没有按标签划分的版本。接受 idempotency_key。
delete_saved_search破坏性操作风险较低——保存的搜索只是存储的筛选条件,不会触及条目或引文。接受 idempotency_key。

测试

uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
pytest

测试绝不会调用实时 Zotero 文献库,即使 .env 中有真实凭据也是如此:app/zotero_service.py(所有 Zotero 读写逻辑)会针对 tests/fakes.py 中的内存版 FakeZotero 进行测试;app/mcp_server.py 的工具函数则会直接针对由该假对象支持的 ZoteroService 进行测试(参见 configure_service())。

状态

v2.3 — 已部署并投入使用,完整覆盖 Zotero Web API 的条目、分类、标签、废纸篓、已保存搜索和架构操作(39 个工具;参见工具)。可直接将其添加为远程 MCP 连接器(例如,在 Claude Desktop/claude.ai 中使用“Add custom connector”,只需提供服务的公开 URL)——上文所述的 OAuth 流程会在浏览器中提示你输入自己的 Zotero 文献库 ID/类型/API 密钥,无需手动配置请求头,也无需管理员注册。

已列入官方 MCP Registry,名称为 dk.herbertkokholm.citecaddy/cite-caddy——元数据位于 server.json,通过 mcp-publisher 发布,并由 DNS 对照 citecaddy.herbertkokholm.dk 完成验证。目前尚未列入 GitHub 单独维护、由人工整理的 github.com/mcp 目录,该目录不会自动与开放注册表同步。

已知限制

针对 MCP 2026-07-28 规范(“无状态核心、企业授权、扩展框架”)跟踪的差距。目前这些问题均无法被利用,也不会影响用户——每一项要么在上游 mcp SDK 变更前不起作用,要么已有缓解措施——但我们在此记录,以便明确说明,而非默默遗漏。

  • 已发送 OAuth iss 参数(RFC 9207),但未声明支持。 complete_login() 中构建的最终授权重定向(app/oauth_provider.py)包含 iss,可增强流程抵御混淆攻击的能力(RFC 9207 §2)。/.well-known/oauth-authorization-server 中对应的 authorization_response_iss_parameter_supported 标志未设置:该文档由 mcp SDK 生成,而截至 mcp 2.3.0,该 SDK 尚无相关设置。没有该标志时,客户端无需验证 iss,但会验证它的客户端仍可获得这项保护。

  • 使用动态客户端注册(RFC 7591),而非 CIMD。 同一规范更新正式弃用动态客户端注册,转而采用客户端 ID 元数据文档(CIMD),不过 DCR 仍可为向后兼容而正常使用。本服务的客户端自动配置(app/oauth_provider.py 中的 _FlexibleClientInformation/register_client/get_client)基于 DCR 构建——这是必要的,因为某些 MCP 客户端(已观察到:Claude Desktop/claude.ai)会跳过注册,直接向 /authorize 发送未注册的 client_id(参见该类的文档字符串)。只要已安装的 SDK 继续支持 DCR 且不发出警告,就无需采取措施;如果这一情况发生变化,则需要以 CIMD 为基础进行替换。

  • rename_tag 在大型文献库上可能会阻塞 — 作为 #6 跟踪;可考虑采用规范中的 tasks 扩展。mcp 2.3.0 有通用扩展框架,但尚未实现 Tasks。

贡献

请参阅 CONTRIBUTING.md。

安全

有关威胁模型及如何报告漏洞,请参阅 SECURITY.md。

隐私

有关你在 /login 登录时服务会存储哪些信息(Zotero 文献库 ID/类型/API 密钥)、这些信息如何受到保护,以及如何要求删除这些信息,请参阅隐私政策。

许可证

MIT

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。