一个工具 — 说明文比代码更要紧

5 分钟
目标MCP 服务端课程第二篇。注册一个工具。它由名字、说明、输入模式、处理器四块组成,其中三块不是我们读,而是调用方读。所以把说明文写好,比把处理器写好更值钱。

第一篇的服务端什么都干不了。给它接一个工具。

四个部件

server.addTool(
  name: 'desk.count',
  description: 'How many people are waiting at the desk right now',
  inputSchema: const {'type': 'object', 'properties': {}},
  handler: (args) async =>
      CallToolResult(content: [TextContent(text: '{"waiting":3}')]),
);

再声明把这项能力打开。

capabilities: ServerCapabilities(tools: ToolsCapability(listChanged: true))

有三块不是我们读的

四块里只有 handler 是我们的代码在读。其余三块全都由调用方来读。

问一句 tools/list,出去的是这个。

{
 "tools": [
  {
   "name": "desk.count",
   "description": "How many people are waiting at the desk right now",
   "inputSchema": {
    "type": "object",
    "properties": {}
   }
  }
 ]
}

人写的客户端会照这份清单画按钮。模型则会读这份清单,决定该调哪个工具。 而这个判断的依据,就是 description 那一行。

所以那一行不是注释。它是会跑的代码。

命名

用 desk.count 这样带点的名字。前面是关于什么,后面是做什么。

desk.count      数有多少人在等
desk.admit      从队列里放人进来
camera.settings 读摄像头的设置

工具只有三个时看着无所谓。到二十个,没规矩这件事就露出来了。 而且名字一旦发出去就改不了——它已经嵌在调用方的代码里。

说明文写不好会发生什么

假设两个工具是这样:

desk.count   「计数」
desk.admit   「放行」

没写数的是什么、放的是谁。模型会随便挑一个。而且 挑错也不报错——工具照常执行,只是答案不着边际。

写成这样,它就挑得出来:

desk.count   "How many people are waiting at the desk right now"
desk.admit   "Admit a number of people from the queue"

设备那篇之所以在点检表工具的说明文里写上 「这些步骤由工厂工程师制定,不得改写」,也是同一个理由。那句话是唯一能传达给模型的指令。

校验

第 2 级的检查只有一行。

ask step2 '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' > captures/s2.txt
grep -q 'desk.count' captures/s2.txt || die "step2: tool not listed"

注册了和出现在清单里,是两件事。调了 addTool 却没打开 capability,清单就是空的。

自己跑一遍

dart run bin/step2.dart
bash verify.sh

可以带走的

  1. addTool 的四个部件 — 其中三块由外面来读
  2. 带点的命名 — 三个的时候不定,二十个的时候就改不动了
  3. 说明文=接口 — 模型挑工具的依据

下一篇

接收参数。以及参数不对时怎么拒绝。

运行示例

makemind-academy/course_server/
git clone https://github.com/makemind-academy/course_server
cd course_server
dart pub get
dart run bin/step2.dart
在 GitHub 上打开 →