예제는 복사 대상이 아니라 설계 도면이다
Apps SDK를 익히는 가장 빠른 방법은 예제를 그대로 실행해 보는 것입니다. 그러나 예제를 제품에 그대로 붙이는 것만으로는 부족합니다. 중요한 것은 예제 안에 숨어 있는 구조를 읽는 일입니다. 어떤 도구가 모델에게 노출되는지, 어떤 결과가 위젯으로 렌더링되는지, 어떤 상태가 서버와 UI 사이를 오가는지를 보면 내 서비스에 맞는 설계가 보입니다.
OpenAI의 Apps SDK 예제는 MCP 서버가 도구를 노출하고, 각 도구가 특정 UI 컴포넌트를 ChatGPT 안에 표시하는 기본 흐름을 보여 줍니다. 공식 문서도 Pizzaz 데모와 GitHub 예제 저장소를 앱을 조립할 때 참고할 블루프린트로 소개합니다.1 GitHub 저장소에는 Pizzaz, Kitchen Sink Lite, Shopping Cart, 인증 예제, Solar System 예제처럼 서로 다른 목적의 샘플이 포함되어 있습니다.2
이 장에서는 예제의 코드를 줄 단위로 따라가기보다, 재사용 가능한 앱 패턴을 뽑아냅니다. 목표는 하나입니다. 내 서비스가 단순 조회형 앱인지, 비교·선택형 앱인지, 주문·예약·결제까지 이어지는 실행형 앱인지 빠르게 판단하는 것입니다.

