第 6 章 · 02 MCP 设置页与工具接入


第 6 章 · 02 MCP 设置页与工具接入

本节摘要:插件架第二层:MCP(Model Context Protocol)设置页。如果说 Skills 教 agent「怎么做」,MCP 就是给 agent「发工具」——通过一个标准化协议,任意外部 MCP server(GitHub、Slack、数据库、浏览器……)都能把一组工具暴露给 agent 后端。本节从 MCPServerConfig 类型出发,走读 src/components/features/mcp-page/(约 1904 行)的市场浏览、安装表单与已装服务器管理,再精读 src/api/mcp-service/mcp-service.api.ts 的连通性测试(stdio/SSE 两种传输、凭据替换与脱敏、OAuth 授权流程),最后厘清 MCP 在 Canvas 架构中的定位:配置在 Canvas、连接在 agent-server、受益者是会话里的 agent。
内容来源:src/types/mcp-server.tssrc/api/mcp-service/mcp-service.api.tssrc/components/features/mcp-page/specs/mcp-settings.md
⚠️ 注意:MCP server 由 agent-server 进程连接(stdio 是它的子进程,SSE/SHTTP 是它的网络连接),浏览器从不直连 MCP server。Canvas 做的是「配置编辑器+连通性体检」,真正跑工具的是后端——这决定了下文所有代码的形状。

学习目标

  1. 说清 MCP 协议的概念与三种传输(stdio/SSE/SHTTP)。
  2. 描述 MCPServerConfig 的字段与配置表单的生成方式。
  3. 读懂 testServer 的本地执行、云短路、凭据替换与脱敏后处理。
  4. 理解 OAuth 授权的弹窗+轮询状态机。
  5. 厘清 MCP 在 Canvas 中的定位(给 agent 后端供工具)。

一、MCP 是什么:标准化的工具提供协议

MCP(Model Context Protocol)是一个开放标准,定义「工具提供方(server)」与「工具使用方(client,通常是 agent 宿主)」之间的契约:server 声明自己有哪些工具(名称、JSON Schema 参数、描述),client 按需调用并取回结果。它的意义与第 4 章的 ACP 同构——ACP 标准化了「agent 与客户端」,MCP 标准化了「工具与 agent」——两者叠加,agent 的能力生态变成乐高:换 agent 不换工具,换工具不换 agent。

传输层有三种形态(MCPServerType):

  • stdio:agent-server spawn 一个本地命令子进程(如 npx -y @some/mcp-server),经标准输入输出对话——适合本地工具;
  • SSE(Server-Sent Events):连接一个远程 URL,HTTP 长连接双向通信;
  • SHTTP(Streamable HTTP):新一代远程传输,普通 HTTP 请求承载流式响应。

对 Canvas 而言三者只是配置形状不同:stdio 填命令+参数+环境变量,远程填 URL+请求头(+认证)。

二、MCPServerConfig:一张配置卡的数据形状

// src/types/mcp-server.ts(全文节选) export type MCPServerType = "sse" | "stdio" | "shttp"; export interface MCPServerConfig { id: string; type: MCPServerType; name?: string; url?: string; // 远程传输(SSE/SHTTP)的端点 headers?: Record<string, string>; // 远程传输的自定义请求头 timeout?: number; command?: string; // stdio 传输的启动命令 args?: string[]; // stdio 传输的命令参数 env?: Record<string, string>; // stdio 传输的环境变量(常为凭据) auth?: MCPAuthCredential; // api_key/bearer/oauth2 认证 /** * agent 是否可使用该 server。缺省即启用——只有显式 false 才禁用, * 因此存量配置不受影响。 */ enabled?: boolean; }

两个字段设计有讲究:env 是 stdio server 的凭据通道(API key 经环境变量注入子进程,与第 4 章 ACP 的 secret-as-env 同一思路);enabled 采用「缺省即启用」的宽松默认并显式注释动机——只有 false 才禁用,保证存量配置在字段引入前后行为不变。这个配置最终落在 agent settings 的 mcp_config 上(第 4 章的 AgentKind 注释已埋过伏笔:ACP 模式下 mcp_config 依然生效——外部 agent 也能挂 MCP 工具)。

三、mcp-page UI:市场、表单与已装管理

