Claude는 최근 인터랙티브 UI를 업데이트해, 채팅창에서 인터랙티브 페이지를 스트리밍 출력할 수 있게 됐다 claude:

동시에 Generative-UI-MCP이라는 프로젝트도 봤다. 저자의 아이디어는 매우 직관적이었다. MCP 프로토콜을 이용해 Claude가 인터랙티브 UI를 생성하는这套 방식을 재현한 것이다.
이 프로젝트 자체는 복잡하지 않다. 하지만 평소에는 설명하기 어려운 문제를 한꺼번에 풀어버린다. Claude 같은 인터랙티브 UI가 진짜로 새로운 점은 어디에 있는지, 왜 ‘AI가 프런트엔드 코드를 대신 작성해 준다’보다 더 많은 것을 의미하는지, 그리고 그背後에서 먼저 해결해야 하는 것이 렌더링인지 프로토콜인지 말이다.
이후에는 실제 SSE 스트리밍 출력 로그를 하나씩 따라가며 전체 과정을 다시 분해했다. 양쪽을 맞춰 보면 더 분명해진다. Claude 스타일의 스트리밍 UI는 본질적으로 ‘모델이 HTML을 출력한다’가 아니라, ‘모델이 호스트가 안정적으로 소비할 수 있는 UI 프로토콜을 계속 출력한다’.
이篇文章에서 이야기하려는 바로 그것이다.
지속적으로 상호작용할 수 있는 UI 페이지
Claude의 이번 인터랙티브 UI / Artifacts에서 진짜 새로운 점은 ‘AI가 페이지를 생성했다’가 아니다. 그런 일은 오래전부터 있었다. 새로운 점은 만들어진 결과물을 계속 사용할 수 있고, 모델과 계속 상호작용할 수 있으며, 도구를 연결하고 상태도 갱신할 수 있다는 데 있다.
이것은 ‘AI가 프런트엔드 코드를 작성해 줬으니 복사해서 실행한다’와는 완전히 다르다.
예전 방식에서는 모델이 시작점에 그쳤다가 코드를 생성하고 나면 끝났다. 이제 모델은 UI 세션에 계속 참여하는 주체가 된다. 사용자의 조작은 다시 모델로 전달되고, 모델이 돌려준 새 내용은 UI의 일부만 갱신하며, 이 과정이 오간다.
이 순환 구조는 대략 다음과 같다.
이 루프가 실제로 돌아가야 비로소 진짜 ‘인터랙티브’한 것이다. 버튼 몇 개를 붙인 HTML은 인터랙티브하지 않다. 이벤트가 다시 흘러들어가야 그 infest다.
실제 스트리밍 출력을 보면 이 문제가 분명해진다
실제 SSE 스트리밍 출력을 보면 모델이 완성된 페이지를 한 번에 뱉는 것이 아니라, 스트림 안에서 서로 다른 유형의 콘텐츠 블록을 계속 출력한다는 것을 알 수 있다. 프런트엔드는 이를 받으면서 붙여 나가고, 완성된 뒤에는 해당 도구에 전달해 렌더링한다.
분해하면 대략 다섯 단계로 나뉜다.
첫 번째 단계: UI 생성 가이드라인을 먼저 불러온다
모델이 처음부터 위젯을 곧바로 생성한 것은 아니다. 먼저 visualize:read_me과 비슷한 도구를 호출했으며, 입력 매개변수는 매우 짧았다.
이 단계가 중요하다. 모델이 실제로 ‘화면을 그리기’ 전에 먼저 런타임 UI 가이드라인을 가져왔다는 뜻이다. 즉, 생성 작업이 가흘한 채로 시작되는 게 아니라, 모델이 이번에 따라야 할 규칙을 먼저 알게 된다는 뜻이다.
두 번째 단계: 도구가 디자인 시스템 전체와 스트리밍 출력 제약을 반환한다
반환된 내용에는 특히 중요한 부분이 몇 개 있다.
먼저 모듈 설명이 나온다.
다음은 역할 정의다.
이어서 가장 중요한 제약 몇 가지가 등장한다.
스트리밍 렌더링에 특화된 순서 규칙도 있다.
표면적으로만 보면 설계 가이드라인처럼 보인다. 하지만 런타임의 관점에서는 ‘모델이 안정적인 UI 메시지를 출력하도록 하는 생성 프로토콜’에 훨씬 가깝다.
이 가이드라인은 다음 작업들을 한다.
무엇을 도구 안에 넣고 무엇을 자연어에 넣을지 규정한다
코드를 어떤 순서로 스트리밍 출력할지 규정한다
어떤 시각 효과가 스트리밍 경험을 깨뜨리는지 규정해 사용을 금지한다
컴포넌트가 호스트 환경에 맞게 조정돼야 한다고 규정한다. 예를 들면 CSS 변수, 다크 모드, 제어된 스크립트 기능 같은 것들이다
즉, 이것은 ‘모델에 미적 조언을 몇 가지 주는 것’이 아니라, 모델이 따라 걸어야 할 좁은 선을 그어 주는 것이다.
진짜 핵심은 HTML이 아니라 모델이 출력하는 구조다
이후 모델은 visualize:show_widget 같은 다른 도구를 호출하기 시작한다. 이 스트림 구간은 가장 쉽게 오해를 부르는 부분이다. 조각난 파편처럼 보이기 때문이다.
이런 조각 하나만 보면 거의 읽을 수 없다. 하지만 이것은 난수가 아니라 도구 호출 인자의 일부다. 호스트는 같은 block에 계속 도착하는 partial_json을 이어 붙여 최종적으로 완전한 JSON을 복원한다.
이번 내용을 다시 조합하면 대략 이렇다.
이 안의 각 필드는 모두 의미가丰富하다.
title은 이번 위젯의 식별자다. loading_messages는 장식이 아니라 ‘기다림’을 체감할 수 있는 생성 과정으로 바꾸는 역할이다. i_have_seen_read_me은 상태 확인 같은 것으로, 모델이 이미 가이드라인을 읽은 상태에서 생성했다는 점을 보여준다.
실제 인터페이스는 모두 widget_code에 들어 있다.
这一步가 드러내는 스트리밍 UI의 핵심 사실은 이것이다. 모델이 완성된 페이지를 직접 출력하는 것이 아니라, 호스트가 소비할 수 있는 UI 메시지를 출력한다는 뜻이다.
왜 Generative-UI-MCP 프로젝트는 작아 보이면서도 가치가 있을까
처음에는 Claude의 인터랙티브 UI를 재현하려면 많은 것이 필요할 거라 생각했다. 커스텀 렌더러, 상태 관리, 완전한 프런트엔드 런타임, 컴포넌트 라이브러리, DSL 같은 것들이다.
결과적으로 Generative-UI-MCP의 핵심은 반직관적으로 단순했다.
필요할 때 모델에 UI 생성 가이드라인을 불러오는
load_ui_guidelines도구가장 기본적인 출력 제약을 미리 주입하는 system prompt 리소스
대단한 컴포넌트 시스템도 없고, 복잡한 DSL도 없다.
이 선택이 오히려 핵심을 잘 드러낸다. Claude의 인터랙티브 UI를 재현할 때 먼저 해결해야 하는 것은 ‘어떻게 렌더링할지’가 아니라, ‘모델이 호스트가 안정적으로 소비할 수 있는 구조를 출력하도록 어떻게 할지’다. 렌더링은 그다음 문제다.
그러므로 이 일은 우선 렌더링 문제가 아니라 프로토콜 문제다
Claude의 인터랙티브 UI에는 매우 뚜렷한 경험적 특징이 있다. 위젯이 안정적이고 예측 가능한 방식으로 나타난다. 이번에는 코드 블록으로 바뀌고, 다음에는 HTML이 섞인 자연어로 바뀌는 식으로 출력되지 않는다.
이것을 보장하려면 모델의 ‘自觉ploit’t에 의존할 수 없고, 프로토콜에 의존해야 한다.
Generative-UI-MCP가 드러내는 것도 바로 이 층이다.
위젯은 전용 fence로 감싸야 한다
fence 안에는 반드시 구조화된 JSON이 있어야 한다
widget_code필드에는 HTML 또는 SVG를 담는다설명 텍스트는 위젯 block 밖에 작성해야 한다
여러 위젯은 여러 block으로 나눠야 한다
출력 순서는 스트리밍 렌더링에 적합해야 한다
이 제약들을 쌓으면 본질적으로 이미 가벼운 UI 메시지 프로토콜에 매우 가까운 수준이다.
호스트가 하는 일은 결국 이 메시지들을 라우팅하는 일이다.
왜 가이드라인은 필요할 때 불러와야 하며 한 번에 prompt에 다 넣으면 안 되는가
재현 프로젝트는 UI 가이드라인을 interactive, chart, mockup, diagram, art 같은 여러 모듈로 나눴다. 필요한 것을 필요한 때에 불러오는 방식이다.
이것은 토큰을 아끼기 위한 조치가 아니다. 더 중요한 이유는, 서로 다른 UI 유형의 제약 자체가 다르기 때문이다.
차트에는 차트에 맞는 규칙이 있다
폼에는 폼에 맞는 규칙이 있다
mockup에는 mockup에 맞는 규칙이 있다
diagram에는 diagram에 맞는 규칙이 있다
art는 또 다른 생성 방식을 사용한다
전부 하나의 큰 prompt에 밀어 넣으면 모델이 무관한 제약으로 오염될 수 있다. 필요한 때에 필요한 규칙만 넘겨야 모델이 해당 유형의 UI를 생성할 때 출력이 훨씬 안정적이다.
앞서 본 실제 스트림에서 먼저 다음 내용을 전달한 것도 같은 사고방식이다.
스트리밍의 까다로운 점은 ‘더 빨리’가 아니라 ‘흐르는 동안 프레임을 나누는 것’이다
많은 사람의 스트리밍에 대한 이해는 ‘더 빨리 표시한다’쯤에서 멈춰 있다. 하지만 실제 출력과 재현 프로젝트 모두에서 호스트가 진짜 해결해야 할 문제는 parser라는 것을 알 수 있다.
호스트는 토큰을 하나씩 출력하기만 해서는 안 된다. 다음을 알아야 한다.
지금 들어오는 것이 일반 텍스트인지 위젯 block인지
지금 출력되는 것이 block의 시작인지 중간 조각인지
JSON이 완전히 닫혔는지
언제 텍스트를 바로 표시할 수 있는지
언제 수집 모드로 전환해야 하는지
언제 완성된
widget_code를 렌더러에 넘겨야 하는지
전체 과정은 대략 다음과 같다.
Claude의 위젯이 자연스럽게 떠오르는 듯한 느낌은 기술적으로 마법이 아니다. parser가 스트림을 받으면서 동시에 프레이밍하고 있기 때문이다.
왜 <defs>, style 순서, 그림자 같은 세부 사항까지 관리해야 하는가
처음 이런 가이드라인을 보면 지나치게 세밀하게 관리한다는 생각이 들 수 있다.
SVG에서는
<defs>가 그래프보다 먼저 와야 한다HTML에서는
style이 앞에,script가 뒤에 있어야 한다가능하면 그라디언트, 그림자, 블러를 피해야 한다
하지만 이 규칙은 미적 수준을 관리하는 것이 아니다. 사용자가 보는 모든 프레임이 올바른 상태인지 관리하는 것이다.
이유는 단순하다.
<defs>가 아직 도착하지 않았는데 그래프가 먼저 나타나면 marker와 clipPath가 처음에는 잘못되었다가 나중에 맞춰진다style이 너무 늦게 도착하면 사용자는 먼저 벗겨진 UI를 보고, 이후 스타일이 갑자기 채워지는 경험을 하게 된다그라디언트, 블러, 그림자는 스트리밍 patch 과정에서 프레임 간 불일치가 더 쉽게 발생한다
Claude가 출력한 위젯에 눈에 띄는 떨림이 거의 없는 것은 모델이 더 강해서일 뿐 아니다. 이 제약들이 중간 상태의 불안정성도 억눌러 주기 때문이다.
실제 위젯 하나를 보면 이 방식이 어떤 프런트엔드를 지향하는지 알 수 있다
그 실제 출력에서 모델은 결국 ‘자주 쓰는 UI 선 아이콘 25개-buyat 인터랙티브 패널’을 생성했다. 카테고리별로 아이콘을 보여 주고, 클릭하면 하이라이트되며, 아래쪽에서 피드백을 제공한다.
생성된 widget_code에는 몇 가지 뚜렷한 선택이 드러난다.
첫째, 레이아웃은 매우 단순하다. 핵심은 안정적인 그리드이며, 복잡한 반응형 기법이 아니다.
둘째, 스타일은 매우 가볍다. 호스트가 제공한 CSS 변수만 사용하고 색을 하드코딩하지 않아 다크 모드에 자연스럽게 적응한다.
셋째, 아이콘은 SVG를 직접 인라인한다. 이미지 리소스에 의존하지 않기 때문에 스트리밍 출력하기도 쉽고, hover와 active 상태에서 색을 바꾸기도 쉽다.
넷째, JS는 짧다. 로컬 상호작용만 처리하며, 복잡한 상태 관리도, 네트워크 요청도, 프레임워크 도입도 하지 않는다.
이 이런 스트리밍 UI는 완전한 프런트엔드 애플리케이션이라기보다 ‘대화 안에서 즉각 반응하는 상호작용 셸’에 가깝다. 복잡한 로직은 모델에 맡기고, 국소적인 상호작용은 프런트엔드에 남기는 방식이다.
다음에는 잘 맞는다.
아이콘 패널
비교 카드
가벼운 필터
작은 차트
인터랙티브 설명 도구
임베드된 mockup
다음에는 잘 맞지 않는다.
- 지나치게 복잡한 비즈니스 폼
-大型 다중 페이지 애플리케이션
상태 관리가 무거운 관리자용 시스템
실시간 협업 편집기
이 방식의 강점은 즉석 생성, 즉석 삽입, 즉각적인 상호작용이지, 장시간 실행되는 대규모 애플리케이션 셸이 아니기 때문이다.
실제 스트림의 마지막에는 아주 중요한 신호가 있다. 텍스트와 UI는 반드시 역할을 나눠야 한다
도구가 위젯을 렌더링한 뒤 시스템은 다시 힌트를 하나 반환했다.
Content rendered and shown to the user. Please do not duplicate the shown content in text because it's already visually represented.
이 힌트의 가치가 크다. 이미 렌더링된 내용을 텍스트로 다시 반복하지 말라고 모델에 명확히 알려주는 것이다.
이후 모델이 덧붙인 자연어도 매우 절제되어 있으며, 세 가지 작업만 한다.
이 위젯이 무엇인지 요약한다
사용자가 어떻게 조작하는지 알려준다
사용자가 다음에 모델에게 무엇을 더 요청할 수 있는지 알려준다
앞서 readme에 나온 다음 문장과 정확히 맞아떨어진다.
Text goes in your response, visuals go in the tool.
이렇게 하나의 고리가 완성된다.
즉, Claude 스타일의 스트리밍 UI는 ‘위젯을 렌더링할 줄 안다’뿐 아니라, 텍스트와 시각 요소 사이의 책임 경계까지 관리한다.
Generative-UI-MCP에서 볼 수 없는 부분이 오히려 제품화에서 가장 어려운 부분이다
솔직히 이 재현 프로젝트를 살펴보면, 원본 시스템에는.protocol만으로 채울 수 없는 부분이 무엇인지 더 분명해진다.
1. 샌드박스
모델이 생성한 HTML/JS를 그대로 실행해서는 안 된다. iframe 격리, 허용된 CDN 목록, 스크립트 기능 제한, 리소스 권한 경계가 반드시 필요하다.
그렇지 않으면 모델이 악성 스크립트 하나만 생성해도 호스트에 문제가 생긴다.
2. action 프로토콜
사용자가 클릭한 뒤 무엇이 일어나는지를 모델이 마음대로 onclick를 작성하고 로직을 자유롭게 결정하게 해서는 안 된다. 성숙한 설계는 호스트가 먼저 다음과 같은 통일된 action 스키마를 정의하는 방식이다.
filter_changedsubmit_formrequest_refreshselect_item
위젯은 액션만 보내고, 호스트가 로컬 처리를 할지, 도구를 호출할지, 다시 모델에게 물을지 결정한다.
3. 증분 patch
Claude는 여러 턴에 걸친 대화에서 위젯을 갱신할 때, 전체를 다시 생성하는 경우가 많지 않고 일부만 업데이트한다. 이를 위해서는 호스트가 상태를 관리해야 하며, 모델도 언제 patch를 반환하고 언제 전체 교체를 반환해야 하는지 알아야 한다.
demo와 제품 수준의 경험이 가장 크게 다른 지점은 아마 여기다.
가장 기억해둘 만한 말
Generative-UI-MCP에서 얻는 가장 큰收获는 새로운 기술을 하나 익힌 데 있지 않다. 오히려 다음 사실을 더 선명하게 인식하게 된다는 데 있다.
인터랙티브 UI를 만드는 일은 렌더링을 먼저 해결하는 일이 아니라, 프로토콜을 먼저 해결하는 일이다.
프로토콜이 안정적이어야 비로소 다음 것들이 가능하다.
스트리밍 parser
위젯 렌더링
샌드박스 실행
이벤트回流
도구 연결
증분 업데이트
Claude는 이 연결 고리를 기본적으로 완성했기 때문에使用时 demo처럼 느껴지지 않는다. Generative-UI-MCP는 이 고리에서 가장 앞부분의 로직을 오픈소스로 공개했기 때문에, 이 일이 처음으로 충분히 이해하고, 논의하고, 분해할 수 있는 대상이 되었다.
이제 돌이켜 보면, 겉으로는 조각난 것처럼 보이는 스트리밍 조각들, 특히 계속 등장하는 input_json_delta, widget_code, tool_use는 더 이상 단순한 잡음처럼 보이지 않는다. 이它们은 바로 이 생성형 UI 프로토콜이 런타임에 남긴 흔적이다.
부록: 이번 스트림에 실제로 등장한 원본 프롬프트
아래 부분은 정리된 템플릿이 아니라, 실제 스트리밍 출력에서 복원한 원본 프롬프트와 가이드라인 텍스트다.