🔌 第9章 基于MCPServer的生活类智能体应用
📄 一句话概括

一个工具、两个参数、一问一答。它简单到没有可拆的地方,所以适合当第一个。 查不到的时候不要报错,回一个「查不到」——这类查询工具,查不到是正常结果。 「每日」这两个字跟协议无关,是定时任务的事。

📝 本章和前面几章的关系

上一节三个 Server,这一节一个工具。两个都叫智能体,复杂度差十倍。 教材把它放在第三个,其实它最该放第一个——先跑通协议,再谈编排。 所以这一节短,但有两个细节值得记住:查不到怎么回,以及「每日」跟 MCP 无关。

一、为什么拿它当第一个跑通

一个工具、两个参数(城市、日期),一次调用一句话。 没有串联、没有写库、没有跨 Server。它能验证的只有一件事:协议通不通。 而这件事恰恰是最该先验证的。工具多了以后报错,你分不清是协议的问题还是编排的问题。

所以顺序应该是:单工具跑通 → 多工具串联 → 加定时 → 加推送。每加一样只改一处。 反过来做的人,最后会在一堆可能性里找 bug。

二、一个工具,返回四个字段

演示里 weather.today 返回:天况、最低、最高、风。 四个字段不是越多越好。天况和温度是必须的,风和湿度是加分项。 返回字段的多少,直接决定模型能说多细。只给「小雨」,它只能说「今天下雨」; 给了温度和风,它才能说「穿件薄外套」。

但字段越多,工具越长,占的上下文越多。 这就是个取舍,没有标准答案。 一个实用的判据:这个字段会不会出现在回答里。 不会就不返回。

三、查不到的时候,别报错

演示里传一个表里没有的城市,工具没有报错,回了一个「查不到」。 这是有意设计的。 工具报错会让模型停下来问用户; 回一个「查不到」,模型可以接着答「苏州的数据我这里没有,换个城市试试」。 判据很简单:这类查询工具,查不到算正常结果,参数错才算错误。

把这两件事混在一起,是很多工具的通病:城市不存在也回 -32602,模型就会以为是自己参数填错了, 反复重试。 错误码要留给「调用方式不对」,业务上的空结果用返回值表达。

四、「每日」是另一件事

加上「每日」两个字,就多出三样东西:定时、要查的城市表、推给谁。 这三样没有一样归 MCP 管。 定时是宿主或系统的任务调度,城市表是提示词里的内容,推送是宿主配的渠道。 所以这类智能体的工程量:工具二十行、提示词十行、定时和推送各三十行。MCP 只占其中一小块。

想清楚这一点,就不会把「我的智能体每天不跑」怪到 MCP 头上。 教材里这一步是在平台上配的定时(本沙箱不执行)。 判据是:到点了宿主有没有触发,触发了有没有调用工具——两件事分开查。

五、动手 Demo:每日天气:最简的一问一答

一个工具、四个字段,问一句答一句。 再传一个不存在的城市,看它怎么回的——这一步是本节的要点。 最后把「每日」要的三样东西拆开,看哪一样归 MCP 管。

s = Server('weather-mcp')
s.add(Tool('weather.today', '查某城市今天的天气:天况、温度、风',
           schema({'city': prop('string', '城市名,如 杭州')}, ['city']),
           lambda a: {'city': a['city'],
                       'text': CITY[a['city']][0][1],
                       'low': CITY[a['city']][0][2],
                       'high': CITY[a['city']][0][3]}))

print(call(s, 'weather.today', {'city': '杭州'}))
print(call(s, 'weather.today', {'city': '苏州'}))   # 不报错,回查不到

实跑输出:

--------------------------------------------------------------------
每日天气:最简的一问一答
--------------------------------------------------------------------


  ① 为什么拿它当第一个
  --------------------------------------------------------------------

