# MCP

> 用户级 mcp.json、项目 .mcp.json 与 ACP 会话三层配置，三种传输与连接池，mcp__ 命名名册，按上下文占比决定的延迟披露，以及插件 MCP、权限与 OAuth 的现状。

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

MiniMax Code 的 MCP 分三层实现。`packages/agent-modules/mcp`（包名 `@mavis/mcp`）是与运行时无关的底座，包描述写着它只用 MCP SDK 与本机的命名存储、从不引用运行时包（`packages/agent-modules/mcp/package.json:4`）：连接池、三种传输、工具命名都在这里。`packages/local-runtime-v2/src/service/mcp` 的 `LocalMcpService` 管配置从哪来、每个回合发现哪些工具。`packages/agent-tools/src/mcp-disclosure` 决定把 MCP 工具全部交给模型，还是藏到 `tool_search` 后面按需检索。[插件](https://daiw.org/manual/minimax-code/plugins)自带的 MCP 服务器另有一套运行时。

## 怎么用

`mcode` 没有 `mcp` 子命令，也没有添加服务器的交互流程，配置靠手写 JSON：

| 层级 | 文件 | 生效范围 |
| --- | --- | --- |
| 用户 | `<dataDir>/mcp.json`，数据目录默认 `~/.minimax-code` | 所有会话 |
| 项目 | 工作区根目录的 `.mcp.json` | 该工作区的会话，只读 |
| 会话 | ACP 客户端在 session/new 等请求里带来的 `mcpServers` | 仅该会话，只在内存里 |

项目文件的写法如下，`${VAR}` 与 `${VAR:-默认值}` 只在项目文件里展开：

```json
{
  "mcpServers": {
    "docs": {
      "type": "http",
      "url": "https://docs.example.com/mcp",
      "headers": { "Authorization": "Bearer ${DOCS_TOKEN}" }
    },
    "local-tools": {
      "command": "npx",
      "args": ["-y", "my-mcp-server"],
      "env": { "API_KEY": "${API_KEY:-dev}" },
      "timeout": 60000
    }
  }
}
```

`type` 可取 `stdio`、`http`、`streamable-http`、`sse`；不写时有 `command` 就当 stdio，有 `url` 就当 HTTP（`packages/local-runtime-v2/src/service/mcp/runtime/config.ts:16`）。`timeout` 以毫秒计，`enabled: false` 可以关掉一台。

查看状态用 `/mcp [filter]`：它列出用户、项目、会话三层的服务器及其可用性，项目文件读不了时先打印一条警告（`packages/tui/src/tui/controller/product/feature-flow.ts:1301`）。所谓 `/mcp reload` 只是把参数当作空过滤词重读一遍（`feature-flow.ts:1299`）。这条命令被标为 `search-only`，默认的命令列表里看不到，只在搜索命令时出现（`packages/tui/src/tui/commands/catalog.ts:737`）。另外，内置的 `mavis` 工具带有 `mcp list`、`mcp create`、`mcp delete` 等子命令（`packages/agent-tools/src/desktop/local-mavis-commands.ts:45`），模型可以替用户改写用户级 `mcp.json`；它是否出现在工具表里取决于 Agent 能力上限的 `features.mavis`。

## 三层配置怎样合并

每次需要配置时，`LocalMcpService` 现读三层，再按“会话 > 项目 > 用户”的顺序覆盖（`packages/local-runtime-v2/src/service/mcp/runtime/local-mcp.service.ts:1019`）：

```ts
  private async readEffectiveFile(context?: LocalMcpRuntimeContext): Promise<LocalMcpFile> {
    const base = this.withBuiltinServers(await this.readFile(), context);
    const project = await this.enqueueMutation(() => this.projectMcp.resolve(context));
    const session = context?.sessionId ? this.sessionServers.get(context.sessionId) : undefined;
    return { mcpServers: { ...base.mcpServers, ...project, ...session } };
  }
```

三层的处理各有讲究：

- **用户文件**读的是 `<dataDir>/mcp.json`。服务器名就是身份，`normalizeServerName` 原样返回（`runtime/config.ts:45`）；读取时顺手清掉几种已退役的内置条目。
- **项目文件**严格只读：`realpath` 后必须仍在工作区内、不超过 1 MiB，整个文件读不了就整份作废；单个条目出错（比如引用的环境变量不存在）只停用那一台（`packages/local-runtime-v2/src/service/mcp/project-config.ts:21`）。`matrix`、`nd`、`cu` 以及 `__proto__` 之类的名字保留不用（`project-config.ts:161`）。项目里的 stdio 服务器以工作区根为工作目录，远程服务器的请求头不会跟着跨域重定向走（`packages/local-runtime-v2/src/service/mcp/project-mcp.service.ts:93`）。
- **会话层**来自 ACP 客户端，类注释写明它与仓库配置无关（`packages/local-runtime-v2/src/service/mcp/runtime/session-servers.ts:7`），会话关闭时断开并丢弃，详见 [Headless 与 ACP](https://daiw.org/manual/minimax-code/headless-and-acp)。

`LocalMcpService` 里还保留着一台内置的 Matrix MCP 服务器，但公开源码组装服务时把 `builtinMatrix` 写死为 `false`（`packages/local-runtime-v2/src/compat/v1/runtime.ts:580`）。TUI 里的 `web_search` 是原生工具，并不经过这台服务器（`packages/local-runtime/src/api/host-turn-tools.ts:82`）；多媒体等云端工具的来路见[账号、OAuth 与云端工具](https://daiw.org/manual/minimax-code/accounts-and-tools)。

## 连接：连接池与三种传输

连接池只在 `enableLiveMcp` 为真时创建（`packages/local-runtime-v2/src/service/mcp/initialize.ts:43`），TUI 总是打开它（`packages/tui/src/runtime/embedded-host.ts:87`）。`McpConnectionPool` 的行为：

- **懒连接**：第一次列工具或调用时才起进程或建会话，空闲 15 分钟回收（`packages/agent-modules/mcp/src/runtime/connection-pool.ts:47`）。
- **限流与超时**：每个连接一把信号量，默认并发 5；单次调用默认超时 120 秒，可被服务器的 `timeout` 覆盖（`packages/agent-modules/mcp/src/runtime/types.ts:41`）。
- **stdio**：用 SDK 的 `StdioClientTransport`，工作目录缺省是用户主目录而不是继承的当前目录，环境变量按“进程环境、配置、宿主注入”的顺序叠加（`packages/agent-modules/mcp/src/runtime/transport/stdio.ts:7`）。
- **HTTP 与 SSE**：`http`、`streamable-http` 走 `StreamableHTTPClientTransport`，`sse` 走 `SSEClientTransport`，按配置直接选，没有失败后改试另一种的回退（`packages/agent-modules/mcp/src/runtime/transport/http.ts:34`）。
- **客户端能力**只声明 `roots`，回答项目根目录，没有就回答主目录（`connection-pool.ts:578`）。

每个回合组装工具时，远程服务器的发现预算是 2 秒，stdio 考虑到 `npx`、`uvx` 冷启动给 15 秒，再与服务器自己的超时取小（`local-mcp.service.ts:750`）：

```ts
    const turnDiscoveryTimeoutMs = this.options.turnDiscoveryTimeoutMs ?? 2_000;
    const discoveryTimeoutMs =
      transport.type === 'stdio'
        ? (this.options.turnStdioDiscoveryTimeoutMs ??
          this.options.turnDiscoveryTimeoutMs ??
          15_000)
        : turnDiscoveryTimeoutMs;
    const timeoutMs = Math.min(configuredTimeout(config), discoveryTimeoutMs);
```

超时或失败的服务器这一回合没有工具，状态记为错误，`/mcp` 里能看到；下一回合重新尝试。

## 工具命名：`mcp__` 与持久名册

模型看到的工具名形如 `mcp__<server>__<tool>`：两段都把 `[a-zA-Z0-9_-]` 以外的字符压成下划线，总长不超过 80，服务器段不超过 48（`packages/agent-modules/mcp/src/runtime/tool-name.ts:2`）。截断后可能撞名，所以真正分配名字的是 `McpNameRegistry`，状态落在 `<dataDir>/mcp-runtime-names.json`，读写都在文件锁里完成（`local-mcp.service.ts:86`）。它的类注释点出两条原则（`packages/agent-modules/mcp/src/runtime/name-registry.ts:51`）：

> Host-scoped, two-level allocation. Never derives identity from a public name.
> Removed identities retain their assignments so a new source cannot inherit a name.

撞名时后来者加 `_2`、`_3` 后缀；全新名册上的先后次序由来源决定（`name-registry.ts:246`）：

```ts
// Preserve the previous configured > official > local ownership on a fresh registry.
function sourcePriority(key: string): number {
  try {
    const identity: unknown = JSON.parse(key);
    if (Array.isArray(identity)) {
      if (identity[0] === 'configured') return 0;
      if (identity[0] === 'plugin' && identity[1] === 'OFFICIAL') return 1;
    }
  } catch {
    /* Tool keys are original names, not structured server identities. */
  }
  return 2;
}
```

用户配置的服务器优先，其次是官方插件，最后是本地插件。删掉的服务器不释放名字，新来源也就接不走旧名字，而权限规则正是按名字写的。对照 [OpenCode 的“服务器名_工具名”](https://daiw.org/manual/opencode/mcp)：那边没有前缀，也不处理长度。

## 延迟披露：`tool_search` 与 `mcp_invoke`

MCP 工具多了，光是工具定义就能吃掉可观的上下文。`planMcpDisclosure` 在每个回合决定要不要把一部分工具藏起来（`packages/agent-tools/src/mcp-disclosure/plan.ts:31`）。只有来源为 `configured` 的工具会被延迟，内置来源永远内联（`packages/local-runtime-v2/src/service/mcp/tools/runtime-tools.ts:11`）。判定的核心几行（`plan.ts:49`）：

```ts
  const candidates = entries.filter((e) => e.source === 'configured');
  const estimate = options.estimateTokens ?? defaultEstimate;
  const estTokens = candidates.reduce((s, e) => s + estimate(e.tool.def), 0);
  const thresholdTokens = options.thresholdPct * model.contextWindow;

  if (candidates.length < options.minDeferCount || estTokens <= thresholdTokens) return inlineAll();
```

token 按定义 JSON 的长度除以 4 估算。超过阈值时，这些工具移出工具表，换成两个工具：`tool_search` 按关键词或正则检索，返回命中工具的完整定义；`mcp_invoke` 以 `tool_name` 与 `arguments` 执行其中之一。同时用户消息前会加一段 `system-reminder`，告诉模型还有未列出的集成工具可搜（`packages/agent-tools/src/mcp-disclosure/hint.ts:1`）。检索用 BM25，中文按单字加相邻双字切词（`packages/agent-tools/src/mcp-disclosure/tokenize.ts:13`）。

参数由 `resolveLocalMcpDisclosureOptions` 解析，环境变量优先于 `config.yaml` 的 `mcpToolSearch` 段，再退到缺省值（`packages/local-runtime-v2/src/service/turn-system/agent-host/assembly/local-turn-tool-catalog.ts:480`）：

| 选项 | 环境变量 | 缺省 |
| --- | --- | --- |
| `enabled` | `MAVIS_MCP_TOOL_SEARCH_ENABLED` | `true` |
| `modelWhitelist` | `MAVIS_MCP_TOOL_SEARCH_WHITELIST`，逗号分隔 | 空 |
| `thresholdPct` | `MAVIS_MCP_TOOL_SEARCH_THRESHOLD_PCT` | 0.15 |
| `minDeferCount` | `MAVIS_MCP_TOOL_SEARCH_MIN_DEFER_COUNT` | 1 |
| `topKDefault` | `MAVIS_MCP_TOOL_SEARCH_TOP_K_DEFAULT` | 5 |
| `topKMax` | `MAVIS_MCP_TOOL_SEARCH_TOP_K_MAX` | 20 |
| `systemHint` | `MAVIS_MCP_TOOL_SEARCH_SYSTEM_HINT` | `true` |
| `maxSchemaTextLen` | `MAVIS_MCP_TOOL_SEARCH_MAX_SCHEMA_TEXT_LEN` | 4096 |

<Callout type="warn">
  `enabled` 缺省为真，但白名单缺省为空，而空白名单不匹配任何模型，所以开箱时所有 MCP 工具都内联。要启用延迟披露，得把模型写进白名单：条目形如 `provider/model`，支持 `*` 通配（`plan.ts:10`）。`packages/config/src/config.ts:892` 的注释也说明了这一点，但它指向的 `disclosure-options.ts` 在公开源码里并不存在，实际逻辑就是上面的解析函数。
</Callout>

## 插件的 MCP 服务器

插件带来的服务器由 `PluginMcpRuntime` 单独管理，注释特意强调它的连接池与用户 `mcp.json` 的那一个分开（`packages/local-runtime-v2/src/service/plugin-system/mcp/runtime.ts:94`）。它并发 4 路列工具（`mcp/runtime.ts:23`）；一台服务器返回超过 128 个工具，或者累计超过每份快照 256 个的上限，就整台拒收（`mcp/runtime.ts:396`）；失败的服务器按 10、30、60、120、300 秒退避重试发现（`packages/local-runtime-v2/src/service/plugin-system/publication.ts:603`）。插件工具总是内联，不参与上面的延迟披露；只有云端连接器的 App 工具可以被强制放进 `tool_search`（`packages/local-runtime-v2/src/service/turn-system/agent-host/assembly/local-turn-plugin-capabilities.ts:228`）。

插件清单里的 MCP 配置比用户配置严：MiniMax 格式必须显式写 `type`，而且不接受笼统的 `http`（`packages/local-runtime-v2/src/service/plugin-system/mcp/config.ts:154`）；stdio 命令只能是 `PATH` 上的可执行文件名，参数里不许出现绝对路径（`mcp/config.ts:206`）。Agent Plugins 格式的远程地址必须是 HTTPS，只有回环地址可以用 HTTP（`packages/local-runtime-v2/src/service/plugin-system/plugin/package/agent-plugin-reader.ts:407`）。

## 权限与 OAuth

**权限**：MCP 工具的权限键就是它的 `mcp__` 名字。不带内容的服务器级规则 `mcp__<server>` 覆盖该服务器的全部工具；匹配时要求名字恰好三段，多出的分隔符让匹配失败，而不是退化成前缀匹配（`packages/agent-modules/permission/src/mcp-runtime-name.ts:10`）。用户配置的工具每次都要过权限闸门，只有 `builtin`、`builtin-matrix` 来源在没开 `enforceBuiltinTools` 时可以跳过（`packages/local-runtime-v2/src/service/turn-system/agent-host/runner/policy/local-turn-permission-gate.ts:573`）。规则怎么写见[权限模式与规则](https://daiw.org/manual/minimax-code/permission)。

**OAuth**：代码里没有 MCP 的 OAuth 流程。`startAuth` 只返回一个 `authUrl: undefined` 的占位结果（`local-mcp.service.ts:570`），授权状态由 `inferAuthStatus` 看配置里的 `auth` 字段静态推断（`runtime/config.ts:91`）；底座的类型文件开头就说明，移植时砍掉了旧 daemon 的 OAuth2 流程（`runtime/types.ts:4`）；插件清单里出现 `oauth`、`clientSecret` 等字段则直接判为不支持（`mcp/config.ts:142`）。需要鉴权的服务器只能靠 `headers` 或 `env` 里的静态凭据，这与 [OpenCode 带 CSRF 校验的 OAuth 流程](https://daiw.org/manual/opencode/mcp)差得很远。

另外，`packages/local-runtime-v2/src/service/host-connector-system` 与 MCP 无关：它是给 MiniApp 宿主进程调用云端连接器的网关，接口注释写明普通回合不走这条路（`packages/local-runtime-v2/src/service/host-connector-system/contracts.ts:105`）。CLI 不运行 MiniApp，也就用不上它。

下一篇：[账号、OAuth 与云端工具](https://daiw.org/manual/minimax-code/accounts-and-tools)——MiniMax 账号的登录与令牌，以及托管连接器等云端工具。
