MCP 集成

本地 stdio 与远程 HTTP / SSE 服务器的连接与五种状态、工具与资源如何交给模型、带 CSRF 校验的 OAuth 流程,以及 opencode mcp 命令。

作者 David更新于 第 25 篇(共 38 篇)

MCP(Model Context Protocol)的实现集中在 packages/opencode/src/mcp/:index.ts 管连接与状态,catalog.ts 把工具、提示、资源翻译成 OpenCode 的形状,auth.ts、oauth-provider.ts、oauth-callback.ts 负责远程服务器的 OAuth,browser.ts 只做一件事——打开浏览器。

配置写法

字段定义在 packages/core/src/v1/config/mcp.ts,以 type 区分两类:

{
  "mcp": {
    "local-tools": {
      "type": "local",
      "command": ["bunx", "my-mcp-server", "--stdio"],
      "environment": { "API_TOKEN": "{env:API_TOKEN}" },
      "timeout": 10000
    },
    "docs": {
      "type": "remote",
      "url": "https://docs.example.com/mcp",
      "headers": { "X-Team": "infra" },
      "oauth": { "scope": "read" }
    },
    // 只写 enabled:开关一个在别处(例如组织下发的远端配置)定义好的服务器
    "jira": { "enabled": true }
  }
}
  • command 是数组,首项为可执行文件;cwd 相对实例目录解析;子进程环境是 process.env 叠加 environment。
  • 远程服务器默认挂上 OAuth provider 做自动探测,oauth: false 才彻底关闭;oauth 对象可给 clientId、clientSecret、scope、callbackPort、redirectUri。
  • 只含 enabled 的条目是 schema 专门留的口子,靠深合并补全;合并后仍没有 type 的条目,运行时记一条错误并忽略。

timeout 的 schema 描述写着“默认 5 秒”,但代码里连接与列工具的兜底都是 DEFAULT_TIMEOUT = 30_000;工具调用依次取服务器的 timeout、experimental.mcp_timeout,都没有时交给 SDK 的默认值。

连接:本地 stdio,远程先 HTTP 后 SSE

MCP.state 在实例内首次被访问时(列命令、组装工具或查询状态)建立,届时并发连接所有启用的服务器。本地服务器用 StdioClientTransport 起子进程;远程服务器先试 StreamableHTTPClientTransport,失败再退回 SSEClientTransport——但如果失败原因是鉴权,就不再尝试下一种,而是把状态置为 needs_auth(弹出提示运行 opencode mcp auth <name>),或在服务器不支持动态注册时置为 needs_client_registration。

客户端声明的能力只有一项:

const CLIENT_OPTIONS = {
  capabilities: {
    // https://github.com/anomalyco/opencode/issues/11948
    // sampling: {},
    // https://github.com/anomalyco/opencode/issues/23066
    // elicitation: {},
    // https://github.com/anomalyco/opencode/issues/2308
    roots: {},
    // https://github.com/anomalyco/opencode/issues/28567
    // tasks: {},
  },
} satisfies ClientOptions

(packages/opencode/src/mcp/index.ts:39-50)

sampling、elicitation、tasks 都被注释掉,每行挂着对应的 issue;唯一开启的 roots 用实例目录作答。

五种状态

状态含义
connected已连接,工具列表已缓存
disabledenabled: false,或被手动断开
failed连接或列工具出错,附错误信息
needs_auth需要 OAuth 授权
needs_client_registration需要预先注册的 client ID

连接建立后还挂着三个监听:连接断开时标为 failed 并广播 mcp.tools.changed;收到 notifications/tools/list_changed 通知时重新拉取工具定义;服务器日志按级别转成 OpenCode 自己的日志。实例销毁时,对 stdio 服务器先用 pgrep -P 递归找出子孙进程逐个 SIGTERM(Windows 上跳过),再关闭连接。

交给模型:工具、资源、提示与说明

工具名是「服务器名_工具名」,两段都把 [a-zA-Z0-9_-] 以外的字符换成下划线:

