← 返回教程库

MCP 工具 / Server 设计四原则:3-10 个工具、结构化错误、传输层鉴权

最后更新 2026-06-24
你将学到
  • 懂为什么一个 server 工具数控制在 3-10 个,太多 Agent 反而选不对
  • 学会返回结构化错误而非裸异常,让 Agent 能读懂、能重试
  • 把鉴权放在传输层,每次调用身份已知,工具代码不再各自校权
  • 用一份设计 checklist 和「拆成多个 server」的方法,把臃肿 server 拆干净

你照着上一节写出了能跑的 MCP server,挂进 Agent 一试——它要么挑错工具,要么调用失败后一脸懵不知道该重试还是放弃,要么你在每个工具里都写了一遍"先校验身份"。这些都不是 bug,是设计没做对。一个 server 能不能让 Agent 真正用顺,靠的不是功能多,而是几条克制的设计原则。这一节给你四条:工具数量、结构化错误、传输层鉴权、命名与描述,每条都讲清"为什么"和"怎么做"。

这篇适合谁:已经接过或写过 MCP server,但发现 Agent 用得不顺、想知道怎么把 server 设计好的人。读完你能写出一个 Agent"一看就会用、出错也不慌"的 server。


原则一:一个 server 3-10 个工具,别塞几十个

最反直觉、也最重要的一条:工具不是越多越好,是越精越好。 一个 server 暴露的工具,控制在 3 到 10 个最舒服。

为什么不能塞几十个?因为 Agent 选工具,是模型在"读完所有工具的名字和说明后挑一个"。工具一多,问题就来了:

  • 选择困难,准确率反而下降。几十个名字相近的工具摆在面前(get_userget_user_infofetch_userquery_user_data……),模型挑错的概率显著上升。
  • 每个工具的定义都常驻上下文,几十个工具的 schema 全程占着 token,又贵又挤。
  • 意图被稀释。一个目标清晰的 server("这是管订单的")比一个什么都干的 server("这是管订单、用户、库存、报表、通知的")好理解得多。

实操判断:如果你的工具列表超过 10 个,先问自己能不能合并(三个相似的查询合成一个带参数的工具),合并不了就拆成多个 server(订单一个 server、用户一个 server,见文末专门讲)。


原则二:返回结构化错误,不是裸异常

工具出错时,返回一个 Agent 能读懂的结构化错误对象,而不是让异常裸奔。

对比一下两种返回。裸异常(错误示范):

Traceback (most recent call last):
  File "server.py", line 42, in get_order
    ...
KeyError: 'order_id'

Agent 拿到这一坨,基本只能干瞪眼——它读不懂这是"参数错了"还是"系统崩了",更不知道该不该重试。

结构化错误(正确示范):

{
  "ok": false,
  "error": "invalid_argument",
  "message": "缺少 order_id 参数,请提供订单号后重试",
  "retryable": true
}

差别就在于,结构化错误给了 Agent 三样它能据此行动的信息

  • error:一个机器可判的错误码(invalid_argument / not_found / unauthorized / rate_limited),让它能分类处理。
  • message:一句人话,说清哪错了、怎么补救——Agent 会照着调整下一步。
  • retryable(可选):明确告诉它"这个错值不值得重试"。限流(rate_limited)值得等一下重试,参数错(invalid_argument)得先改参数再重试,权限不够(unauthorized)重试多少次都没用。

好的错误返回让 Agent 自己把流程走通:它看到"缺 order_id",会回头找订单号再调一次;看到"限流",会稍等重试。裸异常则直接让它卡死或胡乱重试。


原则三:鉴权放传输层,工具代码别自己校

第三条容易被忽略:身份验证应该在传输层一次性解决,而不是在每个工具函数里各校一遍。

什么意思?当客户端连接你的 server 时,连接本身就带好了身份(通过 token、API key 等,在建立连接/每次传输时校验)。等请求到达你某个具体工具的函数体时,"谁在调"这件事已经确定了——你的工具代码可以直接信任这个身份,专心干业务逻辑。

反面做法是这样(别学):

# 反面示范:每个工具自己校鉴权,重复又易漏
def get_order(order_id, token):
    if not verify_token(token):        # 第 1 个工具校一遍
        return {"error": "unauthorized"}
    ...

def cancel_order(order_id, token):
    if not verify_token(token):        # 第 2 个工具又校一遍
        return {"error": "unauthorized"}
    ...

这样的毛病:校验逻辑散落在每个工具里,重复、易漏、改起来要改一片——漏掉一个工具的校验就是个安全洞。

正确做法是把鉴权收口到传输层(server 接收连接/请求那一层统一做),工具函数体里默认身份已知,只管业务:

# 正确:鉴权在传输层统一做,工具函数信任已注入的身份
def get_order(order_id, ctx):
    user = ctx.identity        # 身份已由传输层注入,不在这校
    return query_order(order_id, on_behalf_of=user)

具体怎么在传输层配鉴权、ctx/上下文怎么拿到身份,各 SDK 和传输方式(本地 stdio / 远程 HTTP)写法不同,以官方文档为准。这里要你记住的是原则:校验在门口做一次,房间里别再各查一遍。


原则四:命名和描述决定 Agent 用不用得对