src/components/features/mcp-page/(约 1904 行)的文件清单就是一张功能地图:marketplace-sectionmarketplace-card 渲染集成市场——条目来自 @openhands/extensions/integrationsINTEGRATION_CATALOG(第 5 章推荐自动化卡片同款来源,第 1 节预告过);installed-servers-sectioninstalled-server-card 管理已装服务器;custom-server-editor 支持手写任意 MCP server 配置;mcp-server-health 显示健康状态;mcp-toolbarmcp-section-filter* 提供搜索与分类过滤;mcp-logo-stack-badge 拼装品牌 logo。

核心是 install-server-modal.tsx(735 行)——配置表单不是手写的,是从市场条目生成的:

// src/components/features/mcp-page/install-server-modal.tsx(节选) function getRemoteHeaderFields( option: McpMarketplaceConnectionOption | undefined, ): MarketplaceField[] { if (option?.transport.kind !== "shttp" && option?.transport.kind !== "sse") { return []; // stdio 传输没有请求头字段 } return option.transport.headerFields ?? []; } function makeInitialState(entry: MarketplaceEntry): FieldState { const values: Record<string, string> = {}; const savedAsSecret: Record<string, boolean> = {}; const option = getInstallableMcpConnectionOption(entry); const template = option?.transport; if (template?.kind === "stdio") { for (const field of template.envFields ?? []) { values[field.key] = ""; // 密码字段预勾选"存为 secret";非密码字段默认关闭 } } // ... }

市场条目自带「连接选项」描述:stdio 条目声明 envFields(要收集哪些环境变量、哪些是密码),远程条目声明 headerFields 与认证策略(api_keybeareroauth2)。表单按描述渲染,密码字段默认勾选**「存为 secret」**(save-as-secret-toggle)——凭据不落明文配置,而是进 agent-server 的 secret store,配置里只留引用,读取时再替换。isCredentialOptional 决定凭据是否可留空(有些 server 本地匿名可用)。这套「目录描述表单」的模式与第 5 章 setup 清单同宗:数据与渲染分离,新增集成零前端改动。

装完之后的管理面在 installed-servers-sectioninstalled-server-card:每张卡片显示连接形态(命令或 URL)、启停开关(写回 enabled 字段)、编辑入口与实时健康徽标。健康探测由 src/api/mcp-health/probe-mcp-server-health.ts 驱动——安装成功时 seedMcpServerHealth() 立即播种一次初始状态,后台周期性复测;mcp-server-health.tsx 把「正常/失联/凭据失败」画成不同颜色。findInstalledEntryMatch() 则负责反向对账:市场条目与已装配置互相匹配(marketplace-card 据此显示「已安装」角标),手动改过的自定义服务器不会误配到任何市场条目上。

四、mcp-service.api.ts:连通性测试的深水区

保存前要「体检」:连得上吗?工具列表是什么?凭据有效吗?McpService.testServer() 是答案,但它的第一段代码就颠覆直觉:

// src/api/mcp-service/mcp-service.api.ts(节选) static async testServer(server: MCPServerConfig): Promise<ExtendedMCPTestResponse> { // MCP 连通性测试端点在本地 agent-server 上。它从该进程的环境里 // spawn 配置的 stdio 命令/发起 SSE 或 SHTTP 连接。云后端不把该端点 // 暴露给前端——MCP server 实际会跑在云沙箱里,用户开会话前浏览器 // 根本够不到它。此处若对云会话调 getAgentServerClientOptions() 会抛 // NoBackendAvailableError 并卡死整个安装流程。短路返回合成成功, // 让保存继续;真实连接失败会在会话运行时暴露。 if (getActiveBackend().backend.kind === "cloud") { return { ok: true, tools: [] }; } const { host, apiKey } = getAgentServerClientOptions(); const client = new MCPClient({ host, ...(apiKey ? { apiKey } : {}) }); try { const { request, substituted } = await buildMcpTestRequest(server); const result = (await client.testServer(request)) as ExtendedMCPTestResponse; return finalizeMcpTestResponse(result, validation, [substituted, server]); } finally { client.close(); } }

