Connector 插件——可安装、消费方无关
上级:connector
Connector 不是 MCP——它讲的是分类契约(category contract),而不是 MCP 的线协议;这两条插件轴仅在 connector-deps(confusables,含 action 与 sync 模式的区分)处相交。
术语约定:
Consumer(消费方)= agent、平台功能、未来的 IM 网关 / job-loopCategory Contract(分类契约)= 平台自有的接口(如CalendarContract、MailContract)Connector Binding(connector 绑定)= 供应商操作到分类契约的声明式映射(YAML)Protocol kind(协议型)= 内置的 Go 实现(SMTP/CalDAV,外加没有契约的 Telegram token 保险箱;backend/internal/connector/里不存在 IMAP 或 LDAP 实现)OpenAPI kind(OpenAPI 型)= 作者提供的 OpenAPI spec + binding(面向单个 SaaS 的 HTTP)
定义:带凭证的外部集成
一个 connector 是一个带凭证的外部集成,与消费方无关,结构上完全类似 WordPress 插件。任何人都可以编写一个;owner 把它安装进自己的实例。Hub 是中立的基座;我们不打包 Nango 的目录。
关键性质:消费方(agent、平台功能、未来的网关)永远不持有凭证。凭证始终封存在 connector 这一层。
两种类型(透明可互换)
一个分类(如日历、邮件)可以由两种类型中的任意一种来满足:
openapi——基于 HTTP 的 SaaS。作者提供 OpenAPI 3.0 spec + binding YAML。凭证表单从securitySchemes派生(oauth2、apiKey、http basic、bearer);存在多种时由 owner 在 UI 中选择。protocol——标准协议(SMTP、CalDAV;Telegram 自 2026-09-04 起,8c819f26d ——protocol_telegram.go,分类im,只存 token,由im-bridge消费而非经分类契约)。内置 Go 实现;凭证描述符固定。一份实现覆盖长尾。
两者拥有相同的插件形状和部署路径。
三层架构
① Category Contract(平台自有,固定)
定义一个分类必须支持哪些操作。例如:
CalendarContract:list_busy(range)→ bool 网格,create_event(title, when, attendees)→ event,cancel_event(id)MailContract:send(to, subject, body, attachments)→ message ID
② Connector Binding(声明式 YAML)
把契约映射到供应商的操作上。对 OpenAPI:契约方法 → operationId,请求与响应各自带 JSONata 变换。对 protocol:直接实现。
③ Generic runtime(契约调用 → 结果)
OpenAPI:调用 → 注入 token → HTTP 调用 → 归一化响应。Protocol:直接调用协议客户端。
这三层都不依赖任何具体供应商。内核不含任何供应商专属代码(google-calendar / smtp 这类名字从不出现在 host 里)。
已锁定的决定(design lock)
- 映射语言: 只用 JSONata(先例是 AWS Step Functions)。
- OpenAPI 版本: 只支持 3.0。
- 多重鉴权: 当供应商定义了多个
securityScheme时,owner 在连接时于 UI 中选一个。 - 内置件形状: 内置 connector(随仓库发布)与上传的 connector 形状完全相同。
代码现状
文件布局:
backend/internal/connector/—— Hub、分类槽位、OpenAPI 的 spec/binding/runtime/ingest、protocol_* 实现、builtins/dataconnector.Service(service.go+svc_*.go;connectorsvc包已于 2026-07-26 合并进来,1bc9ba8b0)—— 凭证(经cryptobox的 AES-GCM)、connect、oauth、activate、disconnectcapreg/depresolver.go—— manifest 中的Requires:["calendar"]→ 具名依赖供应商;不满足时 → 能力隐藏,fail-closed- 按调用类别(call-class)设置的重试策略
- 插件收到的是一个调用用的
HANDLE,从不是原始凭证
凭证处理:
静态加密存储,只在 connector 包内部解密。通过 AuthInjector 闭包按请求注入到 *http.Request 中。包外的调用方只能看到一个 Connected 布尔值加结果数据。
类视图——契约在上,类型在下
刻意拆成两个朝向:消费方手上拿的是 contract 层的 proxy(backend/internal/connector/contract/ 里的 CalendarProxy/MailProxy——分类动词,哪里都看不到供应商;Send 返回带供应商消息 id 的 MailReceipt);Hub 追踪的是 Connector(name/kind/connected——只管生命周期)。到底是哪种类型在满足某个 proxy,对消费方不可见。这已经是 Bridge/Strategy 的形状;按 judgment-audit 的规则,不需要再叠加别的模式。
红色契约测试套件(已建成:36789537d、2026-09-07 时 66 个 connector-*.spec.ts)
每个区域都由可执行的红色测试钉住:
- Ingest —— spec 解析、binding 校验
- Cred-form derivation ——
securitySchemes→ UI 表单 schema - JSONata binding —— 对请求与响应做变换
- Connect flow —— OAuth 刷新、凭证存储
- Protocol SMTP —— 内置的 SMTP 客户端(
mailer_smtp.go;没有 IMAP 客户端) - Consumption loop —— 消费方调用契约方法
- Upload mgmt —— owner 上传、生命周期、删除
- Security —— SSRF 拒绝内网服务器、凭证不泄漏、按 owner 隔离(与 connector-egress-guard 有重叠)
由红色测试钉住的设计决定:
- 每个分类槽位同一时刻只有一个生效的 connector
- 面向 agent-tool 的暴露按操作逐一 opt-in(
op_<operationId>,逐操作 ACL) - 断开连接(disconnect)保留凭证
- 拒绝外部
$ref(不允许跨文件的 schema 组合)
两种模式:action(proxy)与 sync(ingest)
Action(proxy)——同步消费。消费方调用 connector;它代理到供应商并返回结果。这是主路径(agent 工具、平台功能)。
Sync(ingest)——异步摄取。connector 按计划(或由事件触发)拉取数据。Obsidian vault 同步已经是一个 sync 模式的 connector(NewSyncConnector + SyncIngester),corpus 支柱与这根支柱相接的那道缝已经合上。
状态(2026 年 7 月,2026-09-07 于 36789537d 复核):已落地proxy 层更早以前就已落地;如今可安装、与消费方无关的设计也落地了——owner 上传流程是真实代码(
connection_repo_uploaded.go里的Repo.SaveUploaded/UpdateUploaded,svc_manage.go里的Service.UpdateUploaded),红色契约测试套件已长成 66 个 e2e spec 文件(deps/retry/security/upload/ingest/binding/matrix),早先 13 个test.fixme的尾巴现已全部转成了实测,TODO(impl)的 mock 基础设施缺口已于 2026-07-03 关闭(059dc5c13)——剩 0 个;落地之后的打磨还在继续(assemble 重设计去掉了供应商下拉框;通用 connector 形状重构;credform 从 authform 派生而来;OAuth 流程带 PKCE,e23c0c9f4,2026-08-20)。sync 模式(那道 Obsidian 缝)已于 2026-07-08 落地(d51805372,backend/internal/connector/sync.go)。