在 Claude Agent SDK 里接 MCP:和 CLI 里配 MCP 是两套写法

2026-08-18

一、先说这篇要解决的那个具体麻烦

你在命令行里已经把 MCP 服务器调通了:claude mcp add 加进去,/mcp 里查到的状态是 connected,Claude 也确实在调那几个工具。然后你要把这套流程搬进程序里,用 Claude Agent SDK 写一个跑批任务或者一个后端服务。代码跑起来,模型说它没有那些工具;或者第一轮就是没有,第二轮又莫名其妙有了。

这不是玄学。Claude Code 官方文档把 SDK 侧的 MCP 单独放了一页(code.claude.com/docs/en/agent-sdk/mcp),页首就有一条提示:这一页讲的是 Agent SDK 的 MCP 配置,如果你要给 CLI 加服务器让它在每个项目里都加载,那要看 code.claude.com/docs/en/mcp 的 MCP installation scopes。两页讲的是两套入口。 命令行那套 --scope local / project / user 的概念,在 SDK 这边并不是你唯一要关心的东西。

这篇只落一件事:SDK 侧 MCP 服务器的声明位置,和它们各自的连接生命周期。 因为绝大多数「工具没出现」的情况,答案要么在声明位置上,要么在连接时机上。

二、前置条件

先把能核到的说清楚,核不到的也照实说。

  • :TypeScript 侧是 @anthropic-ai/claude-agent-sdk,文档示例从它导入 query;Python 侧是 claude_agent_sdk,示例导入 queryClaudeAgentOptionsResultMessageSystemMessageAssistantMessage
  • 版本:这一页没有写明使用 MCP 所需的最低 SDK 版本,官方文档没有说明这一点。涉及版本相关行为时,以官方文档最新内容为准。
  • 权限:文档写得很直白——MCP 工具需要显式许可,否则「Claude 会看到工具可用,但没法调用它们」。所以配好服务器只是第一半,另一半是 allowedTools
  • stdio 类服务器的运行环境:文档的排查一节写明,用 npx 拉起的服务器要确认包存在且 Node.js 在你的 PATH 里。这条在 Windows 上尤其值得先验一遍:宿主进程是在 PowerShell、cmd 还是 Git Bash 里启动的,会影响它继承到的 PATH,而 PATH 里有没有 Node 正是文档点名要查的那一项。(后半句是通用运维经验,官方文档只写到「确认包存在且 Node.js 在 PATH 里」这一层,没有展开各 shell 的差异。)
  • 凭据:文档示例里的 shell 变量设置写的是 POSIX 形式,例如 export GITHUB_TOKEN=YOUR_GITHUB_PAT。这一页没有给 Windows 上 PowerShell 或 cmd 的等价写法,官方文档没有说明这一点;实际写代码时凭据是从进程环境读的(TypeScript 用 process.env.API_TOKEN,Python 用 os.environ["API_TOKEN"]),这部分是跨平台一致的,差异只在你怎么把变量塞进宿主进程。正文里请一律用 <YOUR_API_KEY> 这类占位,别把真 token 写进代码。

三、声明位置:两个,不是一个

位置一:调用 query() 时直接传 mcpServers

这是 SDK 的主入口。TypeScript:

import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "List files in my project",
  options: {
    mcpServers: {
      filesystem: {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
      }
    },
    allowedTools: ["mcp__filesystem__*"]
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

Python 是同一份配置换个写法,键名从驼峰变成下划线:

options = ClaudeAgentOptions(
    mcp_servers={
        "filesystem": {
            "command": "npx",
            "args": [
                "-y",
                "@modelcontextprotocol/server-filesystem",
                "/Users/me/projects",
            ],
        }
    },
    allowed_tools=["mcp__filesystem__*"],
)

代码块里的 /Users/me/projects 是官方文档示例中的占位路径,换成 <你的项目目录>。Windows 上这里要填 Windows 形式的路径,具体怎么转义取决于你用哪种字符串字面量,这一页没有给 Windows 路径的示例。

allowedTools 那一行不是可选装饰。文档给出的命名规则是 mcp__<server-name>__<tool-name>:一个叫 "github" 的服务器上的 list_issues 工具,全名就是 mcp__github__list_issues。通配符可以整台服务器放行:

allowedTools: [
  "mcp__github__*", // All tools from the github server
  "mcp__db__query", // Only the query tool from db server
  "mcp__slack__send_message" // Only send_message from slack server
]

这里有一条容易踩反的规则,文档专门用一个 Note 写了:给 MCP 放行要用 allowedTools,别指望 permission mode。 permissionMode: "acceptEdits" 不会自动批准 MCP 工具,它只覆盖文件编辑和文件系统类的 Bash 命令;permissionMode: "bypassPermissions" 确实会自动批准 MCP 工具,但同时关掉了大部分其它安全提示,范围比你要的宽得多。一个通配符只放开你指定的那台服务器,不多给。

位置二:.mcp.json 配合 settingSources

项目根目录放一个 .mcp.json

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
    }
  }
}

