给开源桌面应用 OpenWork 接外部服务:MCP 服务器、办公套件与搜索的授权边界

2026-08-04

本文基于 openwork 仓库 commit 3b41381(2026-08-03)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/different-ai/openwork 最新代码与文档为准。

给 OpenWork 接外部服务时,真正决定风险的不是你点了几次「连接」,而是这条连接的账号归属是谁、凭据落在哪台机器上。 同样是接 Slack,走组织托管的连接和自己在桌面端加一台自定义 MCP 服务器,凭据位置、可撤销性、以及出事之后谁能查审计日志,完全是三套答案。文档里把这三条路径写在了相邻的几篇里,但没有把「代价」并排列出来——这篇就干这件事。

先做个边界说明。OpenWork 是 Different AI 的开源桌面应用项目(仓库 github.com/different-ai/openwork),把技能、MCP 连接与外部服务打包成可共享的「能力」,和同名的职场点评网站、以及中文里泛指的「开放工作」没有关系,下文出现的 OpenWork 一律指这个项目。另外它的许可证是分层的:仓库根目录 LICENSE 写明 /ee 目录下的内容按 ee/LICENSE 定义的许可证发布(该文件头部标注为 Functional Source License, Version 1.1, MIT Future License,缩写 FSL-1.1-MIT,Copyright 2026 Different AI Inc),其余部分才是 MIT(Copyright 2026 Different AI)。而本文谈到的组织控制面——发布连接、分配访问、下发桌面策略——代码正好落在 ee/apps/ee/packages/ 里。所以不能笼统说一句「MIT 开源」就完事;能不能商用、能不能改,以许可证原文为准,本文不提供法律意见。

一、三条接入路径,先分清你走的是哪条

从桌面应用的视角看,外部能力进入 Agent 有三个入口,文档把它们放在 packages/docs/start-here/connect-your-stack/ 下的不同文件里:

第一条是 OpenWork Connect。成员在桌面应用的 Settings > OpenWork Connect 里,看到组织已经开放的服务并逐个登录。文档点名的服务包括 Gmail、Google Calendar、Google Drive、Slack、Notion、Linear。这条路要求你先登录一个 OpenWork 账号并加入一个启用了 Connect 的组织,而且文档特意强调,这和登录某个模型服务商的账号是两件事。

第二条是 自定义 MCP 服务器Settings > Extensions 里的 Add Custom App,用于组织没有通过 Connect 提供的本地或自建服务器。文档开头就写明这是「advanced path」,只在 Connect 覆盖不到时才用。

第三条是 内置搜索开关。Exa 搜索在文档里被描述为一个全局设置,位置是 Settings -> Advanced,界面文案在 apps/app/src/i18n/locales/en.ts 里对应的键是 settings.enable_exa(“Enable Exa web search”)。它不是一台 MCP 服务器,走的是内置工具通路。

这三条的差别不是「难易程度」,是凭据归属。第一条的登录态按文档说法存在 OpenWork Cloud 而不是你这台机器上;第二条的 OAuth 客户端凭据是你自己填进桌面端配置的;第三条你甚至没有一个可见的第三方账号,只有一个开关。搞混了,后面所有的撤销动作都会做错地方。

如果你想先补 MCP 授权本身的通用加固手法,站内那篇MCP 授权链路加固讲的是协议层的通用做法,MCP Server 能力卡讲的是怎么给一堆服务器建立可检索的登记,而AI 办公自动化谈的是办公场景的落地价值;本篇不重复这三块,只盯 OpenWork 这一个项目里这三条接入路径各自的授权边界与账号归属。

二、加一台自定义 MCP 服务器:动态注册是分水岭

Add Custom App 的对话框要你填名字、URL,并指明服务器是否需要 OAuth。文档明确写了它面向的场景:支持动态 OAuth 客户端注册(dynamic OAuth client registration)的服务器。你点完之后,浏览器里跑完提供方的 OAuth 审批流程,再跳回 OpenWork。

