MCP

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

作者 David更新于 第 27 篇(共 33 篇)

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):

选项环境变量缺省
enabledMAVIS_MCP_TOOL_SEARCH_ENABLEDtrue
modelWhitelistMAVIS_MCP_TOOL_SEARCH_WHITELIST,逗号分隔空
thresholdPctMAVIS_MCP_TOOL_SEARCH_THRESHOLD_PCT0.15
minDeferCountMAVIS_MCP_TOOL_SEARCH_MIN_DEFER_COUNT1
topKDefaultMAVIS_MCP_TOOL_SEARCH_TOP_K_DEFAULT5
topKMaxMAVIS_MCP_TOOL_SEARCH_TOP_K_MAX20
systemHintMAVIS_MCP_TOOL_SEARCH_SYSTEM_HINTtrue
maxSchemaTextLenMAVIS_MCP_TOOL_SEARCH_MAX_SCHEMA_TEXT_LEN4096

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 账号的登录与令牌,以及托管连接器等云端工具。

本页目录