文档写明:这个文件在 project 这个 setting source 启用时被读取,而默认的 query() 选项就是启用的;但如果你显式设置了 settingSources,就必须把 "project" 包含进去,否则这个文件不会加载。这是个很典型的「我明明什么都没改,只是加了一行 settingSources」型故障。

两个位置在行为上并不等价,下一节就是差在哪。

四、生命周期:谁会拖住第一轮

这是本篇的重点,也是 SDK 侧和 CLI 侧体感差最多的地方。

文档的说法是:Claude Code 在启动时注册你通过 options.mcpServers 传入的服务器,并在首轮等待(如果有的话)结束后发出 init 消息。而从设置文件(例如 .mcp.json)加载的服务器拿不到这个完整等待,在 init 时经常显示 pending 你在两个位置写同一台服务器,看到的初始状态可能就不一样。

按服务器类型,官方文档列了三行:

服务器类型是否拖慢第一轮首轮等待超时
stdio 服务器,或没有缓存工具列表的 HTTP/SSE 服务器会,直到它连上MCP_TIMEOUT 控制,到点连接失败
有缓存工具列表的远程服务器(Claude Code 从上次连接保存下来的)不会;缓存的工具从第一轮就可用无;在首次工具调用时连接,那次延迟连接有自己的超时
进程内的 SDK MCP server不会;从不拖慢第一轮

三行读下来,结论很实际:你在代码里定义的进程内 SDK MCP server 永远不会拖住第一轮;而一台需要 npx 冷启动的 stdio 服务器会。(进程内自定义工具怎么写,官方另有 custom tools 一页在讲,这篇不展开。)

还有两个开关是控制更早那一段的——init 消息发出之前的启动阶段:

  • MCP_CONNECTION_NONBLOCKING 设为 0,会在整批连接上阻塞。这个等待有一个上限,上限值可以用 MCP_CONNECT_TIMEOUT_MS 环境变量调整,单位毫秒。到点还没连上的服务器会继续在后台连。
  • 在某台服务器的配置上设 alwaysLoad: true,它的工具会以完整 schema 在第一轮就可用,并豁免 tool search 的延迟加载。Claude Code 会在启动时等这台服务器的工具,等待同样受上面那个上限约束,其它服务器则继续在后台连。

顺带一句 tool search:文档说它默认开启,作用是把工具定义先扣着不放进上下文,每一轮只加载 Claude 需要的那些。所以「模型看不见工具」有时并不是没连上,而是它还没搜到——这跟 alwaysLoad 是同一件事的两面。

五、边界:哪些事 SDK 侧做不了

这一节别跳过,几个坑都在这。

OAuth 是半自动的。 文档明写:SDK 不会打开浏览器,也不会跑交互式 OAuth 流程。当一台配置好的服务器返回授权挑战、且没有已存 token 时,这次运行会在没有该服务器工具的情况下继续,服务器状态报 needs-auth——但 init 消息里的 mcp_servers 数组在发出时可能仍然显示 pending。要确认到底是不是缺凭据,得用 TypeScript 的 mcpServerStatus() 或 Python 的 get_mcp_status() 去轮询。想供凭据,就在你自己的应用里跑完 OAuth 流程,把拿到的 access token 塞进该服务器的 headers

对比一下 CLI 侧:code.claude.com/docs/en/mcp 那边有 /mcp 面板的浏览器登录流程,也有 claude mcp login <name> 这种从 shell 直接跑 OAuth 的命令。这些在 SDK 这页里都没有对应物。同一件事,两边给的是两套手段。

