AI 开发

Claude Code、Codex 等 AI Coding Agent 如何调用 MCP Tools?JSON-RPC、Tool Schema 与执行权限完整解析

阅读约 16 分钟

协议对比文写过 Schema 和 Function Calling 的分工。本篇只落到 Coding Agent:模型填参数,宿主发 JSON-RPC。接服务器、列工具、过 Schema、再评权限,是四步,不是一句「连上 MCP」。

阅读前速览

步 锁什么 谁执行
配置 .mcp.json / config.toml stdio 或 http;url 必须带 type
发现 tools/list + inputSchema 宿主列工具,模型看不见传输
调用 tools/call 的 arguments 模型填 JSON,宿主发 RPC
权限 mcp__server__tool / enabled_tools 连上 ≠ 自动执行

MCP 是宿主和服务器的协议。模型只看见工具名和参数 Schema。

调 MCP 的是宿主,不是模型

Claude Code、Codex、Cursor 都是 MCP 客户端。会话开始后,宿主连上服务器,发 JSON-RPC 2.0 的 tools/list,把每条工具的 inputSchema 交给模型。模型只产出一段参数 JSON。真正发 tools/call、跑副作用的是宿主。没有宿主这一跳,MCP 只是配置文件。

  • 配置:服务器怎么启动。stdio 跑本地进程,http 连远程。两边都是对象,不要口头约定。
  • Schema:order_id 必须过 inputSchema。宽 object 等于请模型发明键。
  • 权限:Claude 用 mcp__服务器__工具 写规则。Codex 用 enabled_tools 和审批档。Cursor 用 mcpAllowlist。连上服务器,不等于这些门已经开。

协议层的 Schema 和 Function Calling,见 MCP Tool Schema vs Function Calling。Coding Agent 自己的工具 JSON,见 AI Coding Agent 与 JSON 工具调用。Schema 为什么是门,见 AI 为什么需要 JSON Schema。本篇不再定义那三个词,只写宿主怎么把 MCP 跑起来。

第一张对象:服务器配置

Claude Code 的项目配置是仓库根的 .mcp.json,可进版本库。本机私有的写进 ~/.claude.json。有 url 就必须写 type:http(streamable-http 是别名)或 sse / ws。只写 url、不写 type,官方会当成坏掉的 stdio,服务器被跳过。虚构订单服务,不要贴真实密钥:

{
  "mcpServers": {
    "orders": {
      "type": "stdio",
      "command": "node",
      "args": ["./servers/orders-mcp.js"]
    }
  }
}

Codex 把同一件事写进 ~/.codex/config.toml 或可信项目的 .codex/config.toml,表名是 [mcp_servers.orders]。stdio 用 command / args,远程用 url。Cursor 用 .cursor/mcp.json 或用户级文件,编辑器和 CLI 共用。项目里新出现的服务器,Claude Code 会停在待批准,不是自动连。

第二张对象:tools/list 和 inputSchema

宿主连上之后,先问有哪些工具。这是 JSON-RPC,不是模型自己发的 HTTP:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

服务器回一张表。每一行是名字、说明、一份 JSON Schema。规范要求声明 tools 能力。列表可以随授权变,但不应随连接抖动。Coding Agent 把这张表转成模型能看的工具。Claude Code 里,可调用名是 mcp__orders__lookup_order。插件带来的服务器还要嵌插件名。工具太多时,官方会推迟加载,用工具搜索按需展开,不要默认把整表塞进上下文。

{
  "name": "lookup_order",
  "description": "Look up an order by ID. Confirm before any refund.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "order_id": { "type": "string", "pattern": "^[A-Z]-\\d{4}$" }
    },
    "required": ["order_id"],
    "additionalProperties": false
  }
}

第三张对象:模型填参,宿主 tools/call

模型看见的是宿主给的工具名。Claude Code 会话里,一次调用长这样:

{
  "type": "tool_use",
  "id": "call_3c91",
  "name": "mcp__orders__lookup_order",
  "input": { "order_id": "A-1042" }
}

宿主校验 input 对上 inputSchema,再翻译成发给服务器的 RPC。名字用服务器注册名 lookup_order,不再带 mcp__ 前缀:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "lookup_order",
    "arguments": { "order_id": "A-1042" }
  }
}

成功就回 content。失败把 isError 设真,原因写成对象,不要只丢一句散文。模型读的是回写,不是套接字。

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      { "type": "text", "text": "{\"order_id\":\"A-1042\",\"status\":\"paid\"}" }
    ],
    "isError": false
  }
}

第四张对象:执行权限

