2026-09-23·by Sijie Wang#standmeet#architecture#design

connector-plugins

Connector 插件——可安装、消费方无关

上级:connector

Connector 不是 MCP——它讲的是分类契约(category contract),而不是 MCP 的线协议;这两条插件轴仅在 connector-depsconfusables,含 action 与 sync 模式的区分)处相交。

术语约定:

  • Consumer(消费方)= agent、平台功能、未来的 IM 网关 / job-loop
  • Category Contract(分类契约)= 平台自有的接口(如 CalendarContractMailContract
  • 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(平台自有,固定)
定义一个分类必须支持哪些操作。例如:

  • CalendarContractlist_busy(range) → bool 网格,create_event(title, when, attendees) → event,cancel_event(id)
  • MailContractsend(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/data
  • connector.Serviceservice.go + svc_*.goconnectorsvc 包已于 2026-07-26 合并进来,1bc9ba8b0)—— 凭证(经 cryptobox 的 AES-GCM)、connect、oauth、activate、disconnect
  • capreg/depresolver.go —— manifest 中的 Requires:["calendar"] → 具名依赖供应商;不满足时 → 能力隐藏,fail-closed
  • 按调用类别(call-class)设置的重试策略
  • 插件收到的是一个调用用的 HANDLE,从不是原始凭证

凭证处理:
静态加密存储,只在 connector 包内部解密。通过 AuthInjector 闭包按请求注入到 *http.Request 中。包外的调用方只能看到一个 Connected 布尔值加结果数据。

类视图——契约在上,类型在下

刻意拆成两个朝向:消费方手上拿的是 contract 层的 proxybackend/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/UpdateUploadedsvc_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)。

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →

connector-plugins