繋ぐ — 伝送は設定ではなく、起動するコマンドだ

5 分
目標MCP クライアント講座の一編目。サーバーに繋ぐ。ここで見るべきは接続のコードではなく伝送の正体だ — 開くソケットではなく起動するコマンドであり、だから後で伝送を替える作業がコマンド一行を替える作業になる。

サーバー講座の六編でサーバーが立った。このトラックは反対側だ — そのサーバーに繋ぐコード。

繋ぐコードの全部

Future<Client> connect(String step) async {
  final r = await McpClient.createAndConnect(
    config: McpClient.simpleConfig(
        name: 'Course Client', version: '1.0.0', enableDebugLogging: false),
    transportConfig: TransportConfig.stdio(
      command: 'dart',
      arguments: ['run', 'bin/$step.dart'],
      workingDirectory: '../course-server',
    ),
  );
  return r.get();
}

config は我々が誰か、transportConfig はどう届くかだ。

伝送がコマンドだ

TransportConfig.stdio の引数を見てほしい。ホストもポートも無い。command と arguments だ。

クライアントがサーバーのプロセスを自分で立ち上げ、そのプロセスの stdin/stdout で話す。設定するソケットも、立ち上がるまで待つサービスも無い。

些細に見えて、後で値を持つ。伝送を替える作業が どのプログラムを起動するか に縮むからだ。

// シリアル機器へ
command: '../serial_bridge/serial_bridge',
arguments: ['/dev/cu.usbmodem1234', '115200'],

// ネットワーク機器へ
command: '../tcp_bridge/tcp_bridge',
arguments: ['mcp-esp32.local', '6270'],

クライアントのコードは一文字も変わらない。実機の編で STM32 と ESP32 に同じコードで繋げたのは、この構造のためだ。

繋がれば相手が分かる

stdout.writeln('connected to ${client.serverInfo?["name"]} '
    '${client.serverInfo?["version"]}');
connected to Course 1.0.0

initialize の往復で受け取ったものだ。まだ何も問うていないのに 相手が誰で何ができるか(capabilities) がすでに手にある。

.get() とは何か

createAndConnect が返すのは Client ではなく、成功/失敗を含む結果だ。.get() は成功なら取り出し、失敗なら投げる。

失敗を扱う場所ならこう分けて受ける。

r.fold(
  (client) => /* 繋がった */,
  (error)  => /* サーバーが立たない、パスが違う、権限が無い */,
);

講座のサンプルは繋がる前提なので .get() を使う。実際のアプリならここが最初の画面を分ける場所だ — 繋がったときと繋がらなかったときで、人に見せるものが違う。

検証

run step1 > captures/s1.txt || die "step1: did not connect"
grep -q 'connected to Course' captures/s1.txt || die "step1: no serverInfo"
step1  connected to Course 1.0.0

実際にサーバーを立てて繋ぐ。 モックではなく、サーバー講座で作ったあのサーバーだ。だから毎回、二つのトラックが噛み合うかを確かめていることになる。

自分で動かす

cd content/sample/course-client
dart pub get
dart run bin/step1.dart
bash verify.sh

持ち帰るもの

  1. createAndConnect + .get() — 繋ぐ最小の形
  2. 伝送=コマンド — ホスト・ポートではなく command・arguments
  3. serverInfo — 何も問わずとも相手が分かる

次の編

何ができるかを問う。そしてその一覧がなぜクライアントに書かれていてはいけないのか。

サンプルを実行する

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/step1.dart
GitHub で開く →