Model Context Protocol(MCP) 让 LLM agent 有可能一次用上数以百计的工具,去完成各种真实世界的任务。但要怎么把这些工具做到最有效?
在这篇文章里,我们会分享自己在打磨各种 agent 系统时最有效的一组技术 1。
我们先讲清楚:
- • 怎么搭出一个工具原型并跑起来
- • 怎么用 agent 给工具做一套完整的评测
- • 怎么和 Claude Code 这样的 agent 一起协作,自动提升你工具的表现
文章最后,我们会总结一路下来沉淀的几条"写好工具"的核心原则:
- • 选对要做的工具(以及不该做的工具)
- • 给工具加命名空间前缀,划清功能边界
- • 让工具返回真正有意义的上下文
- • 优化工具响应,省 token
- • 把工具描述和 schema 当 prompt 来写

把评测搭起来,你才能系统地度量工具表现;之后还可以让 Claude Code 围绕这套评测,自动帮你优化工具。
什么是"工具"?
在计算机里,"确定性"系统在同样的输入下永远给出同样的输出,而非确定性系统——比如 agent——即便起点完全相同,也可能给出不一样的回应。
我们写传统软件,本质上是在两个确定性系统之间立一份"契约"。比如调用 getWeather("NYC"),它每次都会用完全相同的方式去拿纽约的天气。
而工具是一种新的软件形态:它体现了确定性系统和非确定性 agent 之间的一种契约。当用户问"我今天要带伞吗?"时,agent 可能直接调天气工具,也可能凭一般知识回答,甚至可能先反问一句"你在哪儿?"。偶尔它也会产生幻觉,甚至搞不懂某个工具到底怎么用。
这意味着,给 agent 写软件需要一次根本性的思路调整:不能再像给开发者或其他系统写函数和 API 那样写工具和 MCP server——它们得是为 agent 设计的。
我们的目标,是让 agent 能用工具去尝试各种成功路径,把它真正能解决的任务范围撑得更大。让人庆幸的是,我们的经验表明:那些对 agent 最"顺手"的工具,对人来说也往往意外地好理解。
怎么写工具
这一节,我们会讲怎么和 agent 一起协作,去写工具、改工具。第一步是搭一个简单的原型,本地跑起来;第二步是上一套完整的评测,用来衡量后续每一次改动的影响。然后你就可以和 agent 一起,不停地评测、改、再评测,直到 agent 在真实任务上的表现足够强。
搭原型
不亲自上手,光靠想,是很难预判哪种工具 agent 用着顺手、哪种不顺手的。所以先搭一个原型再说。如果你打算让 Claude Code 来写工具(甚至一次成型),最好提前把你工具会依赖的软件库、API、SDK(包括 MCP SDK)的文档喂给 Claude。LLM 友好的文档常常以扁平的 llms.txt 文件形式放在官方文档站点上(这是我们 API 的)。
把你的工具包成一个 本地 MCP server 或一个 Desktop 扩展(DXT),就能在 Claude Code 或 Claude Desktop 里连上试。
要把本地 MCP server 接到 Claude Code,运行 claude mcp add <name> <command> [args...]。
要把本地 MCP server 或 DXT 接到 Claude Desktop,路径分别是: Settings > Developer 或 Settings > Extensions。
工具也可以直接传给 Anthropic API 调用,跑程序化测试。
亲自把工具试一遍,把粗糙的地方找出来。再从你的用户那里收集反馈,逐渐建立起对"工具应该支持哪些用例和 prompt"的直觉。
跑评测
接下来,你需要通过评测来衡量 Claude 用你这些工具到底有多好。先生成大量评测任务,全部基于真实世界的用法。我们建议你和一个 agent 协作,让它帮你分析评测结果、判断怎么改进工具。完整流程可以看我们的工具评测cookbook。