Agent 调不调你的工具、在不在对的时候调,几乎全看工具名和 description。这是 server 设计里"投入产出比最高"的一块。

  • 名字用动词+名词,一看就懂get_order_statussearch_issuescreate_ticket 这种,比 processhandledo_query 这种含糊词强太多。Agent 是靠名字猜用途的。
  • description 写"什么场景用 + 输入啥 + 输出啥",别只写一句空话。对比:
差:query_data —— "查询数据"

好:search_issues —— "在指定仓库里按关键词搜 issue。
输入仓库名和搜索词,返回匹配的 issue 标题、编号、状态列表。
当用户想找某个仓库里跟某话题相关的 issue 时用它。"
  • 参数也要有说明:每个参数标清楚是什么、什么格式(order_id: 订单号,纯数字字符串)。Agent 传参传错,往往是参数说明没写清。
  • 同一个 server 内工具别名字打架:避免 get_userfetch_user 这种近义词并存,Agent 会分不清该用哪个。

记一句:你是在给 Agent 写"用法说明书",不是给自己写注释。 把它当成一个第一次见这个系统的新人来教。


MCP 工具设计 checklist

设计每个 server 时,按这五条过一遍:

  1. 数量:工具数在 3-10 个?超了先合并、再拆 server。
  2. 命名:每个工具名是"动词+名词"、一眼能猜出用途?同 server 内无近义词打架?
  3. 结构化错误:所有出错路径都返回 {error 码 + message + 可选 retryable},没有裸异常逃出去?
  4. 传输层鉴权:身份在连接/传输层统一校验,工具函数体里不再各自校?
  5. 描述清晰:每个工具的 description 写清"何时用 + 输入 + 输出",参数有格式说明?

这五条——数量、命名、结构化错误、传输层鉴权、描述——是一个"Agent 用得顺"的 server 的最低标准。缺哪条,对应的坑就会在用的时候冒出来。


工具太多怎么拆成多个 server

当你发现工具实在压不到 10 个以内,按"领域"拆成多个 server,而不是硬塞一个:

  • 按业务领域切:订单相关的工具放 orders-server,用户相关的放 users-server,库存相关的放 inventory-server。每个 server 目标单一、工具精简。
  • 按权限级别切:只读工具一个 server、能写的工具另一个 server,这样你可以只在需要时才挂上"能写"的那个,平时降低误操作风险。
  • 拆完各自仍守 3-10 条:拆的目的是让每个 server 都清爽,不是把臃肿平摊。如果拆完某个 server 还是 15 个工具,继续往下切。

好处很实在:每个 server 意图清晰,Agent 选工具更准;你还能按需挂载——这次任务只用得上订单,就只挂 orders-server,上下文更省。这跟"一个巨无霸 server 塞四十个工具全程常驻"是两种体验。


避坑表

后果 怎么破
一个 server 塞几十个工具 Agent 选工具准确率下降、token 爆 控制在 3-10 个,超了先合并再拆 server
出错抛裸异常/返回乱字符串 Agent 读不懂、不知该不该重试 返回结构化错误:error 码 + message + retryable
每个工具自己校鉴权 重复、易漏、改一片,漏一个就是洞 鉴权收口到传输层,工具函数信任已知身份
工具名含糊(process/handle) Agent 猜不出用途、用错或不用 用"动词+名词",名字自解释
description 只写一句空话 Agent 不知何时调、传错参 写清"何时用+输入+输出",参数标格式
同 server 内近义词工具并存 Agent 分不清该用哪个 合并或明确区分用途,避免 get/fetch 打架

动手挑战

  1. 翻出你写的(或接的)一个 server,数它暴露了几个工具。超过 10 个,就动手做一次"合并 + 拆 server"的设计:哪些能合成一个带参数的工具?剩下的按领域能拆成几个 server?
  2. 挑一个工具,把它的出错返回从"裸抛异常 / 返回纯文本"改成结构化错误(带 error 码、message、retryable)。然后故意触发"参数缺失"和"限流"两种错,观察 Agent 拿到结构化错误后的不同反应。
  3. 给你某个工具重写 description:原来一句话,改成"何时用 + 输入啥 + 输出啥 + 参数格式"。在 Agent 上对比改前改后,它选这个工具、传对参数的成功率有没有变化。

小结 · 你现在掌握了什么

  • 你懂了工具数控制在 3-10 个:太多 Agent 选不对、token 还爆,超了就合并或按领域拆成多个 server。
  • 你会返回结构化错误(error 码 + message + retryable),让 Agent 能分类、能据此重试,而不是对着裸异常干瞪眼。
  • 你知道鉴权要收口到传输层:连接时身份已确定,工具函数信任已知身份、专心干业务,不在每个函数里重复校验。
  • 你明白命名和描述决定 Agent 用不用得对:动词+名词的自解释命名,加"何时用+输入+输出"的清晰描述。
  • 你有了一份设计 checklist 和"工具太多怎么拆 server"的方法。

下一步:会接、会写、会设计 server 之后,最高阶的打法是把 MCP 和 Skills 叠起来用——MCP 在下给连接、Skill 在上做编排。看本级最后一节「Skills + MCP 分层组合」;想看整条路就对照 三支柱路线图,或回 AI Agent 智能体阶梯

👉 看看 AI 数字员工落地指南,或了解 数字员工搭建实战课。需要为企业落地方案,欢迎找我们聊 企业服务

📄 来源 / 自校链接

本文为学习整理,关键步骤与代码请结合下列官方来源验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为学习与落地整理,AI 工具与平台更新较快,关键步骤请结合官方最新资料验证。见免责声明