Skip to main content
Summary

Apps SDK 앱을 처음 만들 때 가장 헷갈리는 부분은 "어디에 무엇을 써야 하는가"입니다. 모델이 모든 일을 하는 것처럼 보이지만, 실제 구조는 그렇지 않습니다. 모델은 사용자의 의도를 해석하고 어떤 도구를 쓸지 결정합니다. MCP 서버는 실제 데이터를 조회하거나 작업을 실행합니다. ChatGPT 클라이언트는 대화와 도구 호출, 위젯 렌더링을 이어 붙입니다. 위젯 UI는 사용자가 볼 수 있는 화면을 담당합니다.

이 역할 분리를 이해하면 Apps SDK의 많은 문제가 단순해집니다. 도구 호출이 잘못되는 문제는 대개 도구 설명과 JSON Schema 문제입니다. UI가 안 뜨는 문제는 대개 위젯 리소스 연결 문제입니다. 모델이 이상한 답을 하는 문제는 서버가 반환한 structuredContent가 불명확한 경우가 많습니다.

MCP와 Apps SDK 아키텍처 이해하기

큰 그림: 모델은 판단하고, 서버는 실행하고, UI는 보여준다

Apps SDK 앱은 보통 네 가지 구성 요소로 움직입니다.

구성 요소역할
사용자자연어로 요청한다. 예: "이번 주 주문 목록 보여줘."
모델요청을 해석하고 필요한 도구를 선택한다.
MCP 서버도구 목록을 제공하고, 도구 호출을 실행하고, 결과를 반환한다.
ChatGPT 클라이언트와 위젯결과를 대화에 표시하고, 필요한 경우 iframe 안에 UI를 렌더링한다.

MCP는 이 구성 요소들이 같은 규칙으로 대화하도록 만드는 표준 프로토콜입니다. OpenAI Apps SDK에서 MCP는 서버, 모델, UI를 같은 흐름 안에 묶는 기반 역할을 합니다. OpenAI 문서도 Apps SDK에서 MCP가 서버와 모델, UI를 동기화하는 중심이라고 설명합니다.1

여기서 중요한 점은 모델이 직접 데이터베이스에 접근하지 않는다는 것입니다. 모델은 "주문 목록 조회 도구를 호출해야겠다"고 판단할 뿐입니다. 실제 주문 데이터 조회, 권한 확인, API 호출, 데이터 가공은 MCP 서버가 합니다. 모델은 서버가 돌려준 결과를 바탕으로 사용자에게 설명하거나 다음 행동을 이어갑니다.

이 구조를 한 문장으로 정리하면 다음과 같습니다.

모델은 판단하고, MCP 서버는 실행하며, 위젯은 보여준다.

이 원칙을 지키면 앱이 안정적입니다. 반대로 모델에게 너무 많은 실행 책임을 맡기거나, 위젯에 비즈니스 로직을 몰아넣으면 디버깅이 어려워집니다.

MCP 서버가 ChatGPT에 기능을 노출하는 방식

MCP 서버는 ChatGPT에 "내가 어떤 일을 할 수 있는지"를 알려줍니다. Apps SDK 관점에서 핵심 흐름은 세 단계입니다.

  1. List tools: 서버가 사용 가능한 도구 목록을 공개한다.

  2. Call tools: 모델이 적절한 도구를 선택해 인자와 함께 호출한다.

  3. Return widgets: 서버가 구조화된 결과와 UI 연결 정보를 반환하고, ChatGPT가 위젯을 렌더링한다.

예를 들어 쇼핑 앱을 만든다고 가정해 보겠습니다. MCP 서버는 다음과 같은 도구를 제공할 수 있습니다.

  • search_products: 상품 검색

  • get_product_detail: 상품 상세 조회

  • add_to_cart: 장바구니 담기

  • checkout_preview: 결제 전 요약 보기

사용자가 "선크림 중에 민감성 피부용으로 평점 좋은 것 보여줘"라고 말하면 모델은 이 요청이 상품 검색이라고 판단합니다. 그리고 search_products 도구를 선택해 { "category": "sunscreen", "skinType": "sensitive", "sort": "rating" } 같은 인자를 만듭니다. MCP 서버는 이 인자로 실제 상품 API나 데이터베이스를 조회한 뒤 결과를 반환합니다.

