무엇을 할 수 있나 — 목록을 적어 두면 그 서버 전용이 된다

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://waiting

ui:// 는 화면이고 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

가져갈 것

  1. listTools() / listResources() — 표면은 실행 시점에 온다
  2. 이름을 적지 않는다 — 적는 순간 전용 클라이언트가 된다
  3. 하드코딩 금지 검사 — 이 주장이 조용히 깨지는 걸 막는다

다음 편

도구를 부른다. 그리고 서버가 거절했을 때 그게 예외가 아니라는 것.

샘플 실행하기

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