我们内部 Slack 工具在留出测试集上的表现
生成评测任务
哪怕只有一个早期原型,Claude Code 也能很快把你的工具摸一遍,生成几十条 prompt 和对应的回答。这些 prompt 应该取材于真实用法,基于真实的数据源和服务(比如内部知识库和微服务)。我们建议你避免那种过于简单、表面化的"沙盒"环境——它们的复杂度根本不足以压力测试你的工具。强一点的评测任务可能需要多次工具调用——甚至几十次。
下面是几个强任务的例子:
- • 帮我和 Jane 约下周开个会,聊 Acme Corp 这个项目的最新进展。把我们上次项目规划会的笔记附上,再订一间会议室。
- • 客户 ID 9182 反馈说一次购买被扣了三次款。把所有相关日志找出来,并判断有没有其他客户撞上了同样的问题。
- • 客户 Sarah Chen 刚提交了一份取消请求。准备一份挽留方案。请给出:(1) 她想离开的原因,(2) 哪种挽留方案最有吸引力,(3) 我们出价前需要警惕的风险点。
下面是几个偏弱的任务:
- • 帮我和 jane@acme.corp 约下周开个会。
- • 在支付日志里搜
purchase_complete和customer_id=9182。 - • 找到客户 ID 45892 的取消请求。
每条评测 prompt 都需要配一个可验证的回答或结果。验证器既可以简单到对 ground truth 和采样回答做字符串精确比对,也可以高级到让 Claude 来当评委。要避免那种太严格的验证器——格式、标点、合理的换种说法之类的无关差异不应该把正确答案判错。
每对 prompt-回答,你还可以额外标注:你希望 agent 在解题过程中调用哪些工具。这样能衡量 agent 在评测过程中是否真的理解了每个工具的用途。但任务往往有多条正确路径,别过度指定,更别让评测过拟合到某一种策略上。
跑评测
我们建议你直接用 LLM API 调用,以程序化方式跑评测。用简单的 agent 循环(while 循环里交替进行 LLM API 调用和工具调用)就行:每条评测任务一个循环,每个评测 agent 只拿到一个 prompt 和你的工具。
在评测 agent 的 system prompt 里,我们建议你不仅要求 agent 输出可验证的结构化回答块,还要它输出推理和反馈块。让 agent 在工具调用和工具返回之前先把这些写出来,相当于触发思维链(chain-of-thought, CoT)行为,可以提高 LLM 实际表现出来的智力水平。
如果你用 Claude 来跑评测,可以直接打开 交错思考(interleaved thinking),开箱即用地获得类似效果。这能帮你看清楚 agent 为什么调或不调某个工具,并指出工具描述和 schema 里具体要改的地方。
除了顶层准确率,我们建议你顺便收一些别的指标:单次工具调用和单次任务的耗时、工具调用总次数、token 总消耗、工具报错数。盯着工具调用序列,往往能看出 agent 反复走的常见工作流——这就是把多个工具合并成一个的机会。

