跳转到内容

MCP 源码解读

模块概述

MCP(Model Context Protocol)模块是 OpenCode 的外部工具集成通道,位于 packages/opencode/src/mcp/。通过 MCP 协议,OpenCode 可以连接到各种外部工具服务器——数据库客户端、浏览器自动化、搜索引擎、代码分析工具等——将它们的能力统一暴露为 Agent 可调用的工具。MCP 让 OpenCode 的能力不再局限于内置工具集。

模块的核心入口 index.ts(约 922 行)导出 MCP 命名空间,采用 Effect Service 架构模式。对外暴露 start()stop()client()status()tools() 等函数。模块内部通过 Instance.state() 管理全局状态,包含 clients: Record<string, Client>status: Record<string, Status>,每个 MCP 服务器连接独立管理,一个服务器崩溃不影响其他。

核心设计选择:

  • Effect Service 模式提供类型安全的依赖注入和资源生命周期管理
  • 鉴别联合类型(discriminated union)精确表达客户端的五种状态
  • convertMcpTool 将 MCP 工具透明转换为 AI SDK dynamicTool,Agent 无需感知底层协议差异
  • OAuth 2.0 + PKCE 完整实现支持需要认证的远程 MCP 服务器
  • 递归进程清理确保 stdio 模式下不会遗留孤儿进程

关键文件