SSE 已被标记为 deprecated。 SDK 这页的传输一节仍然给了 "type": "sse" 的示例,没有加提示;而 CLI 那页在「Add a remote SSE server」下面有一个明确的 Warning,写的是 SSE 传输已弃用,能用 HTTP 就用 HTTP。两页口径不同,这里照实标出来:新接的服务器优先用 HTTP。

"streamable-http" 这个别名,只在 JSON 里认。 文档写得很清楚:.mcp.json 和其它 JSON 配置文件里,"streamable-http" 被接受为 "http" 的别名;而程序化传入的 mcpServers 选项只接受 "http"。你从某个服务器的 README 里复制一段 JSON,粘到 .mcp.json 里能用,改写成 options.mcpServers 的对象字面量就不行——这条差异值得单独记一下。

WebSocket:CLI 那页有一节讲 type: "ws",Agent SDK 这页的传输类型一节里没有列出 WebSocket,官方文档没有说明 SDK 侧的对应写法。这一点我们不替它补。

连接失败不会抛异常。 文档在错误处理的代码注释里写明:连不上的 MCP 服务器不会 throw / raise,你必须自己去查状态;而且在 init 时仍是 pending 的服务器,需要之后再查一次状态。

输出上限。 一次工具结果过大时,SDK 应用的是与 Claude Code 相同的 MCP 输出限制:完整输出会被落盘成文件,工具结果被替换为一条点名了文件路径的错误信息,让 agent 分段读回来。上限由 MAX_MCP_OUTPUT_TOKENS 环境变量抬高。具体阈值随版本变动,这里不写数值,以官方文档为准。

六、怎么验证配对了

不用猜,SDK 每次查询开头都会发一条 system 消息、subtype 为 init,里面带每台 MCP 服务器的连接状态。文档列出的 status 取值一共五个:"pending""connected""failed""needs-auth""disabled"

关键提醒也在文档里:别把 "pending" 当失败。 它覆盖两种情况——设置文件里的服务器没拿到完整等待,或者工具列表是从缓存里给的、连接推迟到首次调用。要检测真正不可用的,查 "failed""needs-auth"

if (message.type === "system" && message.subtype === "init") {
  const unavailableServers = message.mcp_servers.filter(
    (s) => s.status === "failed" || s.status === "needs-auth"
  );

  if (unavailableServers.length > 0) {
    console.warn("Unavailable MCP servers:", unavailableServers);
  }
}

想看这台服务器到底给了哪些工具,从同一条 init 消息里过滤 mcp__ 前缀:

for await (const message of query({ prompt: "...", options })) {
  if (message.type === "system" && message.subtype === "init") {
    const mcpTools = message.tools.filter((name) => name.startsWith("mcp__"));
    console.log("Available MCP tools:", mcpTools);
  }
}

Python 侧对应的是从 SystemMessagemessage.data.get("tools", []) 里过滤同样的前缀。

读这个 tools 数组时要带上第四节的结论:它列的是到 init 发出那一刻已经连上的服务器的工具,加上那些有缓存工具列表、留到首次调用才连的服务器的工具。还没连上的服务器,其工具在这里是缺席的——所以数组里少了几个工具,第一反应应该是查状态,而不是回去改 allowedTools

会话中途要拿最新状态,就调 mcpServerStatus()(TypeScript)或 ClaudeSDKClient.get_mcp_status()(Python)。

排查顺序建议这么走:先看 init 里的 status,failed 就按文档列的几类常见原因逐个排(环境变量没设、服务器没装、连接串不对、网络不通);status 是 connected 但工具没被调,那就回去看 allowedTools 有没有写对那个 mcp__servername__*;两边都对但第一轮就是没有,那多半是第四节的连接时机问题,考虑 alwaysLoad 或者把工具改成进程内的 SDK MCP server。

以上为按官方文档中的参数语义组合的示例,未经实测,以官方文档与 --help 的实际输出为准。


本文依据 Claude Code 官方文档(code.claude.com/docs)于 2026-08-17 的公开内容整理。 该产品闭源,本文只复述官方文档写明的机制,不推断其内部实现我们没有对文中涉及的功能做过实测,因此不涉及界面外观、操作手感与运行速度的任何描述。 该产品迭代频繁,文中涉及的命令、配置项与默认值随版本变动,请以官方文档最新内容为准。 本文不涉及价格、额度与限流的具体数值,相关信息请以官方定价与用量说明页为准。

安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。