我们内部 Asana 工具在留出测试集上的表现
分析结果
agent 是帮你发现问题、给反馈的得力伙伴:从相互矛盾的工具描述、低效的工具实现,到让人迷惑的工具 schema,它都能帮你看出来。但要记住一点——agent 在反馈和回答中没说的,往往比它说出来的更重要。LLM 不见得总能说出它真正在想的。
留意 agent 哪里被卡住、哪里被绕晕。读它们的推理和反馈(或 CoT),把粗糙的地方找出来。再去翻原始对话记录(包括工具调用和工具返回),把 CoT 里没明说的行为也抓出来。要读懂言外之意——你的评测 agent 并不一定知道正确答案和最优策略。
分析工具调用的指标。大量重复调用,可能在提示你"分页或 token 上限参数该调整大小了";大量因参数非法导致的报错,可能在提示你"工具描述需要更清楚,或者多放几个示例"。我们当初上线 Claude 的 网页搜索工具 时就发现,Claude 老是无端往 query 参数后面拼一个 2025,把搜索结果带偏、拖累效果(后来我们靠改进工具描述把它纠回来了)。
和 agent 一起协作
你甚至可以直接让 agent 帮你分析结果、改你的工具。把评测 agent 的对话记录拼起来,丢给 Claude Code 就行。Claude 很擅长读对话记录、一次性重构大量工具——比如在改动落地时保持工具实现和描述彼此自洽。
事实上,这篇文章里的大部分建议,正是我们和 Claude Code 一遍又一遍优化我们内部工具实现的过程中得到的。我们的评测就建在我们自己的内部 workspace 上,模拟内部工作流的复杂度——包括真实项目、文档和消息。
我们靠留出测试集保证自己没有把评测过拟合到"训练集"上。这些测试集表明:即便是"专家级"的工具实现——不管是研究员手写的,还是 Claude 自己生成的——我们也还能再压出一波性能。
下一节,我们会把这一过程中沉淀下来的经验分享出来。
写好工具的几条原则
下面我们把心得提炼成几条原则。
选对要做的工具
工具越多,未必结果越好。我们看到的一个常见错误是:直接把现有软件的功能或 API 端点原样包成工具——根本没想清楚这些工具对 agent 是否合适。原因在于,agent 拥有和传统软件截然不同的"可供性"(affordance,指它感知"自己能用工具做什么"的方式)。
LLM agent 的"上下文"是有限的(它一次能处理的信息量有上限),而计算机内存又便宜又充裕。想想"在通讯录里找一个联系人"这件事:传统软件可以高效地一个一个遍历,每个都比一下。
但如果 LLM agent 用的工具会一次性把所有联系人返回,再让它自己逐 token 读完,那它就是在用宝贵的上下文空间存无关信息(想象一下,你找联系人时把整本通讯录从第一页念到最后一页——这就是 brute-force 暴力搜索)。更好、也更自然的做法(对 agent 和人都一样)是先翻到对应的那一页(比如按字母直接定位)。
我们的建议是:围绕高价值的工作流,精心做几个工具,对齐你的评测任务,再从这里往外扩展。在通讯录这个例子里,与其做一个 list_contacts,不如做 search_contacts 或 message_contact。
工具可以把功能合并起来,在内部一次性完成多个离散操作(或多次 API 调用)。比如,工具可以在响应里顺带带上相关元数据,或者把经常串起来的多步操作合成一次工具调用。
举几个例子:
- • 与其分别做
list_users、list_events、create_event,不如做一个schedule_event:它自己找空闲时间并把会议定下来。 - • 与其做
read_logs,不如做search_logs:只返回相关的日志行,外加一点上下文。 - • 与其做
get_customer_by_id、list_transactions、list_notes,不如做一个get_customer_context:把这个客户最近的、相关的信息一次拼齐返回。
每个工具的职责都要清晰、独立。工具要让 agent 能像人一样去拆解任务、解决任务——前提是给它同样的底层资源——同时把那些原本要被中间产物占掉的上下文省下来。
工具太多,或者功能重叠的工具太多,反而会干扰 agent 选择高效策略。"要做什么"和"不做什么",都需要谨慎规划——这一步真的能换来回报。
给工具加命名空间前缀
未来你的 AI agent 可能会同时接入几十个 MCP server、几百个工具——其中不少还是别人写的。当工具功能重叠或职责模糊时,agent 很容易迷糊该用哪个。
加命名空间前缀(把相关的工具放在一个公共前缀下)能帮你在大量工具之间画清边界;MCP 客户端有时默认就这么做。例如,按"服务"加前缀(asana_search、jira_search),再按"资源"加前缀(asana_projects_search、asana_users_search),能帮 agent 在恰当的时机选到恰当的工具。
我们还发现:前缀 vs 后缀,对工具使用评测结果会有不小的影响。不同的 LLM 表现也不一样,建议你基于自己的评测来选命名方案。
agent 可能会调错工具、用错参数调对了工具、调用次数太少,或者错误地处理工具返回。把工具名设计得能自然地切分任务,等于一举两得:既减少了 agent 上下文里加载的工具数量和描述,又把"该用哪个工具"的判断从 agent 上下文里挪到了工具调用本身。这都能降低 agent 整体出错的概率。
让工具返回有意义的上下文
同样的道理,工具实现应该注意只把高信噪比的信息返回给 agent。优先考虑上下文相关性而非灵活性,并避免返回底层技术性标识(比如 uuid、256px_image_url、mime_type)。name、image_url、file_type 等字段更可能告诉 agent 下游的操作和响应,对后续的回答更有帮助。
agent 处理自然语言名称、术语、标识符,普遍比处理神秘的字符串标识好得多。我们的经验是:把任意字母数字 UUID 替换成更具语义的可读语言(甚至从 0 开始编号),就能显著降低幻觉、把 Claude 在检索任务上的精度往上拉一个台阶。
某些情况下,agent 既需要自然语言形态、也需要技术性标识——后者是为了触发下游工具调用(例如 search_user(name="jane") → send_message(id=12345))。你可以在工具里暴露一个简单的 response_format 枚举参数,让 agent 自己决定要 "concise" 还是 "detailed" 的响应(图见下文)。
你也可以加更多枚举值,达到类似 GraphQL 的灵活度——让调用方精确地选自己想要的字段。下面是控制工具响应详尽程度的一个 ResponseFormat 枚举示例:
enum ResponseFormat {
DETAILED = "detailed",
CONCISE = "concise"
}这是一个详细工具响应的例子(206 tokens):