问题出在不支持动态注册的服务器上。文档里给的处理方式是:如果服务器给了你一对预注册的 OAuth client ID 与 client secret,就走 Add Custom App > Advanced OAuth 填进去。报错信息长这样——Incompatible auth server: does not support dynamic client registration。看到这句就别在 URL 上折腾了,它说的是注册方式不匹配,不是地址写错。

这里还有个容易被忽略的选择:添加时可以选择把这个 app 加到当前工作区,还是加到全局配置(对所有工作区生效)。这一项决定了这台服务器的可见范围。如果你把一台带写权限的服务器加进了全局配置,那么以后每开一个工作区,Agent 默认就带着这套权限——工作区隔离在这一步被你自己抹掉了。关于隔离本身的收益,可以参考站内Agent 工作区隔离

三、接办公套件:授权到什么程度,由 scope 那一行字决定

Slack 是文档里唯一被单独写了一篇的办公套件案例,因为它的官方 MCP 服务器不支持自动 OAuth 客户端注册,必须走预注册路径。这一篇(connect-slack-mcp.mdx)把授权边界写得最实。

第一件事:Slack 管理员需要在 Slack 应用的 OAuth & Permissions 里加一条重定向地址,文档给的值是 http://127.0.0.1:19876/mcp/oauth/callback。这是本地回环地址,说明这条流程是桌面端本机接住回调的。第二件事:文档说明这条流程不需要旧的 Slack bot tokenxoxb-...xapp-...),走的是用户令牌作用域(user token scopes)。

用户令牌作用域这几个字是关键——它意味着 Agent 在 Slack 里是以你的身份行动的,它能看到的就是你能看到的。文档给了一组「先只读」的起步作用域:

search:read.public search:read.private search:read.mpim search:read.im search:read.files search:read.users channels:history groups:history mpim:history im:history users:read users:read.email channels:read groups:read mpim:read

注意这一串里已经包含 search:read.privateim:historyusers:read.email——私聊历史和成员邮箱都在里面。所谓「只读」并不等于「无害」,它只是不写而已。要让 Agent 真的在 Slack 里动手,才需要另一组:

chat:write reactions:write channels:write groups:write im:write mpim:write canvases:read canvases:write

文档自己也写了一句:Slack MCP uses user token scopes. Choose the smallest set that matches what your team wants agents to do. 这句话值得当成硬规则执行——把上面那一大串照抄进去是最省事的做法,也是暴露面最大的做法。按能力挑作用域的对照表在同一篇文档里,逐行对应「搜索公开与私有消息」「读文件」「发消息」「建频道」等能力。最小权限的做法可参考站内最小权限设计

填完之后,OpenWork 会把这台 MCP 写进配置,文档给出的形状是:

{
  "mcp": {
    "slack": {
      "type": "remote",
      "enabled": true,
      "url": "https://mcp.slack.com/mcp",
      "oauth": {
        "clientId": "your-slack-client-id",
        "clientSecret": "your-slack-client-secret"
      }
    }
  }
}

如果你填了作用域,文档说它还会被存成 oauth.scope,这就是 Slack 在同意页上展示给你的那一份清单。这段配置里躺着一个 client secret——文档也提醒了要把它排除在聊天记录和源码仓库之外。这条路径的代价在这里最直白:你把一份长期有效的第三方凭据,明文形态地交给了本机的配置文件。

四、接搜索:一个开关背后的数据流向

Exa 那一篇文档短到只有一段:它是一个全局设置,位置在 Settings -> Advanced。但短不等于无害。

apps/app/src/lib/build-in-tools.ts 里能看到内置搜索工具的类型定义,其中一行是:

export type WebSearchProvider = "exa" | "parallel";

