MCP 集成
三种传输、工具命名的 64 字符约束与哈希截断、按 Scope 分层的连接管理、OAuth 授权工具与三层配置文件。
MCP(Model Context Protocol)让 agent 接入外部工具服务器。v0.29 时代它集中在 packages/agent-core/src/mcp/;v1 引擎删除后,现役实现在 packages/agent-core-v2/src/,并按 DI × Scope 的生命周期分层:
| 层 | 目录 | 负责什么 |
|---|---|---|
| 公共核心 | mcpCore/ | 客户端、连接管理器、OAuth、工具命名 |
| App | app/mcpConfig/、app/mcpManagement/ | 全局 mcp.json 读写、OAuth 凭据、管理接口 |
| Workspace | workspace/workspaceMcpConfig/、workspace/workspaceMcp/ | 合并配置、持有连接 |
| Session | session/mcp/ | 会话级临时服务器叠加层 |
| Agent | agent/mcp/ | 注册工具、授权工具、结果转换 |
怎么用:三层配置文件
app/mcpConfig/configLoader.ts:27:
return {
user: join(resolveKimiHome(input.homeDir), 'mcp.json'),
projectRoot: join(projectRoot, '.mcp.json'),
project: join(input.cwd, '.kimi-code', 'mcp.json'),
};按用户级(~/.kimi-code/mcp.json)→ 项目根(git 根目录的 .mcp.json,与其他 agent 工具通用)→ 项目本地(.kimi-code/mcp.json)的顺序合并,同名以后者为准。两层项目配置只在工作区被信任后才读(includeProject: this.trust.isTrusted())——陌生仓库里的 .mcp.json 不会一打开就起进程。插件带来的服务器垫在最底下,文件里的同名配置会覆盖它们;ACP 编辑器在 session/new 里带的 mcpServers 则成为只属于该会话的临时服务器。
配置文件有监听,改动经 200ms 防抖后按差异 connect 或 markRemoved,不必重启会话。终端里 /mcp 查看服务器状态;/mcp-config 是一个内置 skill,用对话方式增删配置、完成登录。
三种客户端传输
mcpCore/config-schema.ts:59 用判别联合描述一台服务器,并能从字段推断传输:
export const McpServerConfigSchema = z.preprocess((raw) => {
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return raw;
const obj = raw as Record<string, unknown>;
if ('transport' in obj) return obj;
if (typeof obj['command'] === 'string') return { ...obj, transport: 'stdio' };
if (typeof obj['url'] === 'string') return { ...obj, transport: 'http' };
return obj;
}, McpServerConfigDiscriminatedSchema);写了 command 就是 stdio(本地子进程),写了 url 默认按 http,也可显式写 sse。client-remote.ts 并不是第四种传输,只是两种远程传输共用的请求头与 bearerTokenEnvVar 处理。每台服务器还能配 enabled、deferred、startupTimeoutMs、toolTimeoutMs、enabledTools、disabledTools;全局默认超时在配置的 mcp 段(也可用 KIMI_MCP_STARTUP_TIMEOUT_MS 等环境变量),启动超时缺省 30 秒。
工具命名:一个被 64 字符逼出来的设计
mcpCore/tool-naming.ts:1(agent-core-v2 是禁注释区,v1 那几段解释性注释已删):
const MCP_NAME_PREFIX = 'mcp__';
const MCP_NAME_SEPARATOR = '__';
const MAX_QUALIFIED_LENGTH = 64;
export function sanitizeMcpNamePart(part: string): string {
return part.replaceAll(/[^a-zA-Z0-9_-]/g, '_').replaceAll(/_+/g, '_');
}
export function qualifyMcpToolName(serverName: string, toolName: string): string {
const full = `${MCP_NAME_PREFIX}${sanitizeMcpNamePart(serverName)}${MCP_NAME_SEPARATOR}${sanitizeMcpNamePart(toolName)}`;
if (full.length <= MAX_QUALIFIED_LENGTH) return full;
const hash = stableHash8(full);
const head = full.slice(0, MAX_QUALIFIED_LENGTH - hash.length - 1);
return `${head}_${hash}`;
}限定名的形状是 mcp__<server>__<tool>,三个问题逐一解决:
- 分隔符歧义:先把非法字符换成
_,再把连续下划线折叠成一个。折叠保证净化后的名字里不可能出现__,按前缀后的第一个__切分就不会切错。 - 长度上限:provider 普遍限制工具名 64 字符,扣掉
mcp__前缀与分隔符,留给两段名字的只有 57。 - 超长截断:保留前 55 个字符,拼上
_与 8 位十六进制的 FNV-1a 哈希。确定性保证同一个工具每次得到同一个名字,会话恢复后对得上;哈希让两个长名字几乎不会撞到一起。
冲突与发现记录
即使限定了命名,仍可能撞名。agent/mcp/mcpDiscoveryOps.ts:8:
export interface McpToolCollision {
readonly qualified: string;
readonly toolName: string;
readonly collidesWith:
| { readonly kind: 'same_server'; readonly toolName: string }
| { readonly kind: 'other_server'; readonly serverName: string };
}AgentMcpService 注册时先到先得:落败的工具被丢弃,并发出一条 MCP_TOOL_NAME_COLLISION 错误事件告知用户,而不是静默覆盖。每台服务器发现的工具清单还会作为持久事件 mcp.tools_discovered 记入会话,内容相同的按 sha256 去重。
连接管理
mcpCore/connection-manager.ts 的 McpConnectionManager 管六种状态:pending、connected、failed、disabled、needs-auth、removed。它由 Workspace 层的 WorkspaceMcpService 持有,同一工作区的会话共享一套连接;会话带了临时服务器时,再起一个会话专属的管理器,用 MergedMcpConnectionView 叠在共享连接之上。
每个 agent 的 AgentMcpService 订阅状态变化:连上就把工具注册进工具表并发出 tool.list.updated,被禁用就注销;服务器被移除后,残留工具的调用直接返回错误,提示模型别再调用。工具调用遇到传输层故障时,先用 ping 探活(5 秒超时):还活着就原样重试,真断了再重连后重试。
初始加载耗时由 initialLoadDurationMs() 记录,SDK 以 getMcpStartupMetrics() 暴露给 TUI——MCP 服务器起得慢会拖累启动,需要可观测。
OAuth:一个给模型用的授权工具
远程服务器报 401 一类错误时,状态变为 needs-auth,agent/mcp/tools/auth.ts 的 createMcpAuthTool() 随即注册一个合成工具 mcp__<server>__authenticate。模型调用它时:
- 在
127.0.0.1的随机端口起回调监听,打印授权 URL,并要求模型原样转给用户; - 最多阻塞 15 分钟等浏览器回调;
- 成功后重连服务器,真实工具替换掉这个合成工具。
凭据存进文档存储的 credentials/mcp 作用域,临近过期会提前刷新。这与 README 里的「AI-native MCP configuration」是一套:配置和授权都在对话内完成。
输出转换
agent/mcp/output.ts 的 mcpResultToExecutableOutput() 把 MCP 的返回转成引擎的 ExecutableToolResult:文本、图片、音频、嵌入资源、资源链接分别映射到对应的 ContentPart;单个二进制部分上限 10 MB,模型收不下的类型换成一段 [MCP content dropped: …] 说明;isError 原样透传。
按需加载与权限
服务器配了 deferred: true、模型支持动态加载且开启实验开关 tool-select 时,它的工具不进顶层工具表,由模型经 select_tools 按需加载——MCP 工具一多,就不必全部塞进每次请求。
MCP 工具同样走权限系统:每个工具的 approvalRule 就是它的限定名,规则里的工具名部分用 picomatch 做 glob 匹配,所以可以写:
mcp__github__* 某个服务器的全部工具
mcp__* 所有 MCP 工具内置 profile 的工具白名单同样用 mcp__* 一次放行全部 MCP 工具。稳定前缀让用户能对第三方工具做批量策略——这在安全上很重要,因为 MCP 服务器是外部代码,默认应比内置工具更谨慎。
下一篇:Skills——五路来源、SKILL.md 解析与调用方式。