바뀌었다고만 알린다 — 값을 실으면 안 되는 이유

5 분
목표MCP 서버 강좌 마지막 편. 화면이 1초마다 묻는 걸 없앤다. 서버가 알리는데, 알림에는 uri 만 담고 값은 안 담는다. 그리고 구독하지 않은 클라이언트에는 아무것도 안 간다.

5편까지 화면이 최신 값을 알려면 물어봐야 했다. 1초마다 묻거나, 사람이 새로고침하거나.

알린다

값을 바꾼 뒤 한 줄이다.

waiting -= n;
_save(waiting);
// Say that it changed. Not what it changed to.
server.notifyResourceUpdated('desk://waiting');

구독 가능한 리소스를 하나 두고, capability 에 subscribe: true 를 켠다.

resources: ResourcesCapability(listChanged: true, subscribe: true),

와이어에 나가는 것

{"jsonrpc":"2.0","method":"notifications/resources/updated","params":{"uri":"desk://waiting"}}

uri 하나뿐이다. 몇 명이 되었는지는 안 들어 있다.

값을 실으면 왜 안 되나

알림에 값을 넣는 게 당연해 보인다. 한 번에 끝나니까. 그런데 알림은 순서를 보장하지 않는다.

서버: waiting 2 로 바뀜 → 알림 A 발송
서버: waiting 1 로 바뀜 → 알림 B 발송
클라이언트: B 도착 (1)  →  A 도착 (2)   ← 순서가 뒤집혔다
화면: 2

값이 실려 있으면 늦게 온 옛 알림이 새 값을 덮는다. 화면이 조용히 틀린 숫자를 띄우고, 아무도 모른다.

"바뀌었다" 만 보내면 이 문제가 사라진다. 알림이 몇 번 오든, 순서가 어떻든, 받은 쪽이 읽으면 그때의 최신값이 온다.

주방 화면 편에서 같은 결론에 닿았다. 그때는 설계로 골랐고, 여기서는 패키지가 그렇게만 보낸다 — notifyResourceUpdated(uri) 에 값을 실을 자리가 아예 없다.

구독하지 않으면 아무것도 안 간다

만들면서 걸린 것. notifyResourceUpdated 를 부르고 서버 로그에도 찍히는데 와이어에는 아무것도 안 나갔다.

notified desk://waiting        ← 서버는 불렀다
(클라이언트 쪽에는 아무것도 없음)

구독하지 않은 클라이언트에는 보내지 않기 때문이다. 스펙대로다.

{"jsonrpc":"2.0","id":2,"method":"resources/subscribe","params":{"uri":"desk://waiting"}}

이걸 보낸 뒤부터 온다. 서버 쪽 결함으로 오해하기 쉬운 자리다 — 서버는 정상이고, 받을 준비를 안 한 것이다.

검증 — 값이 실렸는지를 파싱해서 본다

ask step6 \
  '{"jsonrpc":"2.0","id":2,"method":"resources/subscribe","params":{"uri":"desk://waiting"}}' \
  '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"desk.admit","arguments":{"count":1}}}'
params = json.loads(lines[0]).get('params', {})
extra = set(params) - {'uri'}
if extra:
    print('the notification carried', sorted(extra), '— it must carry only the uri')
    sys.exit(1)

처음엔 grep waiting 으로 검사했다가 오탐이 났다. uri 자체가 desk://waiting 이라 항상 걸린다. 문자열로 보면 안 되고 파싱해서 키를 봐야 한다.

   notification params: {"uri": "desk://waiting"}
step6  notification carried the uri and no value

여섯 편이 끝났다

step1  initialize -> serverInfo Course
step2  tools/list -> desk.count
step3  refused 0 and 99, admitted 1 -> waiting 2
step4  ui://desk served from ui/desk.json (6 lines), not from the code
step5  admitted 2, process ended, a new process still reads waiting 1
step6  notification carried the uri and no value

6 steps · each checked against its own claim

서버 하나가 여섯 단계로 자랐고, 단계마다 그 편이 주장하는 것이 검사된다. "돌아간다" 가 아니라.

직접 돌려보기

cd content/sample/course-server
dart pub get
bash verify.sh            # 여섯 단계 전부
dart run bin/step6.dart   # 완성본

가져갈 것

  1. notifyResourceUpdated(uri) — 값을 실을 자리가 없는 게 설계다
  2. 구독 먼저 — 구독 없이는 아무것도 안 간다
  3. 파싱해서 검사 — 문자열 검사는 uri 에 걸려 오탐이 난다

다음 트랙

여기까지가 서버다. 다음은 클라이언트 쪽 — 붙고, 무엇을 할 수 있는지 묻고, 화면을 받아 그리는 것.

샘플 실행하기

makemind-academy/course_server/
git clone https://github.com/makemind-academy/course_server
cd course_server
dart pub get
dart run bin/step6.dart
GitHub에서 열기 →