이때 ChatGPT는 단순 텍스트만 표시할 수도 있고, 상품 카드나 목록 위젯을 함께 렌더링할 수도 있습니다. OpenAI 예제 저장소도 MCP 서버가 도구를 노출하고, 도구 결과에 추가 메타데이터를 담아 Apps SDK 클라이언트가 위젯을 표시하는 구조를 보여줍니다.2

JSON Schema는 모델과 서버 사이의 계약이다

도구는 이름과 설명만으로 충분하지 않습니다. 모델이 정확히 호출하려면 입력값의 모양을 알아야 합니다. 이때 사용하는 것이 JSON Schema입니다.

JSON Schema는 "이 도구는 어떤 인자를 받고, 각 인자는 어떤 타입이며, 무엇이 필수인지"를 정의하는 계약입니다. 예를 들어 상품 검색 도구는 다음과 같은 입력 구조를 가질 수 있습니다.

{
  "type": "object",
  "properties": {
    "query": { "type": "string", "description": "검색어" },
    "limit": { "type": "number", "description": "가져올 상품 수" },
    "sort": {
      "type": "string",
      "enum": ["relevance", "rating", "price_low"]
    }
  },
  "required": ["query"]
}

이 스키마가 좋으면 모델은 도구를 안정적으로 호출합니다. 반대로 스키마가 모호하면 문제가 생깁니다.

  • 필수값이 빠진다.

  • 문자열이어야 할 값이 배열로 들어온다.

  • 도구 목적과 맞지 않는 인자가 생성된다.

  • 비슷한 도구가 여러 개 있을 때 엉뚱한 도구가 선택된다.

따라서 JSON Schema는 단순한 타입 정의가 아닙니다. 모델에게 "이 도구를 어떻게 써야 하는지" 알려주는 사용 설명서입니다. 도구 이름, 설명, 인자 설명, enum 값은 모두 모델의 선택 정확도에 영향을 줍니다.

좋은 도구 계약은 짧고 구체적입니다. do_action 같은 이름보다 search_products가 낫습니다. data 같은 인자보다 query, category, maxPrice가 낫습니다. 설명도 "상품을 처리합니다"보다 "사용자가 조건에 맞는 상품 목록을 보고 싶을 때 사용합니다"가 더 좋습니다.

structuredContent와 _meta의 역할 분리

도구가 실행된 뒤 MCP 서버는 결과를 반환합니다. Apps SDK에서 자주 만나는 반환 영역은 크게 세 가지입니다.

반환 영역주 사용자용도
content모델과 사용자짧은 텍스트 설명
structuredContent모델과 위젯구조화된 핵심 데이터
_meta주로 ChatGPT 클라이언트와 위젯UI 리소스, 렌더링 정보, 모델에게 굳이 보일 필요 없는 보조 데이터

structuredContent는 모델이 이해할 수 있는 데이터입니다. 예를 들어 주문 목록이라면 주문 ID, 상태, 금액, 날짜 같은 핵심 필드가 들어갑니다. 모델은 이 데이터를 보고 "총 3건의 주문이 있고, 그중 1건은 배송 중입니다"라고 설명할 수 있습니다.

_meta는 성격이 다릅니다. 이곳에는 위젯 렌더링에 필요한 정보나 클라이언트 전용 데이터가 들어갑니다. 예를 들어 위젯이 사용할 상세 데이터 맵, 세션 식별자, 추적 ID처럼 모델 답변에 굳이 노출할 필요가 없는 값을 담을 수 있습니다. 단, _meta는 위젯에서 접근할 수 있으므로 비밀번호, 장기 OAuth 토큰, 결제 정보처럼 민감한 값은 넣지 않아야 합니다.

