変わったとだけ知らせる — 値を載せてはいけない理由

5 分
目標MCP サーバー講座の最終編。画面が一秒ごとに問うのをやめさせる。サーバーが知らせるのだが、通知には uri だけを載せて値は載せない。そして購読していないクライアントには何も行かない。

5 編目まで、画面が最新の値を知るには問わねばならなかった。一秒ごとに問うか、人が更新するか。

知らせる

値を変えた後の一行だ。

waiting -= n;
_save(waiting);
// Say that it changed. Not what it changed to.
server.notifyResourceUpdated('desk://waiting');

購読可能なリソースをひとつ置き、capability に subscribe: true を入れる。

resources: ResourcesCapability(listChanged: true, subscribe: true),

ワイヤーに出るもの

{"jsonrpc":"2.0","method":"notifications/resources/updated","params":{"uri":"desk://waiting"}}

uri ひとつだけだ。 何人になったかは入っていない。

値を載せるとなぜいけないのか

通知に値を入れるのは当然に見える。一度で済むからだ。ところが 通知は順序を保証しない。

サーバー: waiting が 2 に → 通知 A 発送
サーバー: waiting が 1 に → 通知 B 発送
クライアント: B 到着 (1)  →  A 到着 (2)   ← 順序が逆転した
画面: 2

値が載っていれば 遅れて来た古い通知が新しい値を上書きする。 画面が黙って誤った数字を出し、誰も知らない。

「変わった」だけを送ればこの問題が消える。通知が何度来ようと、順序がどうであろうと、受けた側が読めば その時点の最新値 が来る。

厨房の画面の編で同じ結論に至った。あのときは設計として選び、ここではパッケージがそうとしか送らない — notifyResourceUpdated(uri) に値を載せる場所が無い。

購読しなければ何も行かない

作りながら引っかかったこと。notifyResourceUpdated を呼び、サーバーのログにも出るのに、ワイヤーには何も出なかった。

notified desk://waiting        ← サーバーは呼んだ
(クライアント側には何も無い)

購読していないクライアントには送らないから だ。仕様どおりである。

{"jsonrpc":"2.0","id":2,"method":"resources/subscribe","params":{"uri":"desk://waiting"}}

これを送ってから来る。サーバー側の欠陥と誤解しやすい場所だ — サーバーは正常で、受ける準備をしていなかった。

検証 — 値が載ったかをパースして見る

ask step6 \
  '{"jsonrpc":"2.0","id":2,"method":"resources/subscribe","params":{"uri":"desk://waiting"}}' \
  '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"desk.admit","arguments":{"count":1}}}'
params = json.loads(lines[0]).get('params', {})
extra = set(params) - {'uri'}
if extra:
    print('the notification carried', sorted(extra), '— it must carry only the uri')
    sys.exit(1)

最初は grep waiting で検査して誤検出になった。uri 自体が desk://waiting なので常に引っかかる。文字列で見てはいけない、パースしてキーを見ねばならない。

   notification params: {"uri": "desk://waiting"}
step6  notification carried the uri and no value

六編が終わった

step1  initialize -> serverInfo Course
step2  tools/list -> desk.count
step3  refused 0 and 99, admitted 1 -> waiting 2
step4  ui://desk served from ui/desk.json (6 lines), not from the code
step5  admitted 2, process ended, a new process still reads waiting 1
step6  notification carried the uri and no value

6 steps · each checked against its own claim

サーバーひとつが六段階で育ち、段階ごとに その編が主張すること が検査される。「動く」ではなく。

自分で動かす

cd content/sample/course-server
dart pub get
bash verify.sh            # 六段階すべて
dart run bin/step6.dart   # 完成形

持ち帰るもの

  1. notifyResourceUpdated(uri) — 値を載せる場所が無いことが設計だ
  2. まず購読 — 購読無しには何も行かない
  3. パースして検査 — 文字列検査は uri に引っかかって誤検出になる

次のトラック

ここまでがサーバーだ。次はクライアント側 — 繋ぎ、何ができるかを問い、画面を受け取って描くこと。

サンプルを実行する

makemind-academy/course_server/
git clone https://github.com/makemind-academy/course_server
cd course_server
dart pub get
dart run bin/step6.dart
GitHub で開く →