同一个文件里,WebSearchInput 的字段包括 querynumResultslivecrawltypecontextMaxCharacters,其中 livecrawl 的取值是 "fallback" | "preferred"type 的取值是 "auto" | "fast" | "deep"WebSearchMetadata 会带上 provider 字段,说明每次搜索结果里记录了实际由哪家服务商执行。

对你意味着什么:这个开关一旦打开,Agent 在推理过程中构造的查询串会离开你的机器,去到一个外部搜索服务。查询串本身经常比你以为的敏感——排查线上问题时,Agent 很可能把内部服务名、异常堆栈里的路径、甚至客户标识拼进查询里。livecrawl 设成 preferred 还意味着它更倾向于实时抓取而不是吃缓存索引,这会让请求特征更明显。这不是「要不要用搜索」的问题,是「哪些工作区允许开这个开关」的问题。

五、团队共享连接,和个人连接是两码事

这是本篇最需要说清的一块。文档 packages/docs/cloud/share-with-your-team/shared-mcp-connections.mdx 里,管理员在 OpenWork Cloud 的 MCP Connections 面板 Add Connection,可以选预设(文档列举了 Notion、Linear、Stripe、Sentry、Exa、Context7)或直接填任意 MCP 服务器地址。真正的分岔在这一步:账号模式

  • Individual accounts:每个人用自己的身份登录,文档说这是 OAuth 服务器的默认模式。Agent 以的身份行动,提供方自己的权限规则照常生效,审计留在提供方那一侧、记在你名下。
  • One org account:管理员登录一次(机器人账号、服务账号或 API key),所有被授权的成员,其 Agent 都以同一个身份行动。

这两种模式的差异不是配置细节,是责任归属的差异。One org account 下,Slack 或 Notion 那边的审计日志会显示所有动作来自同一个账号——你没法从提供方侧回答「是谁让 Agent 干的」,只能回到 OpenWork 这一侧去追。而且这个共享身份的权限通常是所有人权限的并集,等于给每个被授权成员发了一张越权通行证。真要用它,得先想清楚谁能被授权,以及出事之后怎么归因。关于这种权限叠加的失控模式,站内Agent 权限膨胀有更一般的讨论。

成员那一侧看到的是三组状态:Needs your sign-in(等你自己登录)、Needs admin setup(等管理员配)、Ready to use(可用,含组织托管的账号)。文档还写了一句必须放在心上的话:你的登录态存在 OpenWork Cloud,而不是这台机器上——好处是换设备免重连,代价是这份凭据的保管方从你变成了控制面。

管理员侧的几条行为也值得记:成员只看得到被显式授权的连接(工作区级、团队级或按人),文档强调这个限制由服务端在每次请求上执行,而不是靠客户端界面藏起来;撤销连接或撤销某人的访问,对所有设备立即生效;而已经在桌面端自己配过某个工具的成员,发布组织连接不会覆盖或删除他们的本地配置——这条意味着「组织统一管理」并不会自动收编存量的个人连接,你得单独去清。

控制面还能反过来限制桌面端。desktop-policies.mdx 列出的策略键包括 Custom providersEnable OpenCode Zen ModelsMultiple workspacesControl SettingsManage ExtensionsBuilt-in ExtensionsWelcome Page。也就是说,管理员可以直接关掉成员本地加扩展的能力。被策略禁用的内置扩展会从常规目录里隐藏,并在隐藏视图里显示 Disabled by organization。策略由桌面端缓存,重载时生效,并在切换活跃组织、切换 Cloud 账号或每小时的桌面配置刷新时更新。

六、这几个模块分别管什么

