设计LLM真正能够正确使用的MCP工具
高级12 分钟阅读企业AI

设计LLM真正能够正确使用的MCP工具

我们看到的大多数MCP工具在技术上正确,在实践中却毫无用处。LLM会忽略、误用,或以无益的方式调用它们。本文介绍让LLM自然采用工具的设计原则,并给出常见失败及其修复示例。

您应该能够做到的事情

面向LLM的工具设计更接近UX设计,而非API设计。LLM就是你的用户,工具描述就是界面。好工具经常得到正确使用;坏工具则会被忽略、误用或以糟糕的方式串联。本文介绍真正重要的模式。

AI Expert Team发布日期: 2026年5月15日
仅在此浏览器中保存。
本文内容

你已经构建了MCP服务器,工具也能正常工作。接入LLM智能体后观察其运行,却发现:它会忽略工具、使用奇怪的参数调用工具、分不清应该使用哪个工具,还会以古怪的顺序将工具串联起来。

这就是“存在的工具”与“LLM能够正确使用的工具”之间的差距。大多数MCP服务器的投入都浪费在这里。企业构建了强大的能力,将其公开为工具,然后眼看着LLM无法有效使用。

一个有用的视角转换是:将工具设计视为UX设计,而LLM就是你的用户。工具描述是UI,Schema是表单,错误消息是反馈。把这些做好,LLM就能高效工作;做不好,再复杂的后端对智能体而言也等于不存在。

本文通过具体的有效与无效示例来介绍这些原则。

原则1:工具名称传达意图

工具名称是LLM最先看到的内容。它应以动作的形式说明工具做什么。

不佳:

  • customers(名词,没有动作)
  • process_customer(含义模糊)
  • do_x(毫无意义)

更好:

  • search_customers(动作明确)
  • get_customer_by_id(操作具体)
  • update_customer_email(变更具体)

为什么重要: LLM会扫描工具列表以寻找相关工具。描述性名称可以帮助它迅速找到正确工具。名称模糊会迫使它仔细阅读描述(而它有时不会这样做)。

一种实用模式是使用标准动词前缀。

  • 读取操作使用 list_*search_*get_*
  • 写入操作使用 create_*update_*delete_*
  • 计算操作使用 analyze_*summarize_*

服务器内保持一致,有助于LLM建立心智模型。

原则2:描述就是提示词

工具描述是服务器中最重要的文本。LLM据此决定是否以及如何使用工具。

不佳的描述:

search_customers: 搜索客户数据库。

更好的描述:

search_customers: 按姓名、电子邮件地址或公司查找客户。最多返回10位匹配客户及其基本信息。当你需要识别用户所指的客户时使用。要按ID精确查询,请改用get_customer_by_id。

更好的版本做到了:

  • 描述输入(“按姓名、电子邮件地址或公司”)。
  • 描述输出(“最多返回10位匹配客户及其基本信息”)。
  • 说明何时使用(“当你需要识别用户所指的客户时”)。
  • 说明何时不应使用(“要按ID精确查询,请改用get_customer_by_id”)。

“何时不应使用”这一部分至关重要。缺少它时,LLM可能会在更适合 get_customer_by_id 的情况下调用 search_customers

原则3:参数描述很重要

每个参数都需要描述,不能只依赖参数名。

不佳:

{
  customer_id: string,
  fields: string[]
}

更好:

{
  customer_id: string,  // "客户的唯一标识符。从search_customers的结果或用户明确提供的输入中获取。"
  fields: string[]      // "要返回的具体字段。可选值:name、email、phone、tier、created_at、last_active。如未指定,则返回name和email。"
}

这些描述:

  • 告诉LLM如何获取值。
  • 在适用时指定允许的值。
  • 说明默认值。

原则4:错误引导恢复

工具发生错误时,错误消息会引导LLM的下一步操作。模糊的错误会让智能体陷入困惑。

不佳的错误:

{ "error": "输入无效" }

更好的错误:

{
  "error": "validation_error",
  "message": "电子邮件地址 '...' 的格式无效。格式应类似 'name@example.com'。",
  "field": "email",
  "suggestion": "请用户提供有效的电子邮件地址。"
}

现在LLM知道:

  • 哪里出错了(email字段的验证错误)。
  • 如何修复(使用有效的电子邮件格式)。
  • 下一步做什么(询问用户)。

比较智能体面对这两种错误时的行为。第一种情况下,它可能会使用同一参数重试(浪费调用)、直接放弃(糟糕的UX),或凭空编造有效输入。第二种则会带来顺畅的用户交互。

