用 WorkBuddy 时你可能做过三件事:让它读 Gmail 邮箱、把一个站点部署到云端、或者自己在 mcp.json 里填一个本地服务。界面上这三件事都叫连接器,模型看到的也都长成 mcp__server__tool。实现上它们走三条完全不同的路:桌面端内置的能力总线、第三方连接器包、你自己写的 MCP server。
WorkBuddy 的做法是统一命名,但凭据怎么存、调用怎么审批,三层各有一套。
这篇按一个连接器在系统里的生命周期走:注册进来 → 拿到调用资格 → 暴露成工具 → 一次调用经过的检查 → 撤销与失效。每个环节都看三层各自的答案。
注册:连接器以什么形式存在#
三条路的进程来源各不相同:内置能力总线跑在桌面主进程里,第三方连接器包自带一个 MCP server,用户自定义 MCP 由用户或插件启动。
第一层不用安装。桌面主进程起一个本地 MCP 主机,服务名 connector-proxy,把平台能力注册成 MCP 工具,路由挂在 /mcp:云服务连接、站点部署、插件市场、技能安装、会话搜索都在上面,比如 connect_cloud_service、workbuddy_sites_deploy、search_and_install_skills、conversation_search、search_plugins。同样用本地主机挂出来的还有 miora(设计、图像、视频)、sheetagent(表格)、weixinpay、ardot、netdrive。
第二层是第三方连接器包,Gmail、日历、网盘、企业系统都走这条路。包本身是一个标准目录,以 Gmail 为例:
bash
gmail-mcp/
├── connector.json ← 元数据:名称、描述、示例、type: "mcp"、source
├── mcp.json ← 标准 MCP 配置(stdio 或 http)
├── oauth-client.json ← OAuth 客户端配置
├── dist/ ← 连接器实现(如 cli.cjs)
├── icon.svg
└── THIRD_PARTY_NOTICES.md
connector.json 管展示和搜索,mcp.json 管怎么启动。安装后,连接器在 MCP 管理器里以 connector:<id> 注册。
第三层是配置文件:~/.workbuddy/mcp.json,标准 mcpServers 结构;插件自带的 .codebuddy-plugin/.mcp.json 在安装或启用时同样被纳入 MCP 管理器。
连接器也不一定是一个本地包。连接器市场和云端 Agent 的连接器走后端 API(/v2/user/cloudagent/agents/{agentId}/connectors 的增删改查),同一个概念在本地桌面和云端两个运行时里保持一套语义。
调用资格:三层各管一套凭据#
第一层防的是「本机以外的人」。服务只绑回环地址,请求还要校验 Host 的主机名在回环集合内、端口和监听端口一致,否则 403。外部机器连不上;浏览器把恶意域名解析到 127.0.0.1 时,Host 头仍是攻击者的域名,同样被拒。Origin 再过一遍白名单:没有 Origin 的请求(本机 CLI 和脚本)放行,带浏览器 Origin 的必须在白名单里。
资格本身按路由签发。McpAccessTokens.issue(routeKey) 生成 32 字节随机数、base64url 编码,路由和令牌双向映射;令牌只在该路由注册期间有效。
只有 Bearer token 还不够。请求得再带一个 HMAC-SHA256 签名的上下文信封,签名密钥至少 32 字节,信封里绑定 sessionId、projectId、workspace、welcomeMode。校验通过后,处理函数拿到的不是「某个客户端」,而是「某个会话」,后面所有权限判断都按这个粒度做。
第二层的关键是长期凭据不进连接器进程。Gmail 的 mcp.json 里那几个环境变量就是全部:
bash
{
"mcpServers": {
"gmail": {
"type": "stdio",
"command": "node",
"args": ["${WORKBUDDY_GMAIL_CONNECTOR_ROOT}/dist/cli.cjs"],
"env": {
"WORKBUDDY_GMAIL_API_URL": "${WORKBUDDY_LOCAL_MCP_ORIGIN}/mcp/internal/gmail/api",
"WORKBUDDY_GMAIL_CREDENTIAL_AUTHORIZATION": "${WORKBUDDY_LOCAL_MCP_AUTHORIZATION}",
"WORKBUDDY_MCP_CONTEXT": "${WORKBUDDY_MCP_CONTEXT}",
"CODEBUDDY_PROJECT_DIR": "${CODEBUDDY_PROJECT_DIR}"
}
}
}
}
连接器进程拿到的只有短期授权、会话上下文,以及指向本地 MCP 主机的内部 API 地址。真正的 Google OAuth 凭据留在桌面端,连接器只把 MCP 调用翻译成对内部 API 的请求。连接器实现有漏洞,攻击者也拿不到长期 token。
OAuth 本身由 McpManager 统一管生命周期:beginAuthorization(id) 生成授权 URL 并监听本地回调,客户端状态在 starting / ready / unauthorized / error 之间迁移,回调结果交给 onDidOAuthReconnectSettled,成功走 completeMcpConnect(id),失败或未授权走 failMcpConnect(id, state, error)。
第三层没有授权服务器,改用本地信任库 ~/.workbuddy/mcp-approvals.json,键是服务器名加配置哈希:
bash
function approvalKey(config, serverName) {
return `${serverName}::${hash(config)}`;
}
用户批准过 my-server,不等于批准过「my-server 被改成执行另一段命令」。配置内容一变哈希就变,旧批准自动失效,要重新确认。写入路径是先校验、再提交、失败回滚;读取失败时沿用上一份快照,而不是把读不到当成没有配置。插件如果声明了高权限工具,还要另外走审批闸门。
暴露:都变成模型面前的同一个工具#
三层各说各话到这里汇合。命名统一成 mcp__<server>__<tool>,比如 mcp__connector-proxy__connect_cloud_service,名字自带归属,排障时直接看出工具来自哪个服务。connector-proxy 还支持裸名和别名展开(expandMcpAppToolNameAliases),为每个工具生成 tool_control_id 和兼容 ID,UI 的工具开关、灰度、兼容旧配置都依赖这层映射。
工具列表不是全局配置,是按会话算出来的。内置总线的每个会话上下文都带一个 allowedToolNames,加上工具自身的可用性判断,同一个账号在不同会话、不同项目、不同模式下看到的工具面可以不一样。
几十个连接器、上百个工具没有全量塞进上下文,靠的是 defer_loading:标记为延迟加载的工具不进首屏工具目录,要用时通过 ToolSearch 按需发现,系统提示词不用一开始背完整目录。
CLI 侧拿到的是一份标准 MCP 配置:
bash
{
type: "http",
url: `http://127.0.0.1:${port}/mcp`,
headers: {
Authorization: `Bearer ${token}`,
[CONTEXT_HEADER]: signedEnvelope,
},
transparent_proxy: true,
builtin_tools: [...],
tools: { /* defer_loading、prompt_cache_stability、tool_control_id */ }
}
transparent_proxy: true 表示这个 MCP 服务由桌面本地主机透明代理,CLI 不再为它起子进程。这既是性能优化,也是边界:进程的生命周期和凭据都留在桌面端,宿主掌握系统能力和登录态,CLI 只按 MCP 协议调用。
一次 tools/call 经过哪些检查#
把前面几段串起来,一次对内置总线工具的调用依次经过:
- 回环校验:
Host主机名与端口、Origin白名单; - Bearer 令牌:必须是这条路由当前注册期间签发的那个;
- 信封验签:HMAC 通过才解出会话上下文;
- 工具存在、会话允许、当前上下文可用,三个条件同时满足才执行;
- 计费与高危工具再过一道
PreToolUse审批钩子; - 执行。第三方连接器的调用落在连接器进程里,它再翻译成一次带短期授权和上下文的内部 API 请求。
第 4 步的判断只有三行:
bash
const tool = registry.get(params.name);
const ctx = toSessionToolContext(session);
if (!tool || !session.allowedToolNames.has(tool.name) || tool.isAvailable?.(ctx) === false) {
return errorResult(`Tool "${params.name}" is unavailable in this session`);
}
计费工具另有一道闸门#
图像生成、视频生成、图像处理、音频转写这类高积分工具,每次调用都实际扣费,不能和普通查询共用一套审批策略。
high-credit-gate.cjs 是一个 PreToolUse 钩子,扫描各市场的 high-credit-tools.json,命中清单就返回 permissionDecision: "ask" 强制弹审批。判定发生在所有权限规则和 bypassPermissions 之前,所以默认 bypass 模式也绕不过去。审批文案、审批记忆分组、hook matcher 都从同一份清单派生,新增计费工具只改 JSON,不改代码。
bash
{
"version": 1,
"tools": {
"miora_text_to_image": { "approvalKind": "imageGen", "group": "miora-image" },
"miora_text_to_video": { "approvalKind": "videoGen", "group": "miora-video" },
"miora_edit_image": { "approvalKind": "imageGen", "group": "miora-image" }
}
}
清单里有两处设计是刻意的。一是图片和视频分开分组:用户点「本次会话始终允许生成图片」,不应该顺带授权更贵的视频生成。二是 fail-closed:清单损坏、声明非法、脚本异常,一律按「需要审批」处理,因为上游在 hook 抛错时会 fall-through 到常规权限检查,在 bypass 下等于静默放行、零审批扣费,所以宁可多弹一次窗。
还有个前提藏在加载路径里:计费工具的插件必须在 .mcp.json 里显式写 defer_loading: false。走延迟加载的话,调用会先经过 DeferExecuteTool,它在 bypass 模式下的前置检查直接返回「无需审批」,钩子根本不执行。安全机制依赖加载路径,这是个很典型的坑。
撤销与失效#
调用资格不是签了就长期有效,三层各有各的失效路径。
内置总线的路由注销时,对应令牌立即撤销,并中止这条路由上所有在飞请求。
连接器要等 OAuth 结算。流程里维护一个 pending 集合,OAuth 没结算的连接器不算可用,只有 resolveMcpDependency 返回 ready 才调用 completeMcpConnect。解绑走 revokeCredentials(id, { remote: true }),要求远端授权也撤销成功,避免出现本地删了、云端还授权的残留。
用户 MCP 的批准跟着配置内容走,改一行命令就要重新确认。
撤销要盯这么多处,是因为一个连接器的状态分散在安装态、启用态、OAuth 态、MCP 客户端态和会话可见性之间,一致性得专门维护。
和 MCP 规范差在哪#
MCP 规范里的授权模型,核心是把 MCP server 当成 OAuth 2.1 的资源服务器:客户端从授权服务器拿 token,访问 token 由资源服务器校验,支持受保护资源元数据、动态客户端注册、PKCE。
WorkBuddy 在它上面加了一层本地授权代理,三层各取所需:第三方的 OAuth 仍按标准流程走,但凭据集中在桌面端,连接器进程只拿短期本地授权;本地能力不暴露公网 OAuth,用回环、路由令牌、上下文签名构造一条只对当前会话有效的调用通道;用户 MCP 用配置哈希审批替代 OAuth,因为很多本地 MCP server 根本没有授权服务器。
三套机制解决三个不同的问题:第三方授权、本地能力隔离、用户配置信任。产品层把它们都叫连接器,实现层各自有独立的信任模型。
好处是模型侧简单、凭据侧收缩:模型不需要理解工具是内置还是第三方,长期 token 不用下发给连接器进程,异常时各环节都倾向要求确认而不是默认放行。代价主要落在调用链上:一次调用要经过路由解析、令牌校验、信封校验、会话白名单、可用性判断、可能的审批钩子,任何一环出问题排障成本都不低;defer_loading 约束、审批分组、清单格式都是跨进程契约,改任何一处都可能让安全机制静默失效。
剩下的边界#
上面的机制各自还挂着一个前提:会话白名单依赖会话初始化正确;连接器只拿短期授权,靠的是本地内部 API 自身鉴权可靠;高积分闸门依赖清单完整、且插件走非延迟加载;解绑依赖远端撤销接口可用。这些前提之外,还有四类风险是这套设计挡不住的。
同用户的本机进程仍然能读配置、拿令牌。回环加令牌防的是其他网页和未授权客户端,不是同一用户下的恶意程序,桌面应用的本地信任边界最终还是依赖操作系统账户隔离。
oauth-client.json 里可能带 client_secret。桌面应用内置 OAuth 客户端密钥在业界不罕见,但它应被视为公开信息,不能当真正的秘密;更稳妥的做法是用 PKCE 的公共客户端模式,不内置 secret。
工具输出是新的注入面。连接器返回的邮件、文档、网页内容都可能包含提示词注入,MCP 协议本身不解决这个问题,得在工具描述、输出包装和审批策略上一起处理。
还有审批疲劳:高积分工具每次都问,用户会习惯性点允许,分组记忆和文案分级只能缓解,不能消除。
最后#
连接器这个词容易让人以为只是多接几个 API。真正难的是让三种来源在模型面前长得一样,同时在系统内部保持三种信任模型。本地能力靠「回环 + 每路由令牌 + 上下文签名」,第三方凭据集中在桌面端,用户配置审批绑定内容哈希------这三件事各自独立,但得一开始就定下来。
计费和高危工具的闸门还要单独验证:它在延迟加载和 bypass 模式下是否同样生效。机制写在文档里,不等于它在每条路径上都跑得到。
文中的机制、默认值和字段按 WorkBuddy 5.8.4 整理。没有做真实 OAuth 授权、令牌窃取或本地端口扫描,所以文中「能防住什么」是机制层面的判断,不是渗透测试结论。为避免扩散敏感信息,不转载任何 OAuth 客户端配置的具体值。