接住画面 — 客户端里一块画面都没有

5 分钟
目标MCP 客户端课程第四篇。从服务端读回画面定义,交给运行时。这个文件里没有一行是在描述柜台长什么样——而且这一点会被检查。

到第三篇为止,客户端知道该显示什么,是 因为我们写在这里了。这次不写。

三行

final read = await client.readResource('ui://desk');
final text = read.contents.first.text!;
final screen = jsonDecode(text) as Map<String, dynamic>;
ui://desk — 546 B, 6 lines, type "page", title "Desk"
nothing about this screen is written in the client

在 Flutter 宿主里,这之后就交给 MCPUIRuntime.initialize(screen)。课程样例保持纯 Dart,好让任何人都能跑,只确认 它到了,而且它是一块画面。

缺的是什么

这个文件里没有的东西:

  • 控件树
  • 颜色、字号
  • 按钮文案
  • 哪个按钮调哪个工具

全都在服务端给的那份文档里。客户端是 把那份文档接过来交给运行时的管道。

所以服务端改了画面,客户端什么都不用做。就像服务端第四篇把 fontSize 从 88 改成 120 时没有重启服务端一样,这一侧也不重新构建。

546 字节

一块画面 546 字节。六行。

这个数字看着小,正是要点。搬运画面的成本小,搬运就成了默认。 如果每次都要发一版应用,那画面就该在应用里;如果只是取一份文档,那它就该在外面。

校验 —— 画面在客户端里就失败

run step4 > captures/s4.txt || die "step4: failed"
grep -q 'type "page"' captures/s4.txt || die "what arrived is not a screen"
grep -q 'fontSize' bin/step4.dart && die "step4: the screen is inside the client"

bin/step4.dart 里出现一次 fontSize 就失败。

服务端那边也有同一条检查——服务端第四篇在服务端代码里有画面时会失败。两侧都不持有画面,这个结构才成立。只要有一侧拿着,它就只是个硬编码的应用。

自己跑一遍

dart run bin/step4.dart
bash verify.sh

改一下 ../course-server/ui/desk.json 再跑,打印出来的尺寸就变了。客户端没动过。

可以带走的

  1. readResource → jsonDecode → 运行时 三行
  2. 客户端里没有画面 — 控件、颜色、文案都没有
  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/step4.dart
在 GitHub 上打开 →