原则5:输出决定下一步操作

工具的输出决定LLM接下来做什么。输出设计会影响智能体行为。

不佳的搜索输出:

[
  {"id": "c1", "n": "John", "e": "john@..."},
  {"id": "c2", "n": "Jane", "e": "jane@..."}
]

更好的输出:

{
  "customers": [
    {"id": "c1", "name": "John Smith", "email": "john@example.com", "tier": "pro"},
    {"id": "c2", "name": "Jane Doe", "email": "jane@example.com", "tier": "free"}
  ],
  "total_found": 2,
  "summary": "找到2位与 'john' 匹配的客户。请注意,其中一位名叫 'Jane Doe',但电子邮件地址中含有 'john'。"
}

更好的输出:

  • 使用可读的字段名。
  • 包含元上下文(total_found)。
  • 包含自然语言 summary,帮助LLM组织接下来的回复。

摘要字段非常强大——它就像顺带向LLM提示应该如何解读结果。

原则6:一个工具只做一件事

同时做多件事的工具会让LLM困惑。LLM不仅要决定是否使用工具,还要决定使用哪种模式。

容易混淆:

manage_customer:
  - mode: "search" | "get" | "update" | "delete"
  - params: 取决于mode

LLM必须选择模式,而且经常选错。更糟的是,参数Schema会因为模式不同而变得复杂。

更好:拆分工具。

search_customers: 按姓名/电子邮件地址/公司搜索
get_customer: 按ID获取详细信息
update_customer: 更新特定字段
delete_customer: 归档客户

每个工具都没有歧义。LLM根据意图选择工具,Schema也很简单。

这意味着工具数量会增加,但每个工具都更清晰。LLM处理10个清晰工具的效果,优于处理3个多模式工具。

原则7:约束输入

应尽可能限制输入选项。枚举和验证可以防止LLM产生幻觉。

宽松:

{
  status: string  // 可以是任何内容
}

受约束:

{
  status: "active" | "trial" | "churned" | "suspended"
}

该约束在Schema层执行(约束生成可防止LLM产生无效值)。

这同样适用于操作、严重程度、类型等枚举——凡是有效值集合已知的内容,都应加以约束。

日期应使用ISO 8601格式,并在描述中明确说明(“ISO 8601格式的日期,例如2026-05-15”)。如果不说明,LLM会生成各种格式的日期。

原则8:默认值减少幻觉

参数有合理默认值时,应将其设为可选,并由服务器应用默认值。

不佳:

{
  query: string,
  limit: number,  // LLM必须提供某个值
  include_archived: boolean,
  sort_by: string
}

LLM必须为所有参数选值,而这些值可能不正确。

更好:

{
  query: string,
  limit: number = 10,            // 合理默认值
  include_archived: boolean = false,  // 安全默认值
  sort_by: "relevance" | "name" | "created_at" = "relevance"  // 最常用
}

LLM只需指定与具体查询有关的参数。参数越少,产生困惑的空间越小。

在描述中注明默认值:“Limit:要返回的结果数。默认为10,最大为50。”

原则9:可组合性很重要

工具应能组合成LLM可以构建的工作流。粒度合适,复杂任务就会变得简单。

考虑一个任务:“告诉我排名前3的客户有哪些未关闭问题。”

不佳的工具集:

get_customer_summary(customer_id): 一次返回客户 + 工单 + 活动

LLM无法轻松完成“排名前3”筛选——这个工具一次返回一位客户的所有信息。要完成任务,LLM必须先知道哪些是排名靠前的客户,然后调用此工具3次。

更好的工具集:

list_customers(sort_by="value", limit=N): 返回包含优先级信息的客户摘要
list_tickets(customer_id, status): 返回某位客户的工单

LLM可以自然地组合:先列出排名靠前的客户,再逐一列出未关闭的工单。

原则是:考虑多工具工作流。组合良好的工具容易使用;无法组合的工具往往不好用。

原则10:明确传达幂等性

对于写入工具,应在描述中说明幂等性要求:

create_invoice: 为客户创建新发票。
重要:传入idempotency_key(由你生成的UUID)。如果重试此操作,请使用同一个UUID,以防生成重复发票。

参数:
- amount: ...
- customer_id: ...
- idempotency_key: 用于防止重试时重复创建的UUID。每个逻辑操作只生成一次。

这样LLM就知道要生成UUID,并在重试时继续使用同一个UUID。

