Typography

学习笔记

Claude Agent SDK学习12 自定义工具

发布于 # Claude Agent SDK

当前 Agent 的问题

第十一章之后,你已经能加载团队能力包,但 agent 仍然只能使用:

如果你现在想让 Claude 调用自己的业务逻辑,例如:

那就需要自定义工具。

本章功能的作用

这一章会引入:

自定义工具最终会被挂到一个进程内 MCP server 上,这也是为什么它和外部 MCP server 在 Claude 看来几乎是同一类能力。

它解决的根本问题是:Claude 终于可以不只依赖通用内建工具,而是开始访问你自己的业务能力。只要一段逻辑能够被封装成清晰的输入输出,它就可以变成 agent 的可调用工具,这也是 Agent SDK 从“通用助手”走向“业务系统组件”的关键一步。

官方文档里把这条链路拆得很清楚:定义工具、放入进程内 MCP server、在 query() 里注册 server、再通过 namespaced 名称授权工具。只有这四步都打通,Claude 才算真正获得了这把工具的调用权。

先看完整接入链路,后面读示例代码会轻松很多:

flowchart LR
    A["tool() 定义 name / description / schema / handler"] --> B["createSdkMcpServer() 打包为本地 MCP server"]
    B --> C["query() 通过 mcpServers 注册"]
    C --> D["allowedTools 开放 mcp__server__tool"]
    D --> E["Claude 生成参数并调用 handler"]
    E --> F["返回 content / structuredContent"]

具体使用方式

第一步:先把业务能力拆成单一工具

你应该先明确一个工具到底做一件什么事,例如“长度换算”“查询内部制度”“根据 SKU 计算价格”。自定义工具越聚焦,Claude 越容易判断何时该调用它。

工具边界越清晰,Claude 的调用稳定性越高。如果一个工具同时承担“查询价格、校验库存、生成解释文本”三种职责,模型往往既不容易判断该不该调,也不容易稳定地组织出正确参数。

名称和描述在这里不是附属信息,而是工具发现逻辑的一部分。Claude 会根据它们决定何时调用,所以“叫得准、描述得清楚”几乎和 handler 本身同样重要。

第二步:用 tool() 定义输入和处理函数

tool() 的四个核心部分分别是:名称、描述、schema、handler。名称和描述帮助 Claude 决定是否调用,schema 帮助 Claude 组织参数,handler 才是你真正的业务逻辑。

这里最容易被低估的是描述和 schema。对人类开发者来说,真正重要的是 handler;但对 Claude 来说,是否能正确调用一把工具,首先取决于它是否能从名称、描述和 schema 中读出“这把工具适合什么场景、需要什么参数”。

官方 TypeScript 方案特别推荐在 Zod 字段上使用 .describe()。这些字段说明不会改变你的业务逻辑,却会直接进入模型可见的工具 schema,对提升参数生成质量很有帮助。

第三步:把工具放进 createSdkMcpServer()

虽然这是本地代码,但在 SDK 眼里它仍然是一个 MCP server。这样做的好处是:本地工具和外部 MCP 工具在调用模型里被统一了,后续接入方式一致。

这种统一非常重要,因为它让你的系统在演进时不用推翻工具层设计。今天可能只是本地一个转换函数,明天可能升级成外部服务,但 Claude 侧的接入抽象仍然保持一致。

另外,自定义工具并不天然都是“会修改状态的”。如果你的工具确实没有副作用,可以考虑补充 readOnlyHint 注解。官方文档说明它会影响并行调度,让多个只读工具有机会在同一轮里并发运行。

第四步:在主 query 里注册并授权该工具

真正生效需要两步一起完成:一是在 mcpServers 中挂上这个 server,二是在 allowedTools 中开放对应的 namespaced 工具名。

这也是自定义工具最容易“看起来写完了但就是不生效”的地方。定义工具、创建 server、注册 server、授权工具,这四步缺一不可。任何一步漏掉,Claude 都不可能稳定调用到你的业务逻辑。

关键概念

1. 一个自定义工具由什么组成

最常见的四部分是:

2. 为什么 schema 很重要

因为 Claude 不是直接调用你的 JS 函数,它必须先根据 schema 生成参数。schema 越清晰,Claude 生成参数的稳定性越高。

3. 为什么推荐用 structuredContent

如果你的工具结果后面还要被程序消费,structuredContent 比单纯的文本更稳。

前置准备

这一章除了 SDK,还需要:

npm install zod

可运行示例

把下面代码保存为 chapter-12-custom-tools.ts

import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

const convertLength = tool(
  "convert_length",
  "Convert meters to centimeters or kilometers",
  {
    value: z.number().describe("Length in meters"),
    unit: z.enum(["cm", "km"]).describe("Target unit")
  },
  async ({ value, unit }) => {
    const converted = unit === "cm" ? value * 100 : value / 1000;
    return {
      content: [
        {
          type: "text",
          text: `${value} meters is ${converted} ${unit}`
        }
      ],
      structuredContent: {
        inputMeters: value,
        outputValue: converted,
        outputUnit: unit
      }
    };
  }
);

const mathServer = createSdkMcpServer({
  name: "math-tools",
  version: "1.0.0",
  tools: [convertLength]
});

async function main() {
  for await (const message of query({
    prompt: "Convert 12 meters to centimeters.",
    options: {
      mcpServers: { "math-tools": mathServer },
      allowedTools: ["mcp__math-tools__convert_length"]
    }
  })) {
    if (message.type === "result") {
      console.log(message.result);
      console.log("Structured output:", message.structured_output ?? null);
    }
  }
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

运行:

npx tsx chapter-12-custom-tools.ts

示例拆解

第一步:定义 convert_length 工具

示例用 tool() 创建了一个长度换算工具,输入包含 valueunit。这个例子简单,但已经完整覆盖了“描述 + schema + handler”的最小组合。

第二步:在 handler 中同时返回文本和结构化结果

content 给 Claude 作为自然语言上下文,structuredContent 则给程序或后续流程做稳定消费。这个双通道设计是自定义工具非常实用的模式。

为什么要同时返回两种结果?因为 Claude 更擅长消费自然语言上下文,而你的程序更适合消费结构化对象。把两者分开,既能让模型继续推理,也能让宿主系统稳定读取关键字段。

如果工具执行失败,也不一定要直接抛异常中断整轮 agent loop。官方返回协议支持通过 isError: true 把失败作为一个结构化工具结果回给 Claude,让它自己决定是否重试、换方案或向用户解释。

第三步:通过 createSdkMcpServer() 暴露给 Claude

示例把工具放进名为 math-tools 的 server。这样 Claude 看到的工具名就会变成 mcp__math-tools__convert_length

第四步:在 query 中只授权这一把工具

allowedTools 只开放 mcp__math-tools__convert_length,目的是让读者清楚看到:自定义工具并不会因为被创建出来就自动可用,它还必须经过授权。

运行时你应该观察什么

易错点

本章结束后你应该掌握

本章小结

到这里,你的 agent 已经不再局限于内建能力,而是开始真正访问你的业务世界。