调用并接住 — 拒绝不是异常

5 分钟
目标MCP 客户端课程第三篇。调用工具并读回答案。结果不是一个值而是一份内容列表;而服务端拒绝时,它不会以 throw 的形式到来——所以要读,不是去捕获。

第二篇知道了有什么。现在去调。

结果不是一个值

final ok = await client.callTool('desk.admit', {'count': 1});

回来的不是数字,而是 一份内容列表。可能是文本,可能是图片,也可能有好几项。所以把取值这件事收在一处。

Map<String, dynamic> decode(CallToolResult r) {
  final first = r.content.first;
  if (first is! TextContent) {
    throw StateError('expected text content, got ${first.runtimeType}');
  }
  return jsonDecode(first.text) as Map<String, dynamic>;
}

去掉 is! TextContent 直接强转,等哪天服务端混进一张图片,它会在 完全不相干的地方 炸。在这里拦住,栈才读得懂。

admit 1 -> {waiting: 2}

拒绝不会以 throw 到来

调一下服务端第三篇里做的那个拒绝。

final refused = await client.callTool('desk.admit', {'count': 99});
final text = (refused.content.first as TextContent).text;
stdout.writeln('admit 99 -> isError=${refused.isError} "$text"');
admit 99 -> isError=true "only 2 waiting"

没有抛异常。 await 正常完成,结果上带着 isError: true,理由在文本里。

不知道这一点,就会写成:

try {
  await client.callTool('desk.admit', {'count': 99});
  // 走到这儿了,说明成功
} catch (e) {
  // 以为拒绝会掉到这里……其实不会
}

拒绝进不了 catch,于是 被当成成功处理。 屏上写着「放进 99 人」,而服务端一个人也没放。

为什么它不是异常

异常的意思是「这次调用没有成立」——连接断了,或者没有这个工具。

拒绝的意思是 调用成立了,服务端做了判断。 工具正常跑完,答案是「不行」。而这个答案带着理由——要用上那个理由,就必须以结果的形式接住它。

读到 only 2 waiting,就能改成 2 以内重发。当异常捕获掉,那句话就埋进栈里了。

校验

run step3 > captures/s3.txt || die "step3: failed (a refusal should not throw)"
grep -q 'admit 1 -> {waiting: 2}'   captures/s3.txt || die "the call did not go through"
grep -q 'admit 99 -> isError=true'  captures/s3.txt || die "the refusal was not surfaced"
grep -q 'only 2 waiting'            captures/s3.txt || die "the reason did not survive"

第一行很要紧。拒绝要是以异常到来,程序就会死,退出码不为 0 —— || die 会在那儿抓住它。

第三条检查(only 2 waiting)真的抓到了一个缺陷。服务端 4 到 6 级把第 3 级分开的两种拒绝又揉回了一个。只跑服务端的话,第 3 级检查会过,而后面几级不会看那条消息,于是永远发现不了。 是把两个样例咬在一起跑,才露出来的。

自己跑一遍

dart run bin/step3.dart
bash verify.sh

可以带走的

  1. 一处 decode — 内容类型的检查放在这里
  2. 去读 isError — 拒绝是一个结果
  3. 检查拒绝不会抛异常 — 退出码能确认

下一篇

接住画面。而客户端里一行画面都没有。

运行示例

makemind-academy/course_client/
git clone https://github.com/makemind-academy/course_client
cd course_client
dart pub get
(cd course-server && dart pub get)
dart run bin/step3.dart
在 GitHub 上打开 →