文件路径行数职责
src/mcp/index.ts~922模块主入口:MCP 命名空间,Effect Service 模式,客户端创建、工具发现、OAuth 流程、进程清理
src/mcp/auth.ts~174McpAuth 命名空间,OAuth 令牌的 Zod schema 定义与持久化存储(mcp-auth.json,权限 0o600
src/mcp/oauth-callback.ts~217McpOAuthCallback 命名空间,本地 HTTP 回调服务器(端口 19876),处理 OAuth 重定向并提取授权码
src/mcp/oauth-provider.ts~186McpOAuthProvider 类,实现 OAuthClientProvider 接口,管理 OAuth 客户端元数据、令牌和 PKCE 流程

注意:MCP 模块没有独立的 client.tstransport.ts 文件。传输层实现来自 @modelcontextprotocol/sdk npm 包,客户端逻辑全部集中在 index.ts 中。这种设计将协议实现委托给 SDK,OpenCode 专注于连接管理、工具转换和认证编排。

类型体系

Status — 客户端状态鉴别联合类型

const Status = z.discriminatedUnion("status", [
  z.object({ status: z.literal("connected") }),
  z.object({ status: z.literal("disabled") }),
  z.object({ status: z.literal("failed"), error: z.string() }),
  z.object({ status: z.literal("needs_auth"), url: z.string() }),
  z.object({ status: z.literal("needs_client_registration") }),
])

五种状态的含义与转换路径:

状态含义来源
connected已成功连接,工具可用create() 成功、finishAuth() 成功
disabled用户在配置中禁用该服务器start() 检查 mcp.disabled
failed连接失败create() 抛出异常、OAuth 失败
needs_auth需要 OAuth 认证,附带授权 URL远程服务器返回 401
needs_client_registration需要先注册 OAuth 客户端远程服务器未发现已注册的客户端

状态转换链:

disabled ──────────────────────────────────── (配置控制,不参与连接)
needs_client_registration → needs_auth → connected
                                ↑               ↓
                             failed  ←──── (连接/OAuth 失败)

McpAuth 类型(auth.ts)

// OAuth 令牌结构
const Tokens = z.object({
  access_token: z.string(),
  token_type: z.string(),
  scope: z.string().optional(),
  expires_at: z.number().optional(),   // Unix 时间戳(秒)
  refresh_token: z.string().optional(),
})

// OAuth 客户端信息
const ClientInfo = z.object({
  client_id: z.string(),
  client_secret: z.string().optional(),
  client_secret_expires_at: z.number().optional(),
  redirect_uris: z.array(z.string()).optional(),
  grant_types: z.array(z.string()).optional(),
})

// 持久化条目:按 serverUrl 索引
const Entry = z.object({
  serverUrl: z.string(),
  tokens: Tokens.optional(),
  clientInfo: ClientInfo.optional(),
  codeVerifier: z.string().optional(),
  state: z.string().optional(),
})

核心流程

传输层与客户端创建

MCP.create(key, mcp) 是客户端创建的核心函数。它根据配置选择传输方式:

// 本地进程:stdio 传输
if (mcp.type === "stdio") {
  transport = new StdioClientTransport({
    command: mcp.command,
    args: mcp.args,
    env: { ...process.env, ...mcp.env },
    stderr: "pipe",
  })
}

// 远程服务:优先 StreamableHTTP,回退 SSE
if (mcp.url) {
  const url = new URL(mcp.url)
  transport = new StreamableHTTPClientTransport(url, {
    // 如果该 URL 有已存储的 OAuth 凭据,注入 OAuthProvider
    authProvider: hasStoredCredentials ? oAuthProvider : undefined,
  })
}

三种传输方式的技术特征:

传输类型类名通信方式适用场景
stdioStdioClientTransport标准输入/输出管道本地 MCP 服务器进程(最常用)
StreamableHTTPStreamableHTTPClientTransportHTTP POST + 流式响应新版远程 MCP 服务器
SSESSEClientTransportHTTP Server-Sent Events旧版远程服务器兼容方案

远程传输支持自动回退:先尝试 StreamableHTTPClientTransport,如果服务器不支持则回退到 SSEClientTransport。远程传输还可注入 McpOAuthProvider 实现透明认证。

Effect 资源管理 — acquireUseRelease

create() 函数使用 Effect 的 acquireUseRelease 模式管理传输连接的生命周期:

yield* Effect.acquireUseRelease(
  // acquire: 建立连接
  Effect.tryPromise(() => client.connect(transport)),

  // use: 连接成功后的操作(获取工具列表等)
  async (connected) => {
    // ... 工具发现、状态更新 ...
  },

  // release: 无论成功或失败,都确保关闭 transport
  (connected, exit) => {
    if (exit._tag === "Failure") {
      // 连接失败 → 关闭 transport 释放资源
      return Effect.tryPromise(() => transport.close())
    }
  },
)

这种模式确保即使连接过程中发生错误,transport 也会被正确关闭,不会泄漏文件描述符或子进程。

并行初始化所有 MCP 服务器

start() 函数使用 Effect 的 forEach 并行初始化配置中的所有 MCP 服务器:

// 并行初始化,不限制并发数
yield* Effect.forEach(
  Object.entries(config.mcp),
  ([key, mcp]) => create(key, mcp),
  { concurrency: "unbounded" },
)

concurrency: "unbounded" 表示所有服务器同时启动,一个服务器启动慢不会阻塞其他。如果某个服务器连接失败,它会被标记为 failed 状态,但不会影响其他服务器的初始化。

工具发现与注册

MCP 服务器连接成功后的工具发现流程:

  1. defs() 函数调用 client.listTools() 获取服务器声明的工具列表
  2. 设置超时保护,防止无响应服务器阻塞整个启动流程
  3. convertMcpTool(mcpTool, client, timeout) 将每个 MCP 工具转换为 AI SDK 的 dynamicTool
  4. 工具命名规则:sanitizedClientName + "_" + sanitizedToolName(例如 github_search_repositories
  5. 注册 tools/list_changed 通知处理器,服务器动态增删工具时自动重新发现

convertMcpTool — MCP 工具到 AI SDK 工具的桥接

这是 MCP 模块最核心的转换函数,将 MCP 协议的工具定义转换为 AI SDK 的 dynamicTool

function convertMcpTool(
  mcpTool: MCPToolDef,
  client: MCPClient,
  timeout?: number,
): Tool {
  // 1. 参数 schema 转换:直接透传 MCP 的 inputSchema
  const inputSchema = mcpTool.inputSchema
  const schema: JSONSchema7 = {
    ...(inputSchema as JSONSchema7),
    type: "object",
    properties: (inputSchema.properties ?? {}) as JSONSchema7["properties"],
    // 严格校验:不允许额外属性
    additionalProperties: false,
  }

  // 2. 构造 dynamicTool
  return dynamicTool({
    description: mcpTool.description ?? "",
    inputSchema: jsonSchema(schema),
    // 3. 执行器:调用 MCP 客户端的 callTool
    execute: async (args: unknown) => {
      return client.callTool(
        {
          name: mcpTool.name,
          arguments: (args || {}) as Record<string, unknown>,
        },
        CallToolResultSchema,
        {
          resetTimeoutOnProgress: true,  // 有进度更新时自动重置超时
          timeout,
        },
      )
    },
  })
}

关键设计决策:

  • 参数 schema 透传:MCP 工具的 inputSchema 直接作为 AI SDK 工具的参数定义,无需手动映射
  • additionalProperties: false:独占严格校验,LLM 生成的参数必须完全匹配 schema,防止意外字段
  • resetTimeoutOnProgress:长时间运行的工具(如大规模搜索)在收到进度更新时自动重置超时,避免被误杀
  • 命名规则sanitizedClientName + "_" + sanitizedToolName 确保不同 MCP 服务器的工具名不会冲突

tools/list_changed 通知处理

MCP 协议支持服务器在运行时动态增删工具。watch() 函数监听 tools/list_changed 通知:

client.setNotificationHandler(ToolsListChangedNotificationSchema, async () => {
  // 1. 重新获取工具列表
  const newTools = await defs(client, key, mcp)
  // 2. 更新状态中的工具集
  // 3. 通过 Bus 广播 ToolsChanged 事件
  Bus.publish(Event.ToolsChanged, { client: key })
})

这使得 Agent 在不重启的情况下可以获取 MCP 服务器动态新增的工具。

OAuth 2.0 + PKCE 认证流程

远程 MCP 服务器可能需要 OAuth 认证。OpenCode 实现了完整的 OAuth 2.0 + PKCE 流程,涉及三个文件的协作:

完整流程

1. startAuth(key, mcp)
   ├─ 生成随机 state 参数(CSRF 保护)
   ├─ 创建 McpOAuthProvider 实例
   ├─ 尝试连接远程服务器
   └─ 捕获 authorizationUrl(从 Provider 重定向中提取)

2. authenticate(key, mcp)
   ├─ 调用 startAuth() 获取授权 URL
   ├─ 打开用户浏览器(Open.browser)
   └─ waitForCallback(oauthState, mcpName) ← 阻塞等待,5 分钟超时

3. 浏览器回调 → McpOAuthCallback 处理
   ├─ 用户授权后重定向到 http://localhost:19876/mcp/oauth/callback
   ├─ 验证 state 参数(CSRF 保护)
   └─ 解析授权码 code

4. finishAuth(key, mcp, code)
   ├─ transport.finishAuth(code) 交换 access token
   └─ createAndStore() 持久化凭据

5. McpAuth.set() → 写入 mcp-auth.json(权限 0o600)

McpOAuthCallback — 本地回调服务器

oauth-callback.ts 实现了一个轻量级本地 HTTP 服务器,用于接收 OAuth 回调:

// 常量定义
const OAUTH_CALLBACK_PORT = 19876           // 固定回调端口
const OAUTH_CALLBACK_PATH = "/mcp/oauth/callback"  // 回调路径
const CALLBACK_TIMEOUT_MS = 5 * 60 * 1000  // 5 分钟超时

核心数据结构:

// pending 授权映射:state → Promise resolve 函数
const pendingAuths: Map<string, (code: string) => void> = new Map()

// 反向索引:mcpName → oauthState(用于取消特定 MCP 的授权)
const mcpNameToState: Map<string, string> = new Map()

ensureRunning() 方法检查端口是否已被占用:

  • 如果端口空闲,启动 HTTP 服务器监听 19876 端口
  • 如果端口已被占用(说明已有另一个 OpenCode 实例在运行),复用现有服务器

handleRequest() 方法处理回调请求:

  1. 从 URL 中提取 statecode 参数
  2. 验证 state 是否存在于 pendingAuths 中(CSRF 保护)
  3. 调用对应的 resolve(code) 唤醒等待中的 authenticate() 调用
  4. 返回自定义 HTML 页面:成功页(绿色)或错误页(红色)

McpOAuthProvider — OAuth 客户端提供者

oauth-provider.ts 实现了 OAuthClientProvider 接口,这是 @modelcontextprotocol/sdk 要求的标准接口:

class McpOAuthProvider implements OAuthClientProvider {
  // 客户端元数据:告诉授权服务器自己是谁
  get clientMetadata(): OAuthClientMetadata {
    return {
      redirect_uris: [`http://localhost:${OAUTH_CALLBACK_PORT}${OAUTH_CALLBACK_PATH}`],
      client_name: "OpenCode",
      grant_types: ["authorization_code", "refresh_token"],
    }
  }

  // 客户端信息:按优先级查找
  // 1. 用户配置中的 client_id/client_secret
  // 2. McpAuth 中已存储的 clientInfo
  // 3. 无 → 触发动态客户端注册(DCR)
  get clientInformation(): OAuthClientInformation | undefined { ... }

  // 令牌:从 McpAuth 获取并转换格式
  get tokens(): OAuthTokens | undefined { ... }

  // 重定向到授权页面:捕获 URL 而非实际跳转
  async redirectToAuthorization(authorizationUrl: URL): Promise<void> {
    this.capturedAuthorizationUrl = authorizationUrl
  }

  // PKCE code verifier 存储
  async saveCodeVerifier(codeVerifier: string): Promise<void> { ... }
  get codeVerifier(): string | undefined { ... }

  // CSRF state 参数存储
  async saveState(state: string): Promise<void> { ... }
  get state(): string | undefined { ... }

  // 凭据失效时清除存储
  async invalidateCredentials(kind: "client" | "tokens" | "all"): Promise<void> { ... }
}

invalidateCredentials 处理三种失效场景:

  • "client":清除客户端信息(如 clientSecret 过期,检查 clientSecretExpiresAt
  • "tokens":清除令牌(如 access_token 过期且无 refresh_token)
  • "all":清除全部凭据,重新开始认证流程

McpAuth — 令牌持久化

auth.ts 使用 Effect Service 模式实现 OAuth 凭据的安全存储:

// 文件路径:Global.Path.data/mcp-auth.json
// 文件权限:0o600(仅所有者可读写)

// 核心辅助函数
async function writeJson(data: Entry[]): Promise<void> {
  await FileSystem.writeFile(
    Global.Path.data + "/mcp-auth.json",
    JSON.stringify(data, null, 2),
    { mode: 0o600 },  // 严格文件权限
  )
}

关键安全措施:

  • 文件权限 0o600:确保其他用户无法读取 OAuth 令牌
  • URL 匹配验证getForUrl(serverUrl) 在返回凭据时验证 serverUrl 匹配,防止凭据串用
  • 令牌过期检查isTokenExpired() 检查 expiresAt 字段,过期令牌不会被自动使用

泛型辅助函数 updateFieldclearField 提供类型安全的字段级更新:

// 更新特定 URL 条目的指定字段
async function updateField<T extends keyof Entry>(
  serverUrl: string,
  field: T,
  value: Entry[T],
): Promise<void> { ... }

// 清除特定 URL 条目的指定字段
async function clearField(
  serverUrl: string,
  field: keyof Entry,
): Promise<void> { ... }

进程清理

MCP 模块在 stdio 传输模式下有特殊的进程清理需求。本地 MCP 服务器通常作为子进程启动,而这些子进程可能自身又创建了子进程(例如 GitHub MCP 服务器可能启动 git 子进程)。OpenCode 实现了递归进程清理,确保不会遗留孤儿进程。

descendants — 递归获取子进程树

// 使用 BFS(广度优先搜索)递归获取进程的所有后代
const pids: number[] = []
const queue = [pid]  // 从 MCP 服务器进程的 PID 开始

while (queue.length > 0) {
  const current = queue.shift()!
  // 通过 pgrep -P 获取当前进程的直接子进程
  const handle = yield* spawner.spawn(
    ChildProcess.make("pgrep", ["-P", String(current)], ...),
  )
  const text = yield* Stream.mkString(Stream.decodeText(handle.stdout))
  for (const tok of text.split("\n")) {
    const cpid = parseInt(tok, 10)
    if (!isNaN(cpid)) {
      pids.push(cpid)
      queue.push(cpid)  // 继续搜索子进程的子进程
    }
  }
}

清理策略

关闭 MCP 客户端时的完整清理流程:

1. client.close() 关闭 MCP 客户端连接
2. 检查是否有 stdio 传输(有 pid 属性)
   ├─ 无 pid(远程传输)→ 清理完成
   └─ 有 pid(stdio 传输)→ 继续
3. descendants(pid) 获取所有后代进程
4. 从叶子进程开始(列表末尾),依次发送 SIGTERM
5. 等待 5 秒
6. 仍有存活进程?发送 SIGKILL 强制终止
7. 清理 pendingOAuthTransports Map

从叶子进程开始发送 SIGTERM 的原因是:如果先杀父进程,子进程可能被 init 进程接管(reparent),变成孤儿进程。先杀叶子进程可以确保进程树从底部向上完整清理。

Instance dispose 时的自动清理

通过 Effect.addFinalizer 注册实例销毁时的清理逻辑:

Effect.addFinalizer(() =>
  Effect.gen(function* () {
    // 遍历所有客户端,执行上述清理流程
    for (const [key, client] of Object.entries(state.clients)) {
      // ... close + descendants + kill ...
    }
    // 清理 pendingOAuthTransports
    pendingOAuthTransports.clear()
  }),
)

这确保无论实例是正常关闭还是异常退出,子进程都会被正确清理。

调用链示例

链路 1:配置加载 → MCP 启动 → 工具注册

1. Instance.init() 触发 → MCP.start() 被调用


2. 读取 Config.mcp 获取服务器列表
   例如: { "github": { type: "stdio", command: "npx", args: ["-y", "@modelcontextprotocol/server-github"] } }


3. Effect.forEach({ concurrency: "unbounded" }) 并行初始化
   对每个配置调用 create(key, mcp):
   ├─ 检查 mcp.disabled → disabled 状态
   ├─ 选择传输层:
   │  ├─ stdio → StdioClientTransport → 启动子进程
   │  └─ url → StreamableHTTPClientTransport(回退 SSE)
   ├─ Effect.acquireUseRelease 建立连接
   │  ├─ acquire: client.connect(transport)
   │  ├─ use: client.listTools() → convertMcpTool() 转换
   │  └─ release: 失败时关闭 transport
   └─ 更新 status[key] 状态


4. defs(client, key, mcp) 工具发现
   ├─ client.listTools() 获取工具列表(带超时保护)
   └─ convertMcpTool() 将每个工具转为 dynamicTool
      例如: { name: "search_repositories" } → dynamicTool "github_search_repositories"


5. Bus.publish(ToolsChanged) 通知 Agent 工具列表更新


6. Agent 下次构建 prompt 时,工具描述中包含 MCP 工具

链路 2:Agent 调用 MCP 工具

1. LLM 返回 tool_use,工具名为 "github_search_repositories"


2. Agent 工具路由匹配到该 MCP 工具
   → dynamicTool.execute({ query: "opencode" })


3. convertMcpTool 内部:
   client.callTool({
     name: "search_repositories",          // 原始 MCP 工具名
     arguments: { query: "opencode" },     // LLM 生成的参数
   }, CallToolResultSchema, {
     resetTimeoutOnProgress: true,         // 进度更新时重置超时
     timeout: 30000,                       // 默认 30 秒超时
   })


4. MCP SDK 通过传输层将请求发送到 MCP 服务器
   ├─ stdio: 写入子进程的标准输入
   └─ HTTP: POST 请求到远程服务器


5. MCP 服务器处理请求,返回结果


6. 结果通过 Agent 工具返回值传递给 LLM

链路 3:OAuth 认证完整流程

1. 用户启动一个需要认证的远程 MCP 服务器
   MCP.create("remote-server", { url: "https://..." })
   ├─ 尝试 StreamableHTTPClientTransport 连接
   └─ 服务器返回 401 Unauthorized


2. 状态更新为 needs_auth,附带 authorizationUrl


3. MCP.authenticate("remote-server", mcp)
   ├─ startAuth():
   │  ├─ 生成随机 state(CSRF 保护)
   │  ├─ 创建 McpOAuthProvider(包含 PKCE code verifier)
   │  ├─ 尝试连接 → 捕获 authorizationUrl
   │  └─ 返回 { authorizationUrl, oauthState }

   ├─ Open.browser(authorizationUrl) → 打开用户浏览器

   └─ McpOAuthCallback.waitForCallback(oauthState, "remote-server")
      ├─ ensureRunning() → 确保端口 19876 的 HTTP 服务器已启动
      ├─ 将 Promise 存入 pendingAuths[state]
      └─ 等待 resolve,最长 5 分钟


4. 用户在浏览器中登录并授权
   → 重定向到 http://localhost:19876/mcp/oauth/callback?state=xxx&code=yyy


5. McpOAuthCallback.handleRequest()
   ├─ 验证 state 参数匹配(CSRF 保护)
   ├─ 解析 code(授权码)
   ├─ resolve(code) → 唤醒等待中的 authenticate()
   └─ 返回成功 HTML 页面


6. finishAuth("remote-server", mcp, code)
   ├─ transport.finishAuth(code) → 用授权码交换 access_token
   ├─ McpAuth.set() → 持久化到 mcp-auth.json(权限 0o600)
   └─ createAndStore() → 用新凭据重新连接


7. 状态更新为 connected,工具发现开始

链路 4:服务器关闭与资源清理

1. Instance.shutdown() → MCP.stop() 被调用


2. 遍历所有客户端
   ├─ client.close() 关闭连接
   └─ 对 stdio 传输执行进程清理:

      ├─ descendants(pid) 递归获取进程树
      │  例如: pid=1000 → [1001, 1002, 1003]

      ├─ 从叶子进程开始 SIGTERM(列表末尾开始)
      │  kill(1003, SIGTERM) → kill(1002, SIGTERM) → kill(1001, SIGTERM)

      ├─ 等待 5 秒

      ├─ 仍有存活?→ SIGKILL 强制终止

      └─ 清理完成


3. 清理 pendingOAuthTransports Map
   终止所有进行中的 OAuth 流程

设计取舍

决策理由
Effect Service 模式提供类型安全的依赖注入和资源生命周期管理。acquireUseRelease 确保传输连接在任何情况下都能正确关闭,addFinalizer 确保子进程被递归清理
MCP SDK 传输层委托传输协议的复杂细节(stdio 管道管理、HTTP SSE 解析、协议版本协商)由 @modelcontextprotocol/sdk 处理,OpenCode 专注于连接编排和工具转换
additionalProperties: false独占严格校验。MCP 工具的参数 schema 可能不完整,但 AI SDK 要求 LLM 生成的参数严格匹配。宁可让 LLM 重试,也不要将意外字段传给 MCP 服务器
参数 schema 直接透传不做 MCP schema → AI SDK schema 的映射转换。MCP 的 inputSchema 本身就是 JSON Schema,与 AI SDK 的 jsonSchema() 兼容。减少转换层也减少 bug
StreamableHTTP → SSE 自动回退MCP 协议正在从 SSE 迁移到 StreamableHTTP,但许多服务器仍只支持 SSE。自动回降级确保两种服务器都能连接
固定回调端口 19876OAuth 回调需要可预测的端口,以便在 redirect_uri 中预先注册。ensureRunning() 处理端口冲突,多个 OpenCode 实例共享同一回调服务器
递归进程清理(BFS + 叶子优先)stdio MCP 服务器可能启动子进程链。先杀父进程会导致子进程被 init 接管变成孤儿。BFS 遍历 + 从叶子开始 SIGTERM 确保完整的进程树清理
5 分钟 OAuth 超时OAuth 需要用户在浏览器中手动操作。5 分钟给出足够时间完成登录授权,同时防止等待永远挂起
文件权限 0o600OAuth 令牌等价于密码。0o600(仅所有者可读写)是多用户系统上的基本安全措施
concurrency: "unbounded" 并行初始化MCP 服务器之间完全独立,一个慢不会影响其他。不限制并发数可以让所有服务器尽快就绪

与其他模块的关系

  • Agent:Agent 通过统一的工具接口调用 MCP 工具,与内置工具无差别。convertMcpTool() 将 MCP 工具包装为 AI SDK dynamicTool,工具描述自动注入 Agent 上下文。Agent 的 resolveTools() 同时注册内置工具和 MCP 工具
  • ConfigConfig.mcp 定义 MCP 服务器列表,每项包含 type(stdio/url)、commandargsenvurldisabled 等字段。配置变更后 MCP 模块需要重新 start()
  • Instance:MCP 状态通过 Instance.state() 管理,跟随项目实例的生命周期。start()/stop() 在实例初始化/关闭时自动调用。Effect.addFinalizer 确保实例销毁时子进程被清理
  • BusToolsChanged BusEvent(BusEvent.define("mcp.tools.changed"))通知工具列表变更,Agent 和 UI 层订阅此事件更新。BrowserOpenFailed 事件处理 OAuth 浏览器启动失败的降级
  • GlobalGlobal.Path.data/mcp-auth.json 存储 OAuth 令牌。文件权限 0o600 保证安全性。路径由 Global 模块统一管理
  • Permission:MCP 工具调用同样受 Permission 系统管控。用户可审批或拒绝特定 MCP 工具的执行,与内置工具使用同一套权限规则
  • Provider:MCP 工具的执行结果通过 dynamicTool 返回给 Agent,与 Provider 模块的 LLM 调用流程无缝衔接