组成部分它负责什么对应仓库位置你什么时候会碰到它
OpenWork Connect(成员页)桌面端登录组织已开放的服务、查看连接就绪状态packages/docs/start-here/connect-your-stack/connect-services.mdx组织已经帮你配好 Gmail/Slack/Notion,你只需要登录自己那份
Add Custom App(Extensions)加自建或本地 MCP 服务器,含 Advanced OAuth 预注册凭据packages/docs/start-here/connect-your-stack/add-an-mcp-server.mdx组织没提供、或者你在调自己写的 MCP 服务器
Slack 自定义 MCP 流程预注册 OAuth 应用 + 用户令牌作用域 + 本机回调packages/docs/start-here/connect-your-stack/connect-slack-mcp.mdx不走 Cloud、只想在本机接 Slack
内置网页搜索内置搜索工具的服务商与参数类型apps/app/src/lib/build-in-tools.tspackages/docs/start-here/connect-your-stack/enable-exa-search.mdx你在 Settings -> Advanced 里打开搜索开关时
MCP Connections(管理面)组织级发布连接、选账号模式、按人/团队授权packages/docs/cloud/share-with-your-team/shared-mcp-connections.mdx你是管理员,要统一管理团队的第三方连接
Desktop policies下发桌面端能力开关,限制本地扩展与设置packages/docs/cloud/share-with-your-team/desktop-policies.mdx你发现本地某个功能变灰并提示由组织禁用
OpenWork Connect MCP 端点让外部 MCP 客户端复用组织能力packages/docs/cloud/run-in-the-cloud/cloud-mcp.mdx你想在 Claude Code、Codex、Cursor 里用同一套能力
企业侧代码控制面与企业能力的实现ee/apps/(10 个)、ee/packages/(3 个),按 ee/LICENSE你要评估自托管、二次开发或商用边界时

顺带说清一个结构事实:这个仓库里 apps/ 有 4 个、packages/ 有 12 个,packages/docs/ 有 57 份 mdx(其中 model-context-protocol/ 目录下 10 份是各家客户端的接入指南),架构文档 docs/ 有 20 份 md,evals/ 有 26 份流程 md,apps/server/src/ 下有 138 个顶层 .tspackaging/ 提供三种分发方式,全仓受版本控制的文件 3490 个。仓库 README 把自己定位成「Claude Cowork 和 Codex 的开源替代」——这是项目自己的说法,本文只如实转述,不替它背书这个对标关系。

七、边界与代价:它明确不管的事

它不替你决定授权范围。 Slack 那一篇给了作用域对照表,也给了「选最小集合」的建议,但填哪些是你的事。填多了没有任何机制会拦你,OAuth 同意页会照单展示,你点了就是点了。

它管不了提供方的策略。 文档写得很直:如果 Slack 工作区禁止安装应用,得先找 Slack 管理员批,OpenWork 绕不过去;如果某个作用域被工作区策略或套餐限制,只能从两边同时删掉再重试。这类规则会调整,以各服务商官方最新说明为准。

本地路径把长期凭据放在了你机器上。Advanced OAuth 意味着一份 client secret 进入本机 MCP 配置。桌面应用被入侵、配置文件被同步进备份、误提交进仓库,都是现实存在的路径。这是「不依赖 Cloud」的直接代价。

Cloud 路径把凭据集中到了控制面。 好处是能一键撤销、跨设备生效、按人授权;代价是控制面成了新的高价值目标,而且管理员理论上拥有更多可见性。文档在 security-and-operations.mdx 里给了不少缓解措施——组织 API key 与 SCIM 令牌以哈希存储、SSO 配置加密存储、部分敏感数据库列用应用层 AES-256-GCM 加密(密钥来自 DEN_DB_ENCRYPTION_KEY)、特权路由要求较新的会话——但这些是部署方的责任清单,不是开箱即得的保证。文档自己也写了,私有或自托管部署下 TLS 证书、库存储加密、备份加密、私网策略都由运维方负责。

搜索开关是全局的。 文档把 Exa 描述为全局设置,不是按工作区的细粒度开关。你没法只在某一个「安全」的工作区里开它。

它不承诺任何路线图。 仓库里有 packages/docs/roadmap.mdx,但那是文档里的写法,不是本文的判断,也不构成对未来行为的承诺。

