MCP 与已连接的工具
将 Nessie 连接到 MCP 服务器,并配对外部智能体,使其能以你的身份在 Nessie 中工作。
什么是 MCP
模型上下文协议(MCP)是一种小巧、开放的约定,用于向语言模型描述工具并调用它们。服务器声明自己能做什么:一份带名称的工具列表,每个工具都附有一个描述参数的 JSON Schema;客户端则发现这份列表,并代表模型调用这些工具。它的设计刻意保持朴素:价值在于,只要集成一次,就能与每一个支持该协议的模型和产品配合工作。
Nessie 同时位于两端
这一点值得准确说明,因为两个方向很容易混淆,而它们位于刻意分开的路径上。
- 出站。Nessie 是 MCP 客户端。你可以连接他人运营的服务器,它们提供的工具即可供你的智能体使用。管理接口为
/api/mcp/*。 - 入站。Nessie 同时也是 MCP 服务器。运行在其他地方的智能体(例如开发者电脑上的 Claude Code 或 Codex)可以调用你的看板和文档。该端点为
POST /mcp,刻意放在/api之外,以免两种截然相反的含义只隔着一个路径段。
将 MCP 服务器连接到 Nessie
目录条目与实例
有两类对象,区分它们很重要:
- 目录条目是服务器的定义,包括它的名称、传输方式以及认证方式。它只需编写一次,并且可以共享。
- 实例是某个目录条目安装在某一范围内,并附带能够访问它的凭证。为两个不同团队安装同一个条目,会产生两个实例。
大多数人从不直接接触这两者。管理后台的入口是位于 /admin/apps 的应用商店:它浏览的是同一份目录,但隐藏了连接细节;在某个列表上点击 Connect(连接),即可创建一个实例。需要直接操作原始对象的运维人员可以使用 /api/mcp/catalog 和 /api/mcp/instances。
如果某个服务器还不在目录中,可以调用 POST /api/mcp/discover 并传入一个 URL,它会返回该服务器对自身的描述;POST /api/mcp/library/import 则可以从公共服务器库中导入一个条目。首次连接还可以直接通过 POST /api/apps/custom 根据一个 URL 创建。
传输方式
Nessie 通过 HTTP 连接远程服务器,支持两种传输方式:可流式传输的 HTTP(当前的 MCP 传输方式)和较旧的 HTTP+SSE 传输方式。除非服务器只提供 sse,否则请选择 http。
对于你自己编写的连接器,会拒绝 stdio 传输。stdio 意味着 MCP 服务器是由宿主启动的本地进程,而让任意程序在 Nessie 服务器内运行,是运营人员无法有效审查的。仅以本地运行包形式发布的服务器,同样因这一原因被排除在库导入之外。协议字段中存在 ws 取值,但并未实现:使用它的连接在 Nessie 尝试打开时会失败。
Nessie 如何向服务器进行身份验证
一个目录条目需声明以下五种方式之一:none、api_key、bearer、basic 或 oauth2。
api_key 是请求头注入,条目中会说明使用哪个请求头以及前缀是什么。因此 Authorization: Bearer …、X-API-Key: … 和 X-Auth-Token: Token … 本质上是同一种方式,只是配置不同。密钥通过 POST /api/mcp/instances/:instanceId/secret 粘贴一次,之后永远无法再次读取;实例相关接口的任何响应都不会包含凭证引用。
oauth2 有两种形式。服务器端点固定时,端点直接写在目录条目中。服务器若发布了标准元数据,则完全无需配置:Nessie 会读取受保护资源和授权服务器文档,并通过动态客户端注册,或通过它在 well-known 路径下发布的客户端 ID 元数据文档,为自己创建客户端。无论哪种方式,握手流程都是:调用 POST /api/mcp/instances/:instanceId/oauth/start,随后服务商重定向回 GET /api/mcp/oauth/callback。state 令牌只能使用一次,保存在数据库中,有效期为十分钟,并且始终使用 PKCE。
有一条规则常常让人意外:即便是组织所有者,也无法把个人令牌或 OAuth 令牌附加到他人身上。共享凭证只适用于经过验证的 API 密钥类条目,其他情况一律拒绝。属于某个人的凭证,始终只属于那个人。
凭证存放在哪里
连接器机密和 OAuth 令牌包保存在你自己的 Postgres 数据库中,采用按用途划分的 AES-256-GCM 信封加密,密钥由部署环境中独立版本化的密钥环管理,并且只能通过不透明的 secret_* 引用访问。它们刻意不放在保存 Nessie 其他机密的外部机密保险库中。未配置加密存储的生产部署会拒绝启动,而不会去完成一个随后必然丢失的 OAuth 握手。
连接的可达范围
一个实例可以安装在六种范围之一:system、organization、project、team、channel 或 user。该范围是哪些运行可以使用它的硬性上限,而授权只能进一步收窄、不能放宽。因此,你用自己账户安装的连接器不会悄悄变成组织范围的连接器。
谁可以在哪里安装,遵循同样的结构:组织所有者和管理员可以在任何范围管理安装;其他人则在自己的 user 范围内安装连接器,并可以看到他们有权访问的共享安装。
为智能体授予工具
连接服务器并不会把它的工具交给任何人。安装实例时,Nessie 会对其进行探测,并把它宣布的每个工具投射到工具注册表中。管理后台的 /admin/advanced/tools 页面展示的正是这份注册表:内置工具、MCP 工具、捆绑工具和执行器工具统一列在一起,每个工具都标明了来源和状态。
被投射的工具有三种状态之一:active(可用)、pending_review(待审核)或 disabled(已停用)。来自共享范围安装的工具会以 pending_review 状态出现,必须先在该页面审核通过,智能体才能看到它们;来自你自己 user 范围安装的工具则会直接变为可用状态,因为唯一会暴露的人就是你自己。审核是持续进行的,而不是一次性的仪式:如果之后的探测发现某个工具的描述或 Schema 发生了漂移,它会回到 pending_review,在有人审阅该变更之前不再提供。
此后,哪些智能体可以调用一个可用的工具,由以下两种方式之一决定:
- 通过应用商店创建的连接必须有明确授权。只有授权记录中指定了某个工具,智能体才能获得该工具;授权会与该工具当前的指纹进行核对,因此工具一旦变化,就需要重新做出决定。
- 否则,该工具对其范围内可以触达的智能体默认开启,单个智能体则可以在该工具的详情面板中被关闭。
每条授权只指定一个主体(一个角色或一个智能体,二者只能选其一),并且拒绝优先于允许。对于 user 范围的连接,这个问题根本不会出现:该人员交谈过的任何智能体都能使用它,只有明确关闭才能阻止。
有一处很容易找错地方:管理后台中的 /settings/accounts 是已连接账户,包括 Slack、邮箱、模型订阅,以及支撑外部看板的 Linear、Jira、GitHub 和 Trello 的 OAuth 链接。MCP 连接器位于 /admin/apps,由它们生成的工具位于 /admin/advanced/tools。
工具调用的审批关卡
某些工具调用会停下来等待人工确认。发生这种情况时,运行并不会失败:该调用会被冻结,保存工具名称、参数的哈希值以及恢复所需的状态,并在 /api/approvals 中出现一条审批请求,等待有人作出回应。恢复操作是幂等的,因此重复的审批不会让调用执行两次。决策者会看到一份有界的摘要,说明正在请求什么;确切的参数和发起运行则保留在服务器端。
共有两类不同的关卡,配置方式也各不相同。
- 结构性关卡是某些内置工具本身的属性,无法配置。从智能体自己的邮箱或已连接邮箱发送邮件、发送 Gmail 草稿、断开邮件账户、创建、修改或取消日历事件,以及浏览器中一个会写入其未登录站点的操作,都以这种方式被拦截。它们的答复窗口为 24 小时,而不是通常的 30 分钟。
- 策略关卡来自
tool/invoke这一对上的策略规则。允许某次调用的规则可以附带requiresApproval;若没有经过验证的审批,策略评估会返回APPROVAL_REQUIRED,而不会执行。规则按先拒绝后允许的顺序评估,并按范围排序。
我们未能确认是哪一个管理后台页面会为某个工具编写 requiresApproval 策略规则。数据模型和执行机制都已就位,但我们无法确认写入此类规则的界面。
将外部智能体连接到 Nessie
这是另一个方向。Nessie 在 POST /mcp 上运行自己的 MCP 服务器,采用可流式传输的 HTTP,并且是无状态的:每次调用都携带自己的凭证,并解析自己的操作者,因此不存在需要把客户端绑定到某个副本的会话。ChatGPT、Claude、Claude Code、Codex,或某人笔记本电脑上的智能体,都可以在该人员已经使用的任何工具中操作你的看板、文档和客户。每个应用的分步说明见 连接 ChatGPT、Claude 及其他 AI 应用。
配对凭证究竟是什么
这一点务必理解清楚,因为人们最容易想到的模型(附带权限的 API 密钥)是错误的。
配对凭证从来不是一种身份。它指向一个人,每次工具调用都以这个人的身份运行,并调用与 HTTP 路由相同的服务函数。因此,智能体无法触及那个出借凭证的人、通过点击也无法触及的任何内容。权限范围只会收窄这一触达能力,不存在能够放宽它的范围。
有效性在每次调用时都会重新读取,绝不会直接相信令牌本身:包括吊销状态、过期时间、用户的令牌版本、其组织成员资格以及当前角色。降级、停用或“在所有设备上退出登录”,都会在智能体的下一次调用时生效,而不是等到凭证过期。
令牌是不透明的,以 nag1_ 为前缀,仅以 SHA-256 哈希形式存储,有效期为 90 天。在 MCP 端点之外的任何路由上使用它,都会返回专门的 403。在其他地方出示它并不是可以重试的部分成功,这正是为了确保“我把看板借给了智能体”不会变成“我把账户借给了它”。
两种连接方式
能够打开浏览器的应用(ChatGPT、Claude、Claude Code、Codex)使用 MCP 授权配置:带 PKCE(S256)的 OAuth 2.1,在 POST /mcp/oauth/register 进行动态客户端注册,并支持刷新令牌和资源指示符。应用会引导用户前往 /settings/security,用户在那里选择它可以执行的操作,以及它能够访问的团队,然后浏览器携带授权码返回该应用。其访问令牌有效期为一小时,并可刷新;连接本身有效期为 90 天。
命令行智能体无法控制浏览器,也无法托管回调 URL,因此改用设备授权流程(RFC 8628)进行配对。
- 智能体调用
POST /mcp/auth/device,传入clientName和它希望获得的scopes。它会返回一个device_code、一个简短的user_code、一个verification_uri,以及一个已在查询参数中包含该代码的verification_uri_complete,同时返回轮询用的interval和expires_in。智能体随后打印该 URI。 - 用户在已登录的状态下打开该 URI,即
/settings/security。他们会看到是哪个智能体在请求、它将以谁的账户工作、授权有效期至何时,以及它将能够执行的确切操作,然后选择允许或不允许。 - 智能体使用
deviceCode轮询POST /mcp/auth/token,并正确处理authorization_pending、slow_down、expired_token和access_denied。成功时,它会收到{ access_token, token_type, scope, expires_at }。
无论哪种方式,都必须有一个人工步骤,因为凭证继承了某个人的权限,所以必须由那个人选择出借。这里没有任何东西能凭空产生访问权限。整个组织也可以在 /admin/security?tab=programs 中关闭连接功能,而且这一设置会在有人允许连接时检查,而不是在智能体开始发起连接时检查。
发现机制
未经身份验证的 POST /mcp 会返回 401,并附带一个指向 /.well-known/oauth-protected-resource(RFC 9728)的 WWW-Authenticate 响应头,该文档声明此来源即为授权服务器;其元数据位于 /.well-known/oauth-authorization-server(RFC 8414),其中列出了注册、授权、令牌和吊销端点。客户端只要拿到地址,这条链路就足以让它完成全部工作。如果智能体需要从一个域名查找地址,可以读取 /.well-known/ai-catalog.json,它会指向位于 /mcp/server-card 的 Server Card(MCP Server Cards,SEP-2127)。设备授权的端点列在受保护资源文档的 x-nessie-device-authorization 下。
部署还可以在第二个、更短的地址上提供服务:Nessie Cloud 的 https://nessie.works/mcp 会被代理到 API。通过这种方式连接的客户端会被返回该地址作为资源,而登录仍然在 API 上进行。这里不能使用重定向,因为客户端会拒绝任何指向其提供地址以外地址的元数据。运营人员需要在 NESSIE_MCP_ALIAS_ORIGINS 中列出这些来源。
权限范围与工具
权限范围刻意保持粗粒度:无人能解释的权限范围,也就没人能正确设置。连接覆盖多个团队时,可以用 nessie_team_list 列出这些团队,之后的每个其他工具都需要指定其 teamId。
| 权限范围 | 可访问的内容 |
|---|---|
boards_read | 项目、看板和工单,包括其评论、文件、标签、类型和清单 |
boards_write | 创建、编辑和移动工单,以及工单上的一切内容 |
documents_read | 知识空间、文档和电子表格 |
documents_write | 起草和编辑这些内容,并请求发布文档 |
clients_read | 客户收件箱、联系人、联系人列表、电子报和联系表单 |
clients_write | 添加备注、接手、分配和关闭会话,管理联系人列表,以及准备电子报 |
clients_send | 客户会收到的任何内容:回复、首封邮件或短信。除非用户亲自勾选,否则绝不会默认勾选 |
website_tools | 你的团队已登记网站的页面工具,在你可以控制的托管浏览器中操作。除非用户亲自勾选,否则绝不会默认勾选 |
缺少所需权限的调用会以工具结果的形式返回,并说明需要申请的权限,而不是传输层错误。因此模型可以汇报有用的信息,而不是反复撞墙重试。
外部看板并没有单独的一组工具。无论看板的工单来自 Nessie,还是从 Linear、Jira、GitHub 或 Trello 镜像而来,它都是一块看板,因此同一套工具即可覆盖两者,并且每个工单都会标明其来源。真正的差别在于写入:对于从只读来源镜像而来的看板,写入请求会被拒绝,并以文字告知智能体,该工单由另一个系统所有,重试也无济于事。
发布文档需要审批,而不是权限范围
智能体负责起草,由人负责发布。对于 Nessie 自身的智能体,这一规则通过直接拒绝智能体身份的操作来执行,但配对凭证是以人的身份解析的,因此该拒绝无法拦截它。关卡必须设在别处。
因此,nessie_doc_publish 并不会直接发布。它会打开一个 knowledge.page.publish 审批(与 Nessie 智能体所打开的审批相同),并返回 { status: "awaiting_approval", approvalId }。再次轮询时,返回的是同一条请求,而不会另开一条。发布决定与 Nessie 中其他所有发布决定的做法一致:由人针对该文档、在它存在的那一刻做出。
刻意不设发布权限范围。过去曾有一个权限范围:配对时勾选一下,即表示该智能体可以发布。但它已被撤销。它一次性决定了九十天内的事,而 Nessie 在其他所有地方都是针对每份文档单独询问这一问题的;而且它是在文档存在之前做出的决定,因此勾选它的人无从知道自己同意发布的究竟是什么。nessie_doc_publish 需要 documents_write,因为请求审批并非没有代价,它需要读取页面,并把决定交到某人面前,但该权限范围本身不授予任何发布能力。
请求上记录了两种身份,这一区分至关重要。请求方是凭证,而不是它所代表的人。必需的审批人是那个人,并且只有他们可以作出答复。如果请求把该人员记为请求方,那么禁止审批自己请求的规则就会取消唯一有权作出决定之人的资格。
查看和吊销已配对的凭证
用户自己配对的智能体会列在 /settings/security 中,每个都附有标签和吊销控件。所有者或组织管理员可以在 /admin/security?tab=programs 中看到组织内的每一个凭证:其名称、可执行的操作、是否仍然有效,以及它借用的是谁的账户,并可以吊销任何一个。个人列表仅限本人查看:有效凭证的列表就是立足点的清单,而不是可供普遍阅览的资料。
通过凭证进行的写入会归属于出借凭证的人,并在审计元数据中附带该凭证的 ID,因此日志能够区分某人自己的编辑与他的智能体代为进行的编辑。
目前尚不支持的功能
明确说明这些内容,因为协议的能力与某个具体服务器的能力之间的差距,往往正是文档出错的地方:
- 聊天、频道、运行和智能体管理尚未通过 MCP 对外开放。
- 外部智能体负责准备电子报,由人负责发送:没有任何操作能发送、排期或删除营销活动,邮件本身则在设计器中编写。
- 联系人的关联与合并、CRM 结构变更、导入以及渠道设置,仍然需要人员在浏览器中完成。
- 连接开关是组织级设置。团队级和个人级的覆盖设置,目前团队或个人都无法设置。