공식 예제의 공통 골격
대부분의 Apps SDK 앱은 같은 뼈대를 가집니다.
첫째, MCP 서버가 도구 목록을 제공합니다. 도구에는 이름, 설명, 입력 JSON Schema, 출력 구조가 있습니다. 모델은 이 정보를 보고 사용자의 요청에 어떤 도구가 맞는지 판단합니다.
둘째, 도구가 실행되면 서버는 구조화된 데이터를 반환합니다. 이 데이터는 모델이 읽을 수 있어야 합니다. 예를 들어 피자 목록이라면 이름, 가격, 매장, 태그, 이미지 같은 필드가 명확해야 합니다.
셋째, 서버는 위젯을 렌더링할 수 있는 연결 정보를 제공합니다. ChatGPT Apps SDK에서는 보통 도구 descriptor의 _meta["openai/outputTemplate"]가 UI 리소스를 가리킵니다. 위젯은 단순 장식이 아니라, 사용자가 후보를 비교하고, 옵션을 고르고, 다음 행동을 선택하는 작업 공간입니다.
넷째, 위젯은 필요할 때 다시 도구를 호출합니다. 사용자가 "수량 +1" 버튼을 누르거나, 다른 조건으로 다시 검색하면 위젯은 window.openai.callTool 같은 브릿지를 통해 MCP 도구를 다시 실행할 수 있습니다.
이 구조를 한 문장으로 줄이면 다음과 같습니다.
대화는 요구사항을 좁히고, 도구는 데이터를 가져오며, 위젯은 선택을 쉽게 만들고, 실행 도구는 실제 행동을 끝낸다.
이 문장이 대부분의 실전 앱에 적용됩니다.
Pizzaz: 탐색에서 체크아웃까지 이어지는 서비스 앱
Pizzaz는 Apps SDK 예제 중 가장 서비스 앱에 가깝습니다. 피자 목록, 캐러셀, 지도, 피자 가게 화면, 체크아웃 흐름이 함께 들어 있습니다.2 그래서 단순한 UI 샘플이 아니라 "하나의 음식 주문 서비스가 ChatGPT 안에서 어떻게 동작할 수 있는가"를 보여 주는 예제에 가깝습니다.
Pizzaz에서 배울 핵심은 도구를 화면 단위가 아니라 사용자 여정 단위로 나누는 방식입니다.
사용자는 처음부터 "마르게리타 라지 한 판을 오후 7시에 배달해 줘"라고 말하지 않을 수 있습니다. 보통은 이렇게 시작합니다.
"근처에서 인기 있는 피자 추천해 줘."
"매운 피자 말고 치즈 많은 걸로."
"가게 위치도 보여줘."
"두 번째 걸로 할게. 라지 사이즈 가능해?"
"주문 전에 총액 확인해 줘."
이 흐름을 하나의 거대한 order_pizza 도구로 처리하면 복잡해집니다. 더 나은 구조는 역할을 나누는 것입니다.
search_pizzas: 조건에 맞는 피자 후보를 찾는다.get_pizza_details: 특정 피자의 상세 정보를 가져온다.show_stores_on_map: 매장, 배달 가능 지역, 예상 시간을 보여 준다.configure_order: 사이즈, 토핑, 수량, 배달 방식을 조정한다.preview_checkout: 결제 전 요약을 만든다.place_order: 사용자가 확인한 뒤 주문을 실행한다.
이렇게 나누면 모델은 대화 맥락에 맞춰 필요한 도구만 호출합니다. 위젯도 자연스럽게 나뉩니다. 목록은 카드나 캐러셀, 위치 정보는 지도, 옵션 선택은 폼, 최종 단계는 체크아웃 요약 UI가 맡습니다.
중요한 원칙은 실행 도구를 검색 도구와 분리하는 것입니다. 검색은 여러 번 호출되어도 괜찮습니다. 하지만 주문, 예약, 결제는 사용자의 명시적 확인이 필요합니다. 따라서 place_order 같은 도구는 확인 UI 뒤에 놓아야 합니다. 이 분리가 실행형 앱의 안전장치입니다.
Kitchen Sink: 기능을 한 번에 검증하는 진단용 앱
Kitchen Sink 계열 예제는 실제 서비스라기보다 Apps SDK 기능을 점검하는 진단용 앱에 가깝습니다. GitHub 저장소 설명에 따르면 Kitchen Sink Lite는 toolInput, toolOutput, displayMode, theme, widgetState 같은 호스트 상태를 읽고, setWidgetState, callTool, requestDisplayMode, openExternal, sendFollowUpMessage 같은 호스트 API를 함께 보여 줍니다.2
이 예제의 가치는 "무엇을 만들 수 있는가"보다 "내 앱의 연결부가 제대로 작동하는가"를 확인하는 데 있습니다. 실전 프로젝트에서는 Kitchen Sink와 비슷한 내부 테스트 위젯을 하나 만들어 두면 좋습니다.
예를 들어 다음 항목을 한 화면에서 확인할 수 있습니다.
최초 도구 호출 결과가 위젯에 제대로 전달되는가?
위젯 내부에서 저장한 상태가 새 대화 턴에서도 복원되는가?
위젯 버튼이 MCP 도구를 다시 호출할 수 있는가?
인라인 화면에서 전체 화면으로 전환할 수 있는가?
테마 변경이나 표시 모드 변경에 UI가 깨지지 않는가?
외부 링크 열기나 후속 메시지 전송이 의도대로 작동하는가?
이런 진단용 앱은 출시 기능이 아니어도 됩니다. 오히려 개발 중에 더 중요합니다. Apps SDK 앱은 모델, MCP 서버, 위젯, ChatGPT 호스트가 함께 움직입니다. 한 곳의 데이터 모양이 어긋나면 원인을 찾기 어렵습니다. 실제 Apps SDK 앱을 만든 개발자들도 응답 형태 불일치나 메타데이터 누락이 모호한 오류로 이어질 수 있어, 반환 payload를 로깅하고 기준 fixture를 유지하는 방어적 디버깅이 중요하다고 설명합니다.3
Shopping Cart: 공유 상태를 설계하는 패턴
장바구니는 Apps SDK 상태 설계의 대표 예제입니다. 장바구니 상태는 위젯만 알아서는 안 됩니다. 모델도 알아야 하고, MCP 도구도 알아야 하며, 서버도 최종 상태를 신뢰할 수 있어야 합니다.
공식 예제 저장소의 Shopping Cart는 _meta["widgetSessionId"]를 사용해 위젯 상태를 도구 호출 사이에서 이어 가는 방식을 보여 줍니다. 위젯은 도구 응답과 이전 widgetState를 합쳐 최신 장바구니를 만들고, 수량 변경 같은 UI 액션을 다시 공유 상태에 반영합니다.2
여기서 배울 점은 상태를 세 종류로 나누는 것입니다.
첫째, 서버 상태입니다. 결제 가능한 장바구니, 재고, 가격, 할인, 사용자 권한처럼 신뢰가 필요한 데이터는 서버가 원천이어야 합니다.
둘째, 위젯 상태입니다. 펼쳐진 패널, 선택 중인 옵션, 임시 필터, 스크롤 위치처럼 UI 편의를 위한 정보는 위젯 상태로 충분합니다.
셋째, 모델이 알아야 하는 요약 상태입니다. "사용자는 페퍼로니 라지 1개와 콜라 2개를 장바구니에 담았다"처럼 대화에 필요한 요약은 구조화된 출력으로 제공해야 합니다.
실전에서는 위젯 상태만 믿으면 안 됩니다. 사용자가 브라우저를 새로고침하거나 다른 기기에서 이어서 작업할 수 있습니다. 예제는 상태 동기화의 메커니즘을 보여 주지만, 운영 환경에서는 장바구니를 서버 측 저장소에 보관하는 편이 안전합니다.2
서비스형 앱은 같은 흐름을 반복한다
영화 추천, 음식 배달, 숙소 예약, 강의 추천, 부동산 검색은 서로 다른 서비스처럼 보입니다. 하지만 Apps SDK 관점에서는 구조가 비슷합니다.
영화 추천 앱을 보겠습니다. 사용자는 "주말에 볼 가벼운 영화 추천해 줘"라고 말합니다. 모델은 장르, 분위기, 시청 가능 플랫폼, 이전 선호도를 묻거나 추론합니다. 검색 도구는 후보를 가져오고, 위젯은 포스터 그리드나 상세 카드로 비교를 돕습니다. 사용자는 하나를 저장하거나 시청 목록에 추가합니다. 실제로 Apps SDK로 영화 추천 앱을 만든 사례도 검색, 시청 목록, 시청 기록, 선호도, 추천 기능을 ChatGPT 안에서 연결하고, 위젯을 독립 HTML 번들로 빌드하는 구조를 사용했습니다.3
배달 주문도 같습니다. 대화로 메뉴 조건을 좁히고, UI로 후보를 비교하고, 도구로 장바구니와 주문을 실행합니다.
예약 앱도 같습니다. 대화로 날짜, 인원, 지역, 예산을 좁히고, UI로 가능한 시간과 옵션을 보여 주며, 실행 도구로 예약을 확정합니다.
강의 추천 앱도 같습니다. 대화로 목표와 수준을 파악하고, UI로 커리큘럼과 난이도를 비교하며, 저장 도구로 학습 계획에 추가합니다.
부동산 검색도 같습니다. 대화로 지역, 예산, 방 개수, 출퇴근 조건을 좁히고, UI로 지도와 매물 카드를 보여 주며, 문의나 저장 도구로 다음 행동을 실행합니다.
공통 구조는 다음 세 단계입니다.
요구사항 좁히기: 대화가 모호한 의도를 구체적인 조건으로 바꾼다.
후보 비교하기: 위젯이 여러 선택지를 시각적으로 비교하게 한다.
행동 실행하기: 도구가 저장, 예약, 주문, 문의 같은 결과를 만든다.
이 세 단계가 분명하면 ChatGPT 앱은 자연스럽습니다. 반대로 모든 것을 텍스트 답변으로만 처리하거나, 모든 것을 복잡한 위젯 안에 밀어 넣으면 Apps SDK의 장점이 흐려집니다.
조회형 앱을 실행형 앱으로 확장하는 원칙
처음에는 조회형 앱으로 시작하는 것이 좋습니다. 예를 들어 "추천 영화 보여 주기", "근처 피자집 찾기", "가능한 숙소 검색하기"처럼 읽기 작업부터 만듭니다. 읽기 작업은 실패해도 피해가 작고, 모델의 도구 선택 품질을 검증하기 쉽습니다.
그다음 실행형 기능을 붙입니다. 이때는 반드시 단계를 나눠야 합니다.
첫째, 검색 도구와 실행 도구를 분리합니다. search_listings와 book_viewing은 다른 도구여야 합니다. recommend_courses와 enroll_course도 분리해야 합니다.
둘째, 실행 전 확인 단계를 둡니다. 사용자가 무엇을 실행하는지, 비용이 있는지, 취소 가능한지, 어떤 계정 권한이 필요한지 명확히 보여 줘야 합니다.
셋째, 권한 요청을 작업 직전에 배치합니다. 처음부터 모든 권한을 요구하면 사용자가 부담을 느낍니다. 저장, 결제, 계정 데이터 접근처럼 필요한 순간에 요청하는 편이 낫습니다.
넷째, 오류 복구 경로를 만듭니다. 결제 실패, 재고 없음, 예약 시간 만료, 인증 만료는 정상적인 상황입니다. 오류 메시지는 코드가 아니라 다음 선택지를 알려 줘야 합니다. "이 시간은 방금 마감됐습니다. 비슷한 시간 3개를 다시 보여드릴까요?"처럼 복구 행동을 제안해야 합니다.
다섯째, 최종 확인 UI를 남깁니다. 주문 번호, 예약 시간, 저장된 항목, 다음 단계가 한눈에 보여야 합니다. 모델의 마지막 답변은 이 UI를 요약하고, 사용자가 이어서 할 수 있는 일을 안내하면 충분합니다.
내 서비스에 맞는 패턴 고르기
Apps SDK 앱을 설계할 때 처음부터 거대한 앱을 상상할 필요는 없습니다. 먼저 내 서비스가 어떤 패턴에 가까운지 고르면 됩니다.
후보를 보여 주는 서비스라면 Pizzaz의 목록·캐러셀·상세 패턴을 참고한다.
지도, 위치, 거리 조건이 중요하다면 Pizzaz의 지도형 흐름을 응용한다.
브릿지 API와 표시 모드를 검증해야 한다면 Kitchen Sink식 내부 진단 위젯을 만든다.
모델과 UI가 같은 상태를 봐야 한다면 Shopping Cart 패턴을 따른다.
저장, 주문, 예약, 결제처럼 결과가 남는 앱이라면 검색 도구와 실행 도구를 분리한다.
좋은 ChatGPT 앱은 "웹사이트를 채팅창 안에 넣은 것"이 아닙니다. 사용자가 말로 목적을 표현하면, 모델이 조건을 정리하고, UI가 판단을 돕고, 도구가 안전하게 행동을 실행하는 구조입니다. 공식 예제는 이 구조를 작은 장면으로 나눠 보여 줍니다. 그 장면들을 내 서비스의 여정에 맞게 다시 조립하면 됩니다.
참고
1Examples - Apps SDK | OpenAI Developers
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.