무엇을 할 수 있나 — 목록을 적어 두면 그 서버 전용이 된다
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