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 SDKdynamicTool,Agent 无需感知底层协议差异- OAuth 2.0 + PKCE 完整实现支持需要认证的远程 MCP 服务器
- 递归进程清理确保 stdio 模式下不会遗留孤儿进程
关键文件
| 文件路径 | 行数 | 职责 |
|---|---|---|
src/mcp/index.ts | ~922 | 模块主入口:MCP 命名空间,Effect Service 模式,客户端创建、工具发现、OAuth 流程、进程清理 |
src/mcp/auth.ts | ~174 | McpAuth 命名空间,OAuth 令牌的 Zod schema 定义与持久化存储(mcp-auth.json,权限 0o600) |
src/mcp/oauth-callback.ts | ~217 | McpOAuthCallback 命名空间,本地 HTTP 回调服务器(端口 19876),处理 OAuth 重定向并提取授权码 |
src/mcp/oauth-provider.ts | ~186 | McpOAuthProvider 类,实现 OAuthClientProvider 接口,管理 OAuth 客户端元数据、令牌和 PKCE 流程 |
注意:MCP 模块没有独立的 client.ts 或 transport.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,
})
}
三种传输方式的技术特征:
| 传输类型 | 类名 | 通信方式 | 适用场景 |
|---|---|---|---|
| stdio | StdioClientTransport | 标准输入/输出管道 | 本地 MCP 服务器进程(最常用) |
| StreamableHTTP | StreamableHTTPClientTransport | HTTP POST + 流式响应 | 新版远程 MCP 服务器 |
| SSE | SSEClientTransport | HTTP 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 服务器连接成功后的工具发现流程:
defs()函数调用client.listTools()获取服务器声明的工具列表- 设置超时保护,防止无响应服务器阻塞整个启动流程
convertMcpTool(mcpTool, client, timeout)将每个 MCP 工具转换为 AI SDK 的dynamicTool- 工具命名规则:
sanitizedClientName + "_" + sanitizedToolName(例如github_search_repositories) - 注册
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() 方法处理回调请求:
- 从 URL 中提取
state和code参数 - 验证
state是否存在于pendingAuths中(CSRF 保护) - 调用对应的
resolve(code)唤醒等待中的authenticate()调用 - 返回自定义 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字段,过期令牌不会被自动使用
泛型辅助函数 updateField 和 clearField 提供类型安全的字段级更新:
// 更新特定 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。自动回降级确保两种服务器都能连接 |
| 固定回调端口 19876 | OAuth 回调需要可预测的端口,以便在 redirect_uri 中预先注册。ensureRunning() 处理端口冲突,多个 OpenCode 实例共享同一回调服务器 |
| 递归进程清理(BFS + 叶子优先) | stdio MCP 服务器可能启动子进程链。先杀父进程会导致子进程被 init 接管变成孤儿。BFS 遍历 + 从叶子开始 SIGTERM 确保完整的进程树清理 |
| 5 分钟 OAuth 超时 | OAuth 需要用户在浏览器中手动操作。5 分钟给出足够时间完成登录授权,同时防止等待永远挂起 |
文件权限 0o600 | OAuth 令牌等价于密码。0o600(仅所有者可读写)是多用户系统上的基本安全措施 |
concurrency: "unbounded" 并行初始化 | MCP 服务器之间完全独立,一个慢不会影响其他。不限制并发数可以让所有服务器尽快就绪 |
与其他模块的关系
- Agent:Agent 通过统一的工具接口调用 MCP 工具,与内置工具无差别。
convertMcpTool()将 MCP 工具包装为 AI SDKdynamicTool,工具描述自动注入 Agent 上下文。Agent 的resolveTools()同时注册内置工具和 MCP 工具 - Config:
Config.mcp定义 MCP 服务器列表,每项包含type(stdio/url)、command、args、env、url、disabled等字段。配置变更后 MCP 模块需要重新start() - Instance:MCP 状态通过
Instance.state()管理,跟随项目实例的生命周期。start()/stop()在实例初始化/关闭时自动调用。Effect.addFinalizer确保实例销毁时子进程被清理 - Bus:
ToolsChangedBusEvent(BusEvent.define("mcp.tools.changed"))通知工具列表变更,Agent 和 UI 层订阅此事件更新。BrowserOpenFailed事件处理 OAuth 浏览器启动失败的降级 - Global:
Global.Path.data/mcp-auth.json存储 OAuth 令牌。文件权限0o600保证安全性。路径由 Global 模块统一管理 - Permission:MCP 工具调用同样受 Permission 系统管控。用户可审批或拒绝特定 MCP 工具的执行,与内置工具使用同一套权限规则
- Provider:MCP 工具的执行结果通过
dynamicTool返回给 Agent,与 Provider 模块的 LLM 调用流程无缝衔接