如果没有这些指引,LLM可能完全跳过该键(没有幂等性),也可能每次重试都生成新的UUID(违背幂等性的目的)。

原则11:说明前置条件和后置条件

对于有前置条件或重要副作用的工具,应明确说明:

delete_customer: 归档客户记录。30天内可以恢复;30天后,数据将被永久删除。

前置条件:
- 客户不得有有效订阅。
- 客户不得有未关闭工单。

如果不满足前置条件,此工具会返回错误,说明需要先解决什么。

副作用:
- 客户的所有联系人也会一并归档。
- 客户将从活跃报表中移除。
- 将创建一条审计日志记录。

LLM现在知道调用前需要检查什么,也知道调用后会发生什么。它可以正确规划多步骤工作流(“先关闭工单,再删除”)。

原则12:拿不准时就提供示例

对于复杂工具,在描述中提供示例会有所帮助:

analyze_funnel: 根据事件数据分析转化漏斗。

参数:
- start_date: ISO 8601日期
- end_date: ISO 8601日期
- steps: 步骤定义数组,每项为 {event_name: string, filters?: object}

示例:
{
  "start_date": "2026-01-01",
  "end_date": "2026-01-31",
  "steps": [
    {"event_name": "signup"},
    {"event_name": "first_login"},
    {"event_name": "first_action", "filters": {"action_type": "create_project"}},
    {"event_name": "subscription_started"}
  ]
}

示例比单独的Schema更能帮助LLM学会结构。

原则13:不要暴露内部实现

LLM不需要了解数据库结构或内部ID。应公开清晰的概念模型。

不佳:

get_user_by_pk(pk: number)

LLM必须知道要使用“主键”——这是数据库概念。

更好:

get_user(user_id: string)

隐藏数据库概念。LLM使用的是具有实际含义的user_id。

同理,不要暴露弃用字段、内部标志、调试参数,或任何与实现有关而非面向用户概念的内容。

原则14:避免魔法字符串

有些工具要求使用看起来像命令或代码的字符串,很容易出错。

不佳:

modify_record(record_id: string, change_string: string)
// change_string的形式类似 "field1=value1;field2=value2"

LLM必须按照特定字符串格式编码变更,很容易犯错。

更好:

update_record(record_id: string, updates: { field1?: any; field2?: any; ... })

将更新内容构造成对象,LLM可以直接使用任意字段。

原则15:使用真实LLM测试

工具描述对人类而言可能很易读,却仍可能让LLM困惑。只有测试才能确定。

一种实用工作流如下:

  1. 构建工具。
  2. 让LLM智能体仅使用你的工具尝试完成几项现实任务。
  3. 观察失败。
  4. 根据失败情况调整描述。
  5. 重复。

你会发现以下模式:

  • LLM使用了错误工具 → 工具名称或描述不清晰。
  • LLM传入错误参数值 → 参数描述或Schema需要改进。
  • LLM遇到错误后放弃 → 错误消息需要改进。
  • LLM没有尝试本可提供帮助的工具 → 工具不够醒目或命名不佳。

每个问题都指向一项具体修复。

诊断:工具设计不佳的迹象

以下模式表明工具设计存在问题:

LLM经常使用错误工具。 你会看到它在本应调用 get_customer_by_id 时调用 search_customers。修复:明确说明每个工具分别适用于什么情况。

LLM为完成一件事调用许多工具。 它串联5次工具调用,才完成本应由1次调用完成的任务。修复:可能需要更高层级的复合工具,或者当前粒度过细。

LLM遇到错误后放弃。 它尝试一次,收到错误后就告诉用户无法提供帮助。修复:改进错误消息并给出后续步骤。

LLM凭空编造参数值。 它虚构user_id、日期或ID。修复:明确如何获得有效值;增加约束;添加能够捕获并解释问题的错误处理。

LLM重复相同的失败调用。 同一个错误反复发生。修复:错误消息没有具体告诉LLM问题所在。

LLM不使用某个强大的工具。 你构建了优秀工具,LLM却从不调用。修复:改进发现能力(名称更清晰、描述更完善,并加入“在……时使用此工具”的指引)。

工具分类体系

一项有用的练习是将工具整理成分类体系。

读取工具(安全、幂等):
- search_customers
- get_customer_by_id
- list_tickets
- list_orders

计算工具(不改变状态):
- summarize_account_activity
- analyze_funnel
- calculate_lifetime_value

写入工具(改变状态,需要幂等性):
- create_customer
- update_customer_email
- create_ticket
- send_email

