何ができるのか — 一覧を書き留めればそのサーバー専用になる
5 分目標MCP クライアント講座 2 編目。道具とリソースの一覧を問う。二行で終わるのだが、この二行があるかどうかが「そのサーバー専用のクライアント」と「どのサーバーの前にも立てるクライアント」を分ける。
1 編目で繋がった。今度は問う。
二行
final tools = await client.listTools();
stdout.writeln('tools: ${tools.map((t) => t.name).join(", ")}');
for (final t in tools) {
stdout.writeln(' ${t.name} — ${t.description}');
}
final resources = await client.listResources();
stdout.writeln('resources: ${resources.map((r) => r.uri).join(", ")}');tools: desk.admit
desk.admit — Admit a number of people from the queue
resources: ui://desk, desk://waiting書き留めれば専用になる
こう書くこともできる。
// このサーバーは desk.admit を持っている
await client.callTool('desk.admit', {'count': 1});動く。そして その瞬間このクライアントはそのサーバー専用になる。 道具名がひとつでもコードに埋まれば、別のサーバーの前には立てない。
問って受け取れば違う。一覧が実行時に来るので、クライアントは 自分が何に繋がったかを知らなくていい。 ボードに繋げば led.set が来て、デスクに繋げば desk.admit が来る。同じコードだ。
説明文がここで使われる
listTools() は名前だけをくれるのではない。説明文と入力スキーマが一緒に来る。
desk.admit — Admit a number of people from the queue人が使うクライアントならこれでボタンのラベルとツールチップを作る。モデルならこれを読んで何を呼ぶかを決める。サーバー 2 編目で「説明文は注釈ではなく動くコードだ」と言ったものが、ここで消費される。
リソースは uri で来る
resources: ui://desk, desk://waitingui:// は画面で desk:// は値だ。規約が定めたのではなく サーバーが決めた命名規則 である。クライアントはそれを知る必要が無く、4 編目で画面を読むときは一覧にある uri をそのまま使う。
検証 — ハードコードすれば失敗
この編の主張は「クライアントは聞かされるのではなく問う」だ。だから検査がクライアントのコードを見る。
grep -q 'tools: .*desk.admit' captures/s2.txt || die "tools not discovered"
grep -q 'resources: .*ui://desk' captures/s2.txt || die "resources not discovered"
# The claim is that the client was not told; it asked.
grep -q "'desk.admit'" bin/step2.dart && die "the tool name is hardcoded in the client"bin/step2.dart に道具名が一度でも出れば失敗する。
自分で動かす
dart run bin/step2.dart
bash verify.sh持ち帰るもの
listTools()/listResources()— 表面は実行時に来る- 名前を書かない — 書いた瞬間、専用クライアントになる
- ハードコード禁止の検査 — この主張が静かに壊れるのを防ぐ
次の編
道具を呼ぶ。そしてサーバーが拒んだとき、それが例外ではないということ。
サンプルを実行する
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/step2.dart