# MCP 集成

> 三种传输、工具命名的 64 字符约束与哈希截断、按 Scope 分层的连接管理、OAuth 授权工具与三层配置文件。

- 作者：David（道雾轩）
- 专栏：Kimi Code 源码解读（https://daiw.org/manual/kimi-code.md）
- 最后更新：2026-09-19
- 原文：https://daiw.org/manual/kimi-code/mcp
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

MCP（Model Context Protocol）让 agent 接入外部工具服务器。v0.29 时代它集中在 `packages/agent-core/src/mcp/`；v1 引擎删除后，现役实现在 `packages/agent-core-v2/src/`，并按 [DI × Scope](https://daiw.org/manual/kimi-code/agent-core-v2) 的生命周期分层：

| 层 | 目录 | 负责什么 |
| --- | --- | --- |
| 公共核心 | `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`：

```ts
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` 用判别联合描述一台服务器，并能从字段推断传输：

```ts
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 那几段解释性注释已删）：

```ts
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>`，三个问题逐一解决：

1. **分隔符歧义**：先把非法字符换成 `_`，再把连续下划线折叠成一个。折叠保证净化后的名字里**不可能出现 `__`**，按前缀后的第一个 `__` 切分就不会切错。
2. **长度上限**：provider 普遍限制工具名 64 字符，扣掉 `mcp__` 前缀与分隔符，留给两段名字的只有 57。
3. **超长截断**：保留前 55 个字符，拼上 `_` 与 8 位十六进制的 FNV-1a 哈希。**确定性**保证同一个工具每次得到同一个名字，会话恢复后对得上；哈希让两个长名字几乎不会撞到一起。

## 冲突与发现记录

即使限定了命名，仍可能撞名。`agent/mcp/mcpDiscoveryOps.ts:8`：

```ts
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`](https://daiw.org/manual/kimi-code/tool-contract)：文本、图片、音频、嵌入资源、资源链接分别映射到对应的 `ContentPart`；单个二进制部分上限 10 MB，模型收不下的类型换成一段 `[MCP content dropped: …]` 说明；`isError` 原样透传。

## 按需加载与权限

服务器配了 `deferred: true`、模型支持动态加载且开启实验开关 `tool-select` 时，它的工具不进顶层工具表，由模型经 `select_tools` 按需加载——MCP 工具一多，就不必全部塞进每次请求。

MCP 工具同样走[权限系统](https://daiw.org/manual/kimi-code/permission)：每个工具的 `approvalRule` 就是它的限定名，规则里的工具名部分用 `picomatch` 做 glob 匹配，所以可以写：

```text
mcp__github__*        某个服务器的全部工具
mcp__*                所有 MCP 工具
```

内置 profile 的工具白名单同样用 `mcp__*` 一次放行全部 MCP 工具。稳定前缀让用户能对第三方工具做批量策略——这在安全上很重要，因为 MCP 服务器是外部代码，默认应比内置工具更谨慎。

下一篇：[Skills](https://daiw.org/manual/kimi-code/skills)——五路来源、SKILL.md 解析与调用方式。
