设计原则
Coffer 的存在是为了解决一个具体而真实的问题。理解这个问题,是理解后续所有架构决策的最快路径。
问题所在
现代 AI 开发涉及大量 MCP 服务器——文件系统、数据库、浏览器、代码执行器、外部 API——以及多个 MCP 客户端:Claude Code、Codex、Cursor,以及未来还会出现的新工具。朴素的做法是为每个客户端单独配置每个服务器:N 个客户端 × M 个服务器,每一份配置都需要手动维护,每份配置都持有各自的 API 密钥和凭据。每当服务器 URL 变更或凭据需要轮换,每个客户端的配置都必须逐一更新。更糟糕的是,同一个上游服务器在不同客户端中暴露的工具名称可能不同,因为每个客户端可能各自做了过滤或重命名。
结果就是:凭据蔓延 (credential sprawl)、各客户端之间配置漂移,以及身份不一致问题:Claude Code 里的 read_file 工具和 Cursor 里的 read_file 工具不一定是同一个。整个系统没有单一的控制点。
Coffer 对这一切面的解决方案是:一个长生命周期的本地守护进程,负责一次性注册上游 MCP 服务器,通过单一命名空间化的接口面将所有服务器统一暴露(每个工具都以 <server-name>__<tool-name> 的形式出现),并通过这个统一节点处理所有客户端连接。一次配置,所有客户端看到相同的工具、相同的名称、相同的策略。
不过,MCP 网关只是更广义仓库的一项能力。Coffer 是一个本地优先的 AI agent 仓库:同一个守护进程、同一个与 kind 无关的 Resource 框架,还管理着已注册的编码 agent、主技能包、knowledge-base 与 memory 底座,以及消息渠道——共计六种资源 kind——外加横切的聊天、渠道与多机同步。下文的原则约束的是整个仓库,其中网关是奠基性的 kind,而非系统的全部。
三大原则
Coffer 章程确立了三条不可妥协的原则。此后的一切——技术选型、分层设计、进程模型、持久化策略——都是这三条原则的推论。
原则一:本地优先(不可妥协)
不变量
所有用户数据都驻留在用户本机。云服务仅作为 LLM 与工具的提供方——它们绝不充当任何仓库状态的事实记录方。
每一份仓库资产——已注册的服务器配置、凭据引用、审计日志、能力偏好——都保存在你的设备上。HTTP API 仅绑定到 127.0.0.1。未经用户明确操作,不会有任何备份发往厂商云端,也不会有遥测数据外发。
「本地优先」并不意味着「禁止网络」:调用远程 LLM API 或调用云托管的 MCP 工具完全在预期之内。这条约束针对的是状态存放的位置,而非网络使用本身。守护进程可以向 LLM 提供商发起出站 HTTP 调用;它只是不能在没有章程修订的情况下,将你的仓库状态暴露给第三方。
将用户状态复制到由厂商掌控的云端并使其成为事实记录方,需要走正式的章程修订流程——这是一项明确记录在案的决策,而非静默的配置变更。
同步例外(章程 v0.3.0)。 多机同步在本原则的一项有界例外下被允许,该例外随 spec 010 在章程 v0.3.0 中加入。同步只在用户自有、自控的介质上运行(用户自己的 git 仓库);每台参与机器都保有完整仓库,因此该介质只是传输与历史,绝不成为新的事实记录方。密钥仅以 Fernet 密文传输——主密钥绝不跨越同步介质,而是带外(out-of-band)引导到每台机器上。同步是 opt-in 的,指向用户自己提供的远端。见 ADR-016。
原则二:规范即真理(规范驱动开发)
specs/ 目录下的规范是产品契约的唯一权威。任何会改变对外可见行为的 PR 都必须先更新对应规范,再修改代码。规范不是事后写的文档——它是实现必须遵从的契约。当代码与规范不一致时,错的是代码。
这条原则存在的原因是:分布式团队(以及生成代码的 AI agent)往往会随时间偏离设计意图。通过将规范设为真理之源,并要求在代码变更前先更新规范,Coffer 确保架构意图始终有记录、可验证。
原则三:从第一天起就为开源做好准备
MIT 许可证、治理规则、贡献流程、Conventional Commits,必须从 v0.0.1 起就存在于仓库中,而不是事后补齐。这条原则规避了「以后再开源」的隐性代价——事后补加许可证、署名和治理规范的成本极高且容易出错。
任何会让开源就绪清单变短的变更——引入未经豁免的闭源依赖、省略 AI 生成内容的署名——都违反此原则,必须通过章程修订。
保证与不变量
以下条件无条件成立,不是可选配置:
仅监听 loopback。 守护进程的 HTTP 服务器绑定到 127.0.0.1,不接受来自任何其他接口的连接。一旦需要对公网暴露任何接口面,必须以独立进程运行,并仅限于经过签名校验的回调路径。
密钥明文永不进入数据库。 密钥只以 Fernet 密文形式存于 credentials 表(envelope 加密);SQLite 数据库只保存凭据引用——不透明标识符——以及密文,从不保存明文。明文仅在解密与拉起子进程/注入 header(消费密钥处)之间存在于内存。Fernet 主密钥由 infrastructure/credentials/ 独占管理(默认为 DB 旁的 0600 文件,opt-in 时存于操作系统钥匙串),它是唯一被允许 import keyring 的位置。其他任何代码模块均不得直接 import keyring。
每个上游工具都有命名空间。 通过 Coffer MCP 接口面暴露的工具以 <server-name>__<tool-name> 的形式出现(例如 filesystem__read_file)。这个命名空间是稳定且确定性的:以相同名称注册的同一个上游,无论哪个客户端连接,始终产生相同的工具命名空间。
单一 SQLite 写入者。 只有守护进程写入 ~/.coffer/coffer.db。CLI 和 shim 通过守护进程的 HTTP API 读取状态,从不直接打开数据库。这条不变量让 WAL 模式的隔离正确性成为显然。
Coffer 不是什么
理解边界与理解能力同样重要。
不是云服务。 不存在托管版 Coffer,没有 SaaS 方案,不需要账号。守护进程就是你机器上的一个进程。
不是托管同步服务。 Coffer 确实会跨机器同步仓库状态(spec 010),但只在用户自有、自控的介质上——用户自己的 git 仓库——并遵循上文的 v0.3.0 同步例外。Coffer 不提供任何托管或厂商同步端点;提供这样的端点仍需要进一步的章程修订。
不是模型提供方。 Coffer 本身不是 LLM,也不托管 LLM。MCP 网关只路由协议消息、不对工具输出做推理;进程内的 LangGraph 聊天 agent(spec 008)确实会调用 LLM 来对话——但这些模型是 Coffer 调用的外部提供方,绝非 Coffer 自己交付或训练的模型。Coffer 编排模型与工具;它不是模型本身。
不是防火墙或安全边界。 Coffer 应用能力级别的启用/禁用策略(见 ADR-004),但它是以用户自身进程身份运行的开发工具——它不对上游服务器代码进行沙箱化,也不执行 OS 级访问控制。
被拒绝的替代方案
这些拒绝决策记录在各 ADR 中;这里的摘要用于与原则对应:
为什么不用云托管网关? 云托管网关对于团队场景是自然答案,但它无条件违反本地优先原则。凭据必须离开机器;仓库状态将依赖网络。单用户本地优先模型是这套设计的根基。
为什么不用每客户端各自配置? 每客户端各自配置正是 Coffer 要解决的现状——那就是问题本身。它带来的痛点(N × M 维护负担、凭据蔓延、身份不一致)正是引入中央守护进程的动机。
为什么不提前抽取跨层公共抽象? 章程规定跨层公共模块只在第二个 feature 也需要它时才抽取,以避免过度设计。唯一的例外是 Resource 框架——它被提前设计,因为它横跨每一层(domain、持久化、审计、接口面路由);等到第二个资源 kind 到来时再做迁移,代价将是非平凡的重构,而不是简单的抽取。见 ADR-001。
参见: 章程参考