MCP 规范写明:工具调用应当能被拒绝。Coding Agent 把拒绝写成规则,不写成提示。Claude Code 用完整可调用名:

{
  "permissions": {
    "allow": ["mcp__orders__lookup_order"],
    "ask": ["mcp__orders__create_note"],
    "deny": ["mcp__orders__refund_order"]
  }
}

官方顺序仍是 deny、ask、allow。光杆 mcp__* 作 deny,会把 MCP 从上下文摘掉。Codex 用 enabled_tools 做白名单,disabled_tools 做随后的黑名单,默认审批是 auto / prompt / approve,也可按工具覆盖。Cursor 默认每次问;permissions.json 的 mcpAllowlist 写成 orders:lookup_order。Shell / Git / 文件那三道门,见 Coding Agent 权限模型。MCP 是第四张表,不是那三道门的替代。

五步把 MCP 收成对象

不要从「帮我接一个 MCP」开始。按对象写:

  1. 先写服务器配置。stdio 或 http 选一个。url 带 type。密钥走环境变量,不进仓库。
  2. 自己跑一遍 tools/list。记下名字和 required。没有 Schema 的工具不要交给模型。
  3. 收紧 inputSchema。pattern、enum、additionalProperties: false。查单不要带退款键。
  4. 按工具写权限。只读查单 allow。写盘 ask。退款 deny。Codex 用 enabled_tools 先裁表。
  5. 把一次成功调用存成三份 JSON:tool_use、tools/call、result。能对上,才算接通。

Schema 怎么当门,见 Agents API + JSON Schema。本篇只要求:Coding Agent 上,MCP 的每一步都有对象,而且由宿主发。

复盘 MCP,先摊开三份 JSON

一次失败,多半是名字、Schema、权限没对齐,不是模型不会调。

  • 把 inputSchema 和 arguments 格式化,看缺字段和多余键。
  • 用 JSONPath 提取 抽出 $.params.name、$.input.order_id。
  • 一次过门和一次被拒 JSON Diff。先改规则和 Schema,再改提示。
  • 核对宿主名 mcp__orders__lookup_order 和 RPC 名 lookup_order。插件服务器还要带插件段。
  • 配置、令牌、会话日志不要贴进公共格式化站。JSONSort 在浏览器本地跑。

三份轨迹都能按 Schema 重建,你才是在收 MCP,不是在收「我点过连接」。

常见误区

  • 以为连上就等于会调。连上只是进程或 HTTP 通了。没有 list、没有 Schema、没有权限,模型仍调不成。
  • 把 MCP 当成 Function Calling。对模型都是填 JSON。对宿主,MCP 多一跳 JSON-RPC。协议对比文写过分工,这里只要求你看见那一跳。
  • url 不写 type。Claude Code 会按坏 stdio 跳过。远程服务器就此失踪。
  • 只配服务器,不裁工具。一张大表进上下文。官方用推迟加载和 enabled_tools,就是怕这个。
  • 用一句话「请自动批准所有 MCP」当默认。Cursor 的一键全批、Claude 的 bypass、Codex 的 auto 全开,官方留给隔离环境。日常按工具写。
  • 项目 .mcp.json 当成本机私货。它会进版本库,别人打开仓库会看到待批准。密钥放用户级配置。

FAQ

Coding Agent 会自己发 JSON-RPC 吗?

不会。模型只填工具名和 arguments。JSON-RPC 的 id、method、params 由宿主拼。你在日志里看到 tools/call,那是客户端,不是模型。

inputSchema 和权限哪个先评?

先形状,再执行。Schema 不过,不要发 RPC。过了 Schema,仍可能 ask 或 deny。两道门锁的不是同一件事。

stdio 和 http 怎么选?

要碰本机文件或本机进程,用 stdio。要连托管服务,用 http。Claude Code 推荐远程走 http;SSE 已标过时。WebSocket 适合服务器主动推事件。

工具太多怎么办?

Claude Code 可推迟 MCP 工具,按搜索再展开。Codex 用 enabled_tools 裁表。不要把五十个工具一次塞进会话。

Subagent 能用这些 MCP 吗?

看产品。Claude Code 的权限和 allowed-tools 要用完整可调用名。Agents API 的子代理继承 MCP、不继承你的 function。不要假设一张表自动传给所有子会话。

结论

Coding Agent 调 MCP,不是模型直接打服务器。宿主列工具,模型填 JSON,宿主再 tools/call。Schema 和权限是两道门。

先写清 A-1042 会走哪一个工具名、哪一份 Schema、哪一条权限。能指到三份对象,MCP 才算接通。模型会换。这三份应该先稳定。

延伸阅读

更新记录: 首次发布