export const sanitize = (value: string) => value.replace(/[^a-zA-Z0-9_-]/g, "_")

export const toolName = (clientName: string, name: string) => sanitize(clientName) + "_" + sanitize(name)

(packages/opencode/src/mcp/catalog.ts:117-119)

对照 Kimi Code 的做法:那边用 mcp__ 前缀、双下划线分隔,并对超过 64 字符的名字做哈希截断;这里没有前缀,catalog.ts 里也没有长度处理。

convertTool() 把 MCP 工具包成 AI SDK 的 dynamicTool:输入 schema 强制为 type: "object" 且禁止额外属性;调用时开启 resetTimeoutOnProgress,注释解释了为什么要塞一个空的 onprogress——“The MCP SDK only sends a progress token when this hook is present, enabling timeout resets.”;结果带 isError 就抛错;只有 structuredContent 时序列化成文本。列工具若因 outputSchema 的引用解析失败,会换一个忽略它的宽松 schema 重试。

权限:session/tools.ts 在每次调用前以工具名本身作为权限键询问,所以能用通配符管住整个服务器:

{ "permission": { "github_*": "deny", "docs_search": "allow" } }

pattern 为 * 的 deny 规则还会让这些工具从请求里整个消失(Permission.disabled),模型根本看不见。

资源:只要有服务器声明了 resources 能力,就额外注册 list_mcp_resources、list_mcp_resource_templates、read_mcp_resource 三个合成工具,走 read 权限,pattern 形如 mcp:<server>:<uri>。用户也能在输入框里直接引用资源;二进制内容只收白名单 MIME,上限 10 MB。

提示变成斜杠命令(见上一篇)。服务器说明——initialize 返回的 instructions——被包进 <mcp_instructions> 放入系统提示词;某服务器的工具对当前 agent 全部被禁时,它的说明也一并省略。开启 Code Mode 后,MCP 工具不再逐个注册,改由 execute 工具统一调度。

OAuth:state、回调与延迟提交

图表加载中…
  • 回调地址默认 http://127.0.0.1:19876/mcp/oauth/callback,可用 callbackPort 或 redirectUri 修改;端口已被占用时不再另起服务器,等待回调最长 5 分钟。
  • 回调缺少 state,或 state 不在挂起表中,一律按 CSRF 拒绝。
  • 没配 clientId 时走动态客户端注册。注册结果与 token 存进数据目录的 mcp-auth.json(权限 0o600,读写加文件锁),并绑定服务器 URL:URL 一变,旧凭据自动失效。
  • 授权期间用的是 McpOAuthPendingProvider,token 与注册信息先放内存,成功后才 commit() 落盘,半途失败不会留下脏凭据。

opencode mcp 命令

子命令作用
mcp add [name]添加服务器,用 jsonc-parser 写入以保留注释
mcp list / mcp ls列出服务器与连接状态
mcp auth [name]发起 OAuth;mcp auth list 查看授权状态
mcp logout [name]删除已存凭据
mcp debug <name>手工发一次 initialize,诊断 401、WWW-Authenticate 与动态注册

非交互添加(写入全局配置):

opencode mcp add docs --url https://docs.example.com/mcp --header "X-Team=infra"
opencode mcp add local-tools --env API_TOKEN=xxx -- bunx my-mcp-server --stdio

交互模式下,若当前目录是 git 仓库,会先问写进项目还是全局。服务端同样暴露了状态查询、运行时添加、连接与断开以及整套授权接口(server/routes/instance/httpapi/handlers/mcp.ts)。

V2 的形状

V2 配置(packages/core/src/config/mcp.ts)把服务器收进 mcp.servers,用 disabled 取代 enabled,OAuth 字段改为蛇形命名,超时拆成 startup(建立连接与初始化)和 request(每个请求)两个预算:可以在 mcp.timeout 设全局默认,再在单个服务器上覆盖其一。

下一篇:LSP 与格式化器——38 个内置语言服务器如何按需启动,诊断又怎样回到模型手里。

本页目录