위젯 템플릿 연결은 한 번 더 구분해서 이해해야 합니다. ChatGPT Apps SDK 흐름에서는 보통 도구 descriptor의 _meta["openai/outputTemplate"]가 ui://... 리소스를 가리키고, ChatGPT는 도구 호출 후 이 템플릿을 가져와 위젯을 렌더링합니다.3 반면 MCP-UI나 MCP Apps 표준을 함께 다루는 문서에서는 _meta.ui.resourceUri 같은 표현을 볼 수 있습니다. 둘은 비슷한 목적을 갖지만 같은 키가 아닙니다. 이 책에서는 ChatGPT Apps SDK를 기준으로 _meta["openai/outputTemplate"]를 기본 표현으로 사용하겠습니다.

실무에서는 다음 원칙을 지키는 것이 좋습니다.

  • 모델이 답변에 써야 하는 핵심 데이터는 structuredContent에 둔다.

  • 사용자에게 바로 보여줄 짧은 설명은 content에 둔다.

  • 위젯 렌더링이나 클라이언트 전용 정보는 _meta에 둔다.

  • ChatGPT에서 특정 위젯 템플릿을 연결할 때는 도구 descriptor의 _meta["openai/outputTemplate"]를 확인한다.

이 구분이 흐려지면 앱이 불안정해집니다. 모델이 봐야 할 데이터가 _meta에만 있으면 모델은 결과를 제대로 설명하지 못합니다. 반대로 위젯만 필요한 대용량 데이터를 structuredContent에 모두 넣으면 모델 컨텍스트가 낭비되고 응답 품질도 떨어질 수 있습니다.

위젯은 서버 결과를 시각화하는 독립된 화면이다

Apps SDK의 위젯은 ChatGPT 대화 안에 표시되는 작은 웹앱입니다. 일반적으로 HTML, CSS, JavaScript 또는 React 같은 웹 기술로 만들고, ChatGPT는 이를 안전한 iframe 환경에서 렌더링합니다.

위젯은 모델을 대체하지 않습니다. 위젯의 역할은 서버 결과를 사용자가 조작하기 쉬운 화면으로 보여주는 것입니다. 예를 들어 다음과 같은 UI는 텍스트보다 위젯이 적합합니다.

  • 상품 카드 목록

  • 지도와 위치 목록

  • 장바구니 수량 조절

  • 예약 시간 선택

  • 차트와 대시보드

  • 단계별 폼 입력

위젯은 도구 호출 결과를 받아 화면을 구성합니다. 이후 사용자가 버튼을 누르면 위젯이 다시 도구를 호출할 수도 있습니다. 이때 window.openai 브릿지를 사용하면 위젯이 ChatGPT 호스트와 통신하고, 도구 호출이나 상태 저장 같은 동작을 수행할 수 있습니다. 다만 이 장에서는 큰 구조만 이해하면 충분합니다. 자세한 브릿지 사용법은 뒤의 장에서 다룹니다.

중요한 설계 기준은 "위젯은 UI에 집중한다"입니다. 가격 계산, 권한 검증, 결제 가능 여부 판단 같은 핵심 로직은 서버에 두는 편이 안전합니다. 위젯은 서버가 준 결과를 보여주고, 사용자의 선택을 다시 서버에 전달하는 얇은 계층으로 유지하는 것이 좋습니다.

전송 방식: SSE와 Streamable HTTP

MCP는 특정 전송 방식에 묶이지 않습니다. Apps SDK와 관련된 MCP 서버는 SSE(Server-Sent Events) 또는 Streamable HTTP 방식으로 연결될 수 있습니다. OpenAI 예제 저장소도 Apps SDK가 두 방식을 모두 지원한다고 설명합니다.2

SSE는 서버가 클라이언트로 이벤트를 계속 밀어 보낼 수 있는 방식입니다. 구현이 비교적 단순하고, 실시간 업데이트 흐름을 이해하기 쉽습니다. 로컬 개발이나 기존 MCP 서버를 다룰 때 접할 수 있습니다.

Streamable HTTP는 HTTP 기반 요청과 스트리밍 응답을 활용하는 방식입니다. 배포 환경, 프록시, 인증, 재시도, 타임아웃 관리와 맞물려 운영하기 좋습니다. 공개 HTTPS 엔드포인트를 구성하고 ChatGPT에서 연결할 때는 배포 인프라가 스트리밍 동작을 제대로 지원하는지 확인해야 합니다.

두 방식의 차이는 앱 코드에도 영향을 줍니다.

