MCP
用户级 mcp.json、项目 .mcp.json 与 ACP 会话三层配置,三种传输与连接池,mcp__ 命名名册,按上下文占比决定的延迟披露,以及插件 MCP、权限与 OAuth 的现状。
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 后面按需检索。插件自带的 MCP 服务器另有一套运行时。
怎么用
mcode 没有 mcp 子命令,也没有添加服务器的交互流程,配置靠手写 JSON:
| 层级 | 文件 | 生效范围 |
|---|---|---|
| 用户 | <dataDir>/mcp.json,数据目录默认 ~/.minimax-code | 所有会话 |
| 项目 | 工作区根目录的 .mcp.json | 该工作区的会话,只读 |
| 会话 | ACP 客户端在 session/new 等请求里带来的 mcpServers | 仅该会话,只在内存里 |
项目文件的写法如下,${VAR} 与 ${VAR:-默认值} 只在项目文件里展开:
{
"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):
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。
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 与云端工具。
连接:连接池与三种传输
连接池只在 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):
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):
// 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 的“服务器名_工具名”:那边没有前缀,也不处理长度。
延迟披露: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):
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 |
enabled 缺省为真,但白名单缺省为空,而空白名单不匹配任何模型,所以开箱时所有 MCP 工具都内联。要启用延迟披露,得把模型写进白名单:条目形如 provider/model,支持 * 通配(plan.ts:10)。packages/config/src/config.ts:892 的注释也说明了这一点,但它指向的 disclosure-options.ts 在公开源码里并不存在,实际逻辑就是上面的解析函数。
插件的 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)。规则怎么写见权限模式与规则。
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 流程差得很远。
另外,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 与云端工具——MiniMax 账号的登录与令牌,以及托管连接器等云端工具。