화면을 받는다 — 클라이언트가 화면을 하나도 안 갖고 있으면

5 분
목표MCP 클라이언트 강좌 4편. 서버에서 화면 정의를 읽어 런타임에 넘긴다. 이 파일에는 데스크가 어떻게 생겼는지에 대한 코드가 한 줄도 없다 — 그게 검사된다.

3편까지 클라이언트가 무엇을 보여줄지는 우리가 여기 적었기 때문에 알았다. 이번엔 안 적는다.

세 줄

final read = await client.readResource('ui://desk');
final text = read.contents.first.text!;
final screen = jsonDecode(text) as Map<String, dynamic>;
ui://desk — 546 B, 6 lines, type "page", title "Desk"
nothing about this screen is written in the client

Flutter 호스트라면 여기서 MCPUIRuntime.initialize(screen) 로 넘어간다. 강좌 샘플은 누구나 돌릴 수 있게 순수 Dart 로 뒀고, 도착했다는 것과 그게 화면이라는 것까지만 확인한다.

무엇이 없는가

이 파일에 없는 것들이다.

  • 위젯 트리
  • 색상·글꼴 크기
  • 버튼 라벨
  • 어떤 버튼이 어떤 도구를 부르는지

전부 서버가 준 문서 안에 있다. 클라이언트는 그 문서를 받아 런타임에 넘기는 배관이다.

그래서 서버가 화면을 고치면 클라이언트는 아무것도 안 해도 된다. 서버 4편에서 fontSize 를 88에서 120으로 바꿨을 때 서버를 안 껐던 것처럼, 이쪽도 다시 안 빌드한다.

546 바이트

화면 하나가 546 바이트다. 6줄.

이 숫자가 작아 보이는 게 요점이다. 화면을 옮기는 비용이 작으면 옮기는 게 기본이 된다. 매번 앱을 배포해야 한다면 화면은 앱 안에 있는 게 맞고, 문서 하나 받는 게 전부라면 밖에 있는 게 맞다.

검증 — 클라이언트에 화면이 있으면 실패

run step4 > captures/s4.txt || die "step4: failed"
grep -q 'type "page"' captures/s4.txt || die "what arrived is not a screen"
grep -q 'fontSize' bin/step4.dart && die "step4: the screen is inside the client"

bin/step4.dart 에 fontSize 가 한 번이라도 나오면 실패한다.

서버 쪽에도 같은 검사가 있다 — 서버 4편은 서버 코드에 화면이 있으면 실패한다. 양쪽 다 화면을 안 갖고 있어야 이 구조가 성립한다. 한쪽이라도 갖고 있으면 그건 그냥 하드코딩된 앱이다.

직접 돌려보기

dart run bin/step4.dart
bash verify.sh

../course-server/ui/desk.json 을 고치고 다시 돌리면 바뀐 크기가 찍힌다. 클라이언트는 안 건드렸다.

가져갈 것

  1. readResource → jsonDecode → 런타임 세 줄
  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/step4.dart
GitHub에서 열기 →