| 项 | 这个案例 | 别的案例 |
|---|---|---|
| 工具数 | 1 个 | 三到六个 |
| 参数 | 城市、日期 | 一串坐标或 id |
| 要不要串联 | 不要,一问一答 | 要,参数来自上一步 |
| 要不要写库 | 不要 | 多数要 |

  它简单到没有可拆的地方。
  【第一次跑通 MCP 就该从这种形状开始】——先确认「协议能通」,再谈「智能」。
  跑通之后再往上加:加定时、加推送、加多城市。每加一样只改一处。

  ② 一个工具,两个参数
  --------------------------------------------------------------------
问一句:
  用户:今天杭州天气怎么样
  模型:weather.today(city=杭州)
  工具:多云,19 到 27 度,东风 2 级
  模型:今天杭州多云,19 到 27 度,东风 2 级,出门穿件薄外套就行。
  一次调用、一句话。【这里没有任何「智能」,是提示词在管说话的风格。】

  ③ 查不到的时候
  --------------------------------------------------------------------
传一个表里没有的城市:
  {"city": "苏州", "text": "查不到", "low": 0, "high": 0, "wind": ""}
  它没有报错,回了一个「查不到」。【这是有意设计的。】
  工具报错会让模型停下来问用户;
  回一个「查不到」,模型可以接着答「苏州的数据我这里没有,换个城市试试」。后者体验好得多。
  判据是:【这类查询工具,查不到算是正常结果,不算错误。】 参数错才算错。

  ④ 加定时,就变成「每日」
  --------------------------------------------------------------------

| 要做的事 | 在哪一层 | MCP 管不管 |
|---|---|---|
| 每天八点跑一次 | 宿主或系统的定时任务 | 不管 |
| 查哪几个城市 | 提示词里写死一张表 | 不管 |
| 查到之后推给谁 | 宿主的通知渠道 | 不管 |
| 怎么查 | 工具 weather.today | 管 |

  四行里 MCP 只管最后一行。【「每日」这两个字跟协议一点关系都没有】,是定时任务加推送渠道的事。
  所以这类智能体的工程量:工具二十行、提示词十行、定时和推送各三十行。【MCP 只占其中一小块。】

  ⑤ 往上加的三条路
  --------------------------------------------------------------------
加城市:提示词里那张表从三行变十行,工具不动。
加内容:再挂一个「空气质量」工具,还是同一个 Server。
加推送:宿主那边配一个渠道,工具不动。
  三条路都只改一处。
  【这就是「工具与话术分开」的好处】:换推送渠道不用碰工具,换数据源不用改提示词。

①第一张表四行,最后一行「要不要写库」是不要。四个「不要」说明它简单到没有可拆的地方。 ②第二段那个工具的返回只有四个字段。判据是「这个字段会不会出现在回答里」。 ③第三段那次「查不到」是关键:它不是错误,是正常结果。模型可以接着说下一句。 ④第四段那张四行的表,前三行都不归 MCP 管。「每日」这两个字跟协议无关。 ⑤最后那句比例值得记:工具二十行、提示词十行、定时和推送各三十行。MCP 只是一小块。

30 秒实验

  1. 给工具加一个「空气质量」字段,看模型的回答能多说什么。
  2. 把「查不到」改成抛异常,再跑一遍,看回答变成什么样。
  3. 列出你要每天查的三个城市。想想这张表该写在哪一层。

常见误区

  1. 「智能体越复杂越厉害。」这个案例一个工具一个参数,照样是智能体。复杂度不是标准。
  2. 「查不到就报错最严谨。」报错会让模型停下来问用户。查询类工具,空结果是正常返回。
  3. 「每天不跑是 MCP 的问题。」定时不在协议里。先查宿主有没有触发。
自测三问(点开对答案) 1. 为什么这个案例适合当第一个跑通的? —— 一个工具两个参数,没有串联和写库。它只能验证协议通不通,而这正是该先验证的。 2. 查不到的时候,该报错还是该回空结果? —— 回空结果。查询类工具查不到是正常结果;错误码留给「调用方式不对」。 3. 「每日」要的三样东西,哪一样归 MCP 管? —— 只有「怎么查」这一样。定时、城市表、推送渠道都在 MCP 之外。

从一问一答往前走一步:加一个「附近」的要求,看坐标这个参数怎么把一句话变成四个数。

相关笔记