这是一个精简工具响应的例子(72 tokens):

Slack 的会话线程及其回复通过唯一的 thread_ts 标识,要拉取回复就必须有它。其他 ID(如 channel_id、user_id)可以从"详细"响应里拿到,用来支撑后续需要这些 ID 的工具调用。"精简"响应只返回内容、不返 ID。在这个例子里,"精简"响应只占了"详细"响应大约 1/3 的 token。
哪怕是工具响应的结构本身——XML、JSON 还是 Markdown——也会影响评测表现:没有放之四海皆准的最优选。这是因为 LLM 是用 next-token 预测训练的,更擅长它训练数据里常见的格式。最合适的响应结构因任务和 agent 而异,建议你以自己的评测为准来选。
优化工具响应,省 token
上下文的质量要优化;它的数量同样要优化——也就是工具响应里返回给 agent 的内容量。
对于任何可能吞掉大量上下文的工具响应,我们建议你做一些组合:分页、范围选择、过滤、截断,并设好合理的默认参数。Claude Code 默认把工具响应限制在 25,000 tokens 以内。我们预期 agent 的有效上下文长度会随时间变长,但"工具要省 token"这件事不会变。
如果你确实要截断响应,记得给 agent 一些有用的指引。你可以直接鼓励 agent 走更省 token 的策略,比如做知识检索时多做几次小范围、精准的搜索,而不是一次性宽泛大搜。同样,工具调用报错时(比如入参校验失败),你可以把错误响应也当 prompt 来写,明确告诉 agent 怎么改才对,而不是甩一串不透明的错误码或调用栈。
下面是一个被截断的工具响应的例子:

下面是一个没什么帮助的报错响应的例子:
下面是一个有帮助的报错响应的例子:

工具的截断和报错响应,可以把 agent 推向更省 token 的使用方式(用过滤或分页),或者直接给它看正确入参格式的示例。
把工具描述和 schema 当 prompt 来写
下面要讲的,是改进工具最有效的方法之一:把工具描述和 schema 当 prompt 来精心写。这些内容会被加载进 agent 的上下文,能从整体上引导 agent 做出更合理的工具调用。
写工具描述和 schema 时,想象你在向团队里一个新来的同事介绍这个工具。你心里可能默认带着的那些上下文——专属查询格式、小众术语的定义、底层资源之间的关系——都要明说出来。要消除歧义,明确(用严格的数据模型强制)你期望的输入和输出。特别是入参的命名,不要含糊:与其叫 user,不如叫 user_id。
有了评测,你就可以更有底气地衡量这些 prompt 改动的效果。哪怕是工具描述上的小修小补,都可能带来惊人的提升。Claude Sonnet 3.5 在 SWE-bench Verified 上拿到 SOTA,正是因为我们对工具描述做了精细打磨——错误率显著下降、任务完成率显著上升。
工具定义的更多最佳实践,可以参考我们的 开发者指南。如果你是给 Claude 做工具,建议再读一下工具是怎么动态加载进 Claude system prompt 的。最后,如果你是给 MCP server 写工具,tool annotations(工具注解)可以用来标明哪些工具需要"开放世界"访问,或者会做有破坏性的变更。
展望
要给 agent 做好工具,我们得把软件研发的习惯从"可预测、确定性"的范式,转向"非确定性"的范式。
在本文描述的这种迭代式、评测驱动的流程里,我们识别出了一些一致的模式:好用的工具往往目标清晰、刻意而为,把上下文用得精打细算,能在各种工作流中组合使用,并让 agent 凭直觉解决真实任务。
接下来,agent 与世界打交道的具体机制会继续演化——从 MCP 协议的升级,到底层 LLM 本身的升级。只要我们用一套系统化的、评测驱动的方法不停打磨工具,那么随着 agent 能力变强,它们手里的工具也会一起跟上。
来源:https://www.anthropic.com/engineering/writing-tools-for-agents
夜雨聆风