항목SSEStreamable HTTP
개발 체감이벤트 스트림으로 이해하기 쉽다일반 HTTP 인프라와 맞추기 좋다
로컬 테스트빠르게 붙여 보기 좋다실제 배포 구조와 유사하게 테스트하기 좋다
운영 고려연결 유지와 프록시 설정 확인 필요타임아웃, 스트리밍, 인증 헤더 처리 확인 필요
공통점모두 MCP 메시지를 전달하는 통로다모두 도구 목록 조회와 도구 호출을 처리할 수 있다

전송 방식은 앱의 본질이 아닙니다. 본질은 MCP 서버가 도구 계약을 제공하고, 모델이 도구를 호출하고, 서버가 구조화된 결과와 위젯 연결 정보를 반환하는 것입니다. 전송 방식은 이 메시지를 어떤 네트워크 경로로 주고받을지에 대한 선택입니다.

요청 흐름을 한 번에 따라가기

마지막으로 전체 흐름을 하나의 예로 정리해 보겠습니다.

사용자가 말합니다.

"최근 주문 5개를 보여주고, 배송 중인 주문은 강조해줘."

ChatGPT의 모델은 이 요청이 주문 조회 도구와 관련 있다고 판단합니다. MCP 서버가 미리 제공한 도구 목록과 JSON Schema를 보고 list_orders 도구를 선택합니다. 그리고 { "limit": 5, "highlightStatus": "shipping" } 같은 인자를 만듭니다.

ChatGPT 클라이언트는 이 도구 호출을 MCP 서버로 보냅니다. MCP 서버는 사용자 인증을 확인하고 주문 API를 조회합니다. 그런 다음 다음과 같은 결과를 반환합니다.

  • content: "최근 주문 5건을 불러왔습니다."

  • structuredContent: 주문 ID, 상품명, 상태, 결제 금액, 주문일

  • _meta: 위젯 전용 상세 데이터, 세션 식별자, 추적 정보 등

도구 descriptor에 _meta["openai/outputTemplate"]가 설정되어 있다면 ChatGPT는 해당 UI 리소스를 가져와 주문 목록 위젯을 렌더링합니다. 모델은 structuredContent를 바탕으로 결과를 설명하고, 위젯은 같은 데이터를 화면으로 보여줍니다. 사용자는 대화 안에서 텍스트 설명을 읽고, 바로 아래에서 주문 카드 UI를 확인합니다.

이 흐름이 Apps SDK 아키텍처의 핵심입니다. 도구 호출은 모델이 결정합니다. 실행은 MCP 서버가 담당합니다. 결과 데이터는 structuredContent로 전달됩니다. UI 연결은 도구 descriptor의 openai/outputTemplate과 리소스 등록으로 이어집니다. 사용자는 이 모든 과정을 하나의 자연스러운 대화로 경험합니다.

아키텍처를 이해하면 디버깅도 쉬워진다

Apps SDK 앱에서 문제가 생겼을 때는 네 부분으로 나누어 보면 됩니다.

  1. 모델이 도구를 선택하지 않는가?
    도구 이름, 설명, JSON Schema가 모호한지 확인합니다.

  2. 도구 호출 인자가 이상한가?
    Schema의 타입, 필수값, enum, 설명을 점검합니다.

  3. 서버 결과는 맞는데 답변이 이상한가?
    모델이 봐야 할 데이터가 structuredContent에 있는지 확인합니다.

  4. 데이터는 있는데 UI가 안 뜨는가?
    _meta["openai/outputTemplate"]의 위젯 리소스 연결, 정적 파일 호스팅, CSP, 전송 URL을 확인합니다.

이렇게 보면 Apps SDK는 복잡한 마법이 아닙니다. MCP라는 공통 규칙 위에서 모델, 서버, 클라이언트, 위젯이 각자 맡은 일을 하는 구조입니다. 다음 장부터는 이 구조를 실제 개발 환경에서 실행해 보며 하나씩 확인하겠습니다.

참고

1MCP - Apps SDK | OpenAI Developers

3OpenAI Apps SDK Integration

책 목록

ChatGPT 앱을 만드는 법: OpenAI Apps SDK 실전 가이드

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기