붙는다 — 전송은 설정이 아니라 실행할 명령이다

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에서 열기 →