破坏性工具(需要谨慎授权):
- delete_customer
- cancel_subscription
- archive_record

分类体系可以帮助你:

  • 应用适当的防护措施(幂等性、破坏性操作确认)。
  • 在面向LLM的系统提示词中说明各个类别。
  • 发现缺失的工具(如果某个类别为空,是否需要添加?)。

可以在系统提示词中加入:

可用工具类别:
- READ工具(可安全调用):search_customers、get_customer_by_id、...
- COMPUTE工具(无副作用):summarize_account_activity、...
- WRITE工具(有副作用,需包含idempotency_key):create_customer、...
- DESTRUCTIVE工具(需要人工确认):delete_customer、...

调用WRITE或DESTRUCTIVE工具前,先向用户确认。

这会在工作流层面塑造LLM使用工具的方式,而不只影响单次调用。

常见改进示例

下面通过改进前后的示例具体说明这些原则:

示例1:搜索工具

改进前:

// 搜索文档
{
  name: "documents",
  description: "搜索文档",
  inputSchema: { query: "string" }
}

改进后:

{
  name: "search_documents",
  description: `搜索内部文档(知识库、Wiki页面、策略)。
  返回匹配文档及其标题、摘录和链接。当用户询问公司策略、流程或内部文档时使用。按语义相似度返回最多10个最相关的匹配项。`,
  inputSchema: {
    query: {
      type: "string",
      description: "搜索查询。请尽量具体。好的示例:'2026年远程办公政策'。不佳示例:'与工作有关的文档'。"
    },
    document_type: {
      type: "string",
      enum: ["policy", "procedure", "guide", "faq", "any"],
      default: "any",
      description: "筛选特定类型的文档。"
    },
    limit: {
      type: "number",
      default: 5,
      maximum: 10,
      description: "结果数量。"
    }
  }
}

示例2:操作工具

改进前:

{
  name: "send_email",
  description: "发送电子邮件",
  inputSchema: {
    to: "string",
    subject: "string",
    body: "string"
  }
}

改进后:

{
  name: "draft_email_to_customer",
  description: `根据近期互动起草一封发给客户的电子邮件。电子邮件将保存为草稿,供人工审核后发送——不会自动发送。用户必须在收件箱中批准草稿。

  在以下情况使用:
  - 你已经确定了一项需要跟进客户的操作。
  - 你有发送邮件的具体原因和内容。

  请勿在以下情况使用:
  - 发送营销或推广内容。
  - 用户没有明确提出请求。
  - 回复退款或取消请求(应改为升级给人工处理)。`,
  inputSchema: {
    customer_id: {
      type: "string",
      description: "来自search_customers或get_customer的客户ID。"
    },
    subject: {
      type: "string",
      description: "电子邮件主题,4–8个词,内容具体。避免使用 '跟进一下' 之类笼统的主题。"
    },
    body: {
      type: "string",
      description: "电子邮件正文,纯文本。3–5句话。应个性化、具体,不要有模板感。"
    },
    tone: {
      type: "string",
      enum: ["professional", "friendly", "apologetic", "urgent"],
      default: "professional",
      description: "电子邮件的语气。"
    },
    idempotency_key: {
      type: "string",
      description: "此草稿使用的UUID。重试时使用同一个UUID,以免产生重复草稿。"
    }
  }
}

“改进后”的版本能为LLM提供更有效的指引。它们看起来很啰嗦,但完全值得。

核心结论

面向LLM的工具设计是一门独立学科。这些原则并非凭直觉就能掌握;你需要把LLM当作用户,并据此设计界面。

真正重要的模式包括:

  • 使用动作动词命名。
  • 使用丰富描述说明做什么、何时使用以及何时不应使用。
  • 为每个参数提供描述、示例和约束。
  • 提供结构化、可操作的错误消息。
  • 让输出结构引导下一步操作。
  • 每个概念对应一个工具。
  • 设置合理默认值。
  • 使用可组合的粒度。
  • 明确说明幂等性。
  • 记录前置条件和后置条件。
  • 为复杂工具提供示例。
  • 隐藏内部实现。
  • 使用真实LLM测试。

大多数MCP服务器失败,不是因为协议太难,而是因为工具设计时没有考虑LLM。工具设计正确,服务器才会有效;设计错误,再复杂的后端也会白白浪费。

把LLM当作用户,并据此设计。这样的投入会通过工具实际使用效果得到多倍回报。

继续阅读

通过下一篇文章继续沿着相同的学习路径进行学习。