八、上手与避坑清单

别一上来就 Add Custom App 会踩是因为工程师习惯性走「高级」路径,觉得自己配更可控。结果是你把一份 client secret 放进了本机,还绕开了组织已经配好的连接。避法:先打开 Settings > OpenWork Connect 看一眼,能在里面找到的服务一律走那条路;文档也把自定义路径明确限定为「组织没通过 Connect 提供」的场景。

别把两个界面名字搞混。 会踩是因为文档里两种写法都出现过——connect-services.mdx 写的是 Settings > OpenWork Connectshared-mcp-connections.mdx 写的是 Settings > Connect。避法:认功能而不是认字面,找的是那个按「需要你登录 / 需要管理员配置 / 可用」三组分类的页面,界面文案会随版本变。

别拿只读作用域当安全底线。 会踩是因为「read」这个词让人放松。而文档给的读优先起步集合里包含私聊历史与成员邮箱。避法:按能力对照表逐条勾,写权限单独一轮评估再加;先跑一周只读,看 Agent 到底用到了哪几个工具,再补。

别忽略「工作区 / 全局」那个选择。 会踩是因为它在添加对话框里只是一个不起眼的选项。选了全局,这台服务器就跟着你所有工作区跑。避法:默认选当前工作区;只有确认过这台服务器无写权限、且确实每个项目都要用,才提升到全局。

别把重定向地址写错端口。 会踩是因为 Slack 那条回调是本机回环地址 http://127.0.0.1:19876/mcp/oauth/callback,跟大多数人熟悉的公网回调长得不一样,容易凭印象改。避法:从文档原样复制粘贴进 Slack 应用的 OAuth & Permissions;Slack 报重定向无效,先回去核这一行。

别把动态注册报错当成网络问题排查。 会踩是因为 Incompatible auth server: does not support dynamic client registration 这句话里有 auth server,很容易往连通性上想。避法:看到这句就直接去补 Advanced OAuth 里的 client ID 与 secret,重载引擎再试。

别以为发布了组织连接就统一了。 会踩是因为管理面上看着整齐了。但文档明说,成员已有的本地配置不会被删除或修改。避法:推组织连接的同时,单独发一轮通知让成员清掉本地重复的自定义 app,否则同一个服务会有两条权限不同的通路。

别在没定账号模式之前先建连接。 会踩是因为 Individual accountsOne org account 在界面上只是一个选项,但它决定了提供方那侧的审计能不能追到人。避法:建连接前先回答一个问题——出事之后,你要从哪里查是谁触发的?答不上来就别选 One org account

收尾自检

接完一轮之后,拿这五个问题过一遍:这条连接的凭据现在躺在哪(本机配置、Cloud、还是根本没有第三方凭据);Agent 以谁的身份行动(我自己、还是一个共享账号);撤销要去哪里点,多久生效;作用域清单里有没有我当初没细看就勾上的写权限;这台服务器是挂在当前工作区还是全局。五个都答得上来,这条连接才算是配好了。

想继续往下读,按你的角色选:只用桌面端就从 packages/docs/start-here/connect-your-stack/ 这一整个目录读起;要管团队就读 packages/docs/cloud/share-with-your-team/shared-mcp-connections.mdxdesktop-policies.mdx;要评估自托管与商用边界,先读根目录 LICENSEee/LICENSE 原文,再看 packages/docs/cloud/security-and-operations.mdx 里那份运维责任清单。

本篇属于一个把开源AI 工作流桌面应用 OpenWork逐层拆开讲的系列,整体地图见 OpenWork 是什么:把技能与 MCP 打包成能力的开源桌面应用;沿着这条线往下,还可以看 OpenWork 开源桌面应用:技能、插件与 MCP 各在哪一层起作用OpenWork 开源桌面应用怎么接模型:三条路径的机制与代价

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。