「云后端返回合成成功」是精心权衡的谎言:云沙箱里的 MCP server 浏览器测不到,硬测只会报错卡流程;不如放行保存,把真相留给会话运行时。测试请求的构造里藏着凭据的处理链:buildMcpTestRequest()substituteRedactedMcpCredentials() 把配置里的 secret 引用换回真实值(配置在 UI 上是被脱敏的),再把脱敏前后的两份配置都传给 finalizeMcpTestResponse()——错误信息与工具结果要对着两份配置做秘密擦除redactMcpSecrets),防止 API key 泄漏进错误提示。finalize 还做一层「凭据校验」:如果目录声明了探测工具调用(如列一次仓库),且 server 确实公示了该工具,则把失败的只读调用翻译成 error_kind: "credentials"——但注释强调,只有工具确实出现在 tools 列表里才这么判:一个不暴露该工具的 server 变体(比如 hosted 版)必须降级为「连通成功」,而不是误报「凭据错误」。

OAuth 是最重的一段:authorizeOAuth() 先开一个空白弹窗占位(避免浏览器拦截后开的窗口),startOAuth 拿到 job_idauthorization_url,轮询状态直到 callback_ready 才把弹窗导航到授权页,随后以每秒一次的频率轮询最多 120 秒(OAUTH_MCP_TEST_TIMEOUT_SECONDS),等用户在弹窗里完成授权、状态变成 succeededfailed。注释顺带交代了一个类型边界的无奈:typescript-client 把 oauth_state 的递归 JSON 类型生成为 unknown,线上形状没变,本地收窄回来即可。

五、MCP 在 Canvas 中的定位:给 agent 后端供工具

把 MCP 放回全书坐标系:

  • 配置在 Canvas:mcp-page 编辑 MCPServerConfig[],市场表单降低门槛,secret 机制保凭据安全;
  • 连接在 agent-server:stdio 是它的子进程、SSE/SHTTP 是它的网络连接,会话启动时按 mcp_config 建立;
  • 受益者是 agent:无论 OpenHands 原生 agent 还是第 4 章的 ACP agent(Claude Code 等),都能通过挂载的 MCP server 获得额外工具——工具调用出现在第 2 章对话舱的事件流里,与内置工具同屏渲染;
  • 与 Skills 互补:MCP 给 agent「手」(可调用接口),Skills 给 agent「脑」(流程知识);一个 GitHub MCP server 让 agent 能改 PR,一个 code-review skill 教 agent 怎么评审 PR——第 5 章的 PR 自动评审自动化,正是两者合力的舞台。

顺带做一次跨库对照,巩固概念:在 smolagents 那类 Python agent 库里,「给 agent 加能力」往往意味着写一个 Python 工具类注册进 agent(Tool 子类、@tool 装饰器);MCP 把这件事从「宿主内编码」变成「跨进程协议」——server 与 agent 可以由不同人、不同语言实现,靠标准消息面协作。代价是多了一层连接管理(这正是 mcp-page 存在的理由),收益是工具生态的即插即用。Canvas 选择了协议路线,也就要承担协议路线的工程成本:传输差异、凭据安全、连通性体检、OAuth 流程——本节走读的每一块代码都是在付这笔「标准化税」。

💡 驾驶舱要点:MCP 设置页最大的工程亮点是诚实的边界感——云后端测不到就明说「留到会话运行时」,探测工具没公示就不误报凭据错误,错误信息先脱敏再展示。给不可靠的外部世界做 UI,宁可少下结论,不可错下结论。

本节要点回顾

  • MCP 是标准化的工具提供协议:server 声明工具(JSON Schema),client(agent 宿主)调用;三种传输 stdio/SSE/SHTTP,分别对应命令子进程与远程 URL 两类配置形状。
  • MCPServerConfig:stdio 填 command/args/env,远程填 url/headers/authenabled 缺省即启用以兼容存量;配置落 agent settings 的 mcp_config,ACP 会话同样可用。
  • mcp-page(约 1904 行):市场来自 INTEGRATION_CATALOG,安装表单由条目的 envFieldsheaderFields/认证策略生成;密码字段默认「存为 secret」,配置留引用、用时替换。
  • testServer:本地后端经 agent-server 真连真测;云后端短路返回合成成功(沙箱里的连接浏览器够不到);测试链路完成凭据替换→调用→双份配置脱敏→凭据错误分类;OAuth 走「预开弹窗+轮询 callback_ready+最长 120 秒」状态机。
  • 定位:配置在 Canvas、连接在 agent-server、受益者是会话里的 agent;与 Skills「手与脑」互补,支撑第 5 章的自动化场景。

下一节:03 Canvas Extensions 扩展体系——插件架第三层,也是最强的一层:改变 App 本身。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U