把画面做成资源 — 不是代码,是服务端交出来的文档

5 分钟
目标MCP 服务端课程第四篇。服务端把画面当成一个文件拿着,再交出去。改了文件,下一次连接就变了,不用重编译也不用重装——前提是每次请求都去读它。

到目前为止服务端答的是值。现在它答 一块画面。

画面就是一个文件

{ "type": "page", "title": "Desk",
  "content": { "type": "center",
    "child": { "type": "linear", "direction": "vertical", "spacing": 14, "alignment": "center", "children": [
      { "type": "text", "text": "WAITING", "style": { "fontSize": 16, "letterSpacing": 4, "color": "#6b7280" } },
      { "type": "text", "text": "{{waiting}}", "style": { "fontSize": 88, "fontWeight": "bold", "color": "#111827" } },
      { "type": "button", "label": "Admit one", "onTap": { "type": "tool", "tool": "desk.admit", "params": { "count": 1 } } } ] } } }

六行。值落在 {{waiting}} 的位置,按钮调用第三篇做的那个工具。

服务端把它交出来

server.addResource(
  uri: 'ui://desk',
  name: 'Desk screen',
  description: 'The desk screen, served as a document',
  mimeType: 'application/json',
  handler: (uri, params) async => ReadResourceResult(contents: [
    ResourceContentInfo(
      uri: 'ui://desk',
      mimeType: 'application/json',
      text: File(_screenPath).readAsStringSync(),
    )
  ]),
);

ResourcesCapability 也要打开。

capabilities: ServerCapabilities(
  tools: ToolsCapability(listChanged: true),
  resources: ResourcesCapability(listChanged: true),
),

每次请求都读,这才是要点

readAsStringSync() 在处理器里面。启动时读一次放进变量当然更快,但这里没那么做。

一旦拿住,每改一次画面就得重启服务端。 那一刻,这一篇主张的事——改了文件就是另一个应用——就成了假的。

/// Read fresh on every request, deliberately. A screen cached at boot is a
/// screen you have to restart the server to change, and then the claim above
/// stops being true.

等性能真的成为问题时可以缓存,但 必须靠检测文件变化来失效。只是拿住,等于把这项能力丢掉。

有什么不同

画面一旦出了代码,就是这样:

在代码里时 作为文档交出来时
改字号 构建 → 发布 → 安装 文件里一行
客户端种类 每种各实现一遍 都接同一份文档来画
加一块画面 改应用 多一条资源

校验 —— 画面在代码里就失败

这一篇最容易变成谎话的地方只有一个:把画面放在代码里,文里却写「服务端交出来的」。 光看响应分不出来。

ask step4 '{"jsonrpc":"2.0","id":2,"method":"resources/read","params":{"uri":"ui://desk"}}'
grep -q 'ui://desk' captures/s4.txt || die "resource not served"
grep -q 'page'      captures/s4.txt || die "served document is not a screen"

# The claim is that the screen is not in the code.
grep -q 'fontSize' bin/step4.dart && die "step4: the screen is inside the code"

服务端代码里出现一次 fontSize 就失败。

step4  ui://desk served from ui/desk.json (6 lines), not from the code

自己跑一遍

dart run bin/step4.dart
bash verify.sh

把 ui/desk.json 里的 fontSize 从 88 改成 120 再问一次,回来的就是改过的文档。服务端一直没关。

可以带走的

  1. addResource + capability — 两个都要,否则不出现在清单里
  2. 在处理器里面读 — 一缓存就不再是免安装
  3. 禁止画面进代码的检查 — 这个主张唯一可能作假的地方

下一篇

现在的等待人数是个变量。进程一死就没了。要让它留下来。

运行示例

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