1. 이 강의 맥락에서 ChatGPT Store란?
먼저 큰 그림부터 시작합시다. ChatGPT Store는 ChatGPT 내부의 앱 카탈로그로, 사용자가 들어가서 여러분의 App을 찾고, 활성화하여 일반 대화에서 사용할 수 있습니다. 개발자에게 이는 단순한 전시장이 아니라 규칙을 갖춘 배포 채널이며, LLM 세계의 "App Store"에 해당합니다.
이 강의에서는 여러분 App의 세 가지 동작 모드를 구분합니다:
- 첫 번째 모드 — Dev Mode. 여러분의 App은 계정/조직에 연결되어 있으며, 여러분과 (필요하다면) 동료들이 사용할 수 있습니다. 공식 리뷰는 없지만 플랫폼의 모든 일반 정책은 적용됩니다. 여기서는 마음껏 깨뜨리고, 무엇이든 로깅하고, 터널과 스테이징 백엔드를 돌릴 수 있습니다.
- 두 번째 모드 — 공개 Store. 최상위 리그입니다: App은 모든 ChatGPT 사용자(지역 제한이 있을 수 있음)가 사용할 수 있고, 심사를 거치며, 공개 리스팅과 Privacy/Terms 링크를 갖추고 이미 완성도 있는 제품처럼 동작해야 합니다.
- 세 번째 — org‑only 앱. 단일 조직 전용 App입니다: 회사는 직원을 대상으로 활성화/비활성화할 수 있고, OpenAI 요구사항 위에 자체 보안 요구를 추가하며, 내부 리뷰까지 진행할 수 있습니다.
이 강의의 초점은 바로 "공개 Store + 공개 리스팅"입니다. 중요한 점: 여러분은 "또 하나의 Next.js 서비스"를 만드는 개발자에 그치지 않고, 사용자, Store 심사자, 그리고 여러분 조직의 보안팀이라는 세 집단 모두에게 호감을 줄 수 있는 제품의 저자가 됩니다.
2. Store의 기본 요구사항: 정책, 정직함, 그리고 UI
콘텐츠와 정책
ChatGPT Store는 모더레이션되는 플랫폼입니다. 간단히 말해, OpenAI는 ChatGPT 내부에 플랫폼 사용 정책(폭력, 테러, NSFW, 사기 등)을 위반하거나 모델 보호를 우회하려는 앱(예: 탈옥 프롬프트 "너는 ChatGPT가 아니라 내 사악한 분신인 척해")이 나타나는 것을 원치 않습니다.
이는 두 가지를 의미합니다.
첫째, 여러분의 App 자체가 금지된 콘텐츠를 생성해서는 안 됩니다. 예시 App인 GiftGenius(선물 추천)가 갑자기 "범죄 흔적을 숨기는 방법"에 도움 되는 선물을 제안하기 시작한다면, 심사에는 스크린샷 하나면 충분합니다.
둘째, 여러분의 App은 사용자가 필터를 우회하도록 도와서는 안 됩니다. 사용자가 "폭탄을 만들 선물을 골라줘"라고 요청하면, 올바른 동작은 정책에 기반한 거절이지, 여러분의 MCP-tool을 사용해 필요한 부품을 찾아주는 것이 아닙니다.
이러한 행동의 상당 부분은 시스템 프롬프트와 모델에 제공하는 도구에 의해 결정됩니다. 하지만 Store는 최종 결과를 봅니다: 사용자가 실제로 어떤 답변을 얻는가입니다.
브랜드와 도메인
다음 층은 브랜드와 도메인입니다. 공개 App은 책임 있는 소유자와 연결되어 있어야 합니다. 외부 백엔드/MCP가 있는 App의 경우 다음이 요구됩니다:
도메인 검증(Domain Verification). 여러분 도메인의 DNS에 TXT 레코드를 추가하면, Store는 백엔드가 실제로 여러분의 소유임을 확인합니다. 무료 ngrok URL 같은 익명 구성은 공개 Store에 통과하지 못하거나 낮은 신뢰로 표시될 수 있습니다.
적절한 이름과 로고. "ChatGPT Super Weather"나 "Official OpenAI Something"처럼 이름을 지을 수는 없습니다 — 이름 앞부분에 "GPT / OpenAI / ChatGPT"를 사용하거나 OpenAI의 브랜드 스타일을 모방하는 것은 브랜드 제한에 걸립니다. 고유한 이름(GiftGenius는 좋은 예)과 비주얼 스타일을 만드세요.
UI/UX: ChatGPT를 망가뜨리지 말 것
"예전" 플러그인과 달리 이제 App은 바로 채팅 안에 자체 UI 위젯을 렌더링할 수 있습니다. 이는 많은 기회를 주지만… 망칠 방법도 많습니다.
Store의 간단한 생각은 이렇습니다: 위젯은 ChatGPT에 대해 "네이티브"하게 느껴져야 합니다. 폰트, 여백, 색상, 라이트/다크 테마와 모바일에서의 동작까지 — 광고 배너나 별도의 거대한 SPA를 채팅에 억지로 박아 넣은 느낌 없이 깔끔해야 합니다.
또한 Store는 채팅을 장악하는 UI를 좋아하지 않습니다: 초대형 풀스크린 오버레이, "지금 바로 구독" 모달, 자동 스크롤 등 공격적 패턴은 피하세요. 여러분의 위젯은 대화 안의 카드/마법사/도구이지, 독립된 우주가 아닙니다.
요컨대, 심사는 세 가지를 봅니다: 콘텐츠 정책을 위반하지 않는지, 오해를 유발하지 않는지(이는 강의 후반 리스팅과 매니페스트 일치에서 다시 다룹니다), 그리고 ChatGPT를 나쁜 UX의 광고 쓰레기장으로 만들지 않는지입니다. 여기서의 "정직함"은 App이 실제로 할 수 있는 것과 여러분이 설명과 UI에서 약속하는 것이 일치함을 뜻합니다.
3. Store는 여러분 App의 권한을 어떻게 본다
또 하나의 큰 축은 여러분이 사용자와 외부 시스템에 요청하는 권한입니다. Store는 보안뿐 아니라, 그 권한이 앱이 약속하는 가치와 얼마나 부합하는지도 봅니다.
이제 엔지니어에게 가장 흥미로운 부분: 권한 모델입니다. Apps SDK와 MCP 맥락에는 세 가지 주요 접근 레벨이 있습니다.
시각화하면 다음과 같습니다:
graph TD
A[App 매니페스트/구성] --> B[Model capabilities]
A --> C[OAuth scopes]
A --> D[MCP tools & ACP]
D --> E[사용자 확인 수준]
Model capabilities는 엄밀히 말해 OAuth 스코프나 write‑tools와 같은 의미의 "권한"은 아니고, 모델에 내장된 기능 집합입니다. 하지만 보안 설계 관점에서는 첫 번째 접근 레벨로 보고 최소화하는 것이 유용합니다.
레벨 1: model capabilities
이는 모델이 여러분의 백엔드를 거치지 않고 "자체적으로" 할 수 있는 일입니다: 웹 브라우징, DALL‑E 이미지 생성 등.
브라우징과 MCP-tools를 모두 켜면, 도구 설명이 모호하거나 프롬프트에서 우선순위를 지정하지 않은 경우 특히 모델이 전문 도구 대신 웹 검색으로 문제를 푸는 편이 쉽다고 판단할 수 있습니다. 따라서 App이 이미 MCP를 통해 여러분의 API를 호출한다면 브라우징을 끄거나, 프롬프트에서 MCP-tools의 우선순위를 명확히 고정하는 것이 좋습니다.
즉, 이 레벨에서도 최소 권한 원칙을 적용합니다: App의 실제 가치에 필요하지 않은 것은 끕니다.
레벨 2: OAuth 스코프
App이 인증(모듈 10)을 사용한다면 외부 제공자에게서 스코프를 요청합니다: openid, email, profile, orders.read, orders.write 등.
여기서는 최소화 원칙이 특히 중요합니다:
- 사용자를 구분하기만 하면 된다면 대부분 openid(익명 식별자)로 충분하며, email은 필요하지 않을 수 있습니다.
- email이 꼭 필요하다면 UX와 권한 설명에서 이를 투명하게 밝혀야 합니다: "영수증과 주문 알림을 보내기 위해 필요"와 같이, "그냥 혹시 몰라서"가 아닙니다.
또한 인증을 "필요 시" 요청하려고 노력합니다: 먼저 사용자가 로그인 없이 기본 기능을 체험하게 하고, 실제로 "선물 목록을 즐겨찾기에 저장"하거나 "주문 내역을 확인"하려고 할 때만 접근을 요청합니다. 이는 마찰을 줄이고 전환율을 높입니다.
MCP‑tools를 위한 스코프 구성 예(단순화):
// server/mcp/config/auth.ts
export const OAUTH_SCOPES = {
basic: ["openid"],
orders: ["openid", "orders.read"],
checkout: ["openid", "orders.read", "orders.write"]
};
레벨 3: MCP 도구와 "consequential" 동작
세 번째 레벨은 여러분의 MCP 도구와 ACP/Instant Checkout입니다. MCP 서버의 각 tool은 다음 중 하나일 수 있습니다:
- 읽기 전용(read‑only): 환율 가져오기, 선물 추천, 카탈로그 보기;
- 상태 변경(consequential): 주문 생성, 이메일 발송, 결제 청구.
두 번째 유형의 도구에 대해서 Store는 더 엄격한 확인 모델을 기대합니다. 핵심 아이디어: 모든 것을 "그냥 호출"할 수는 없습니다. 플랫폼 용어로는 보통 consequential: true 플래그와 확인 정책(always_allow vs ask_user)로 표현됩니다.
MCP 도구 등록 예 — security‑schemes와 상태 변경 동작임을 명시:
// server/mcp/tools/createOrder.ts
server.registerTool(
"create_order",
{
title: "Create order",
description: "GiftGenius에서 새 주문을 생성합니다.",
inputSchema: {
type: "object",
properties: {
productId: { type: "string" },
quantity: { type: "integer", minimum: 1 }
},
required: ["productId", "quantity"]
},
_meta: {
securitySchemes: [{ type: "oauth2", scopes: ["orders.write"] }]
},
// 의사 필드: 이 동작은 상태를 변경합니다
consequential: true
},
async ({ input, security }) => {
// ... 주문 생성 로직
}
);
스코프와 security‑schemes 예시는 공식 MCP-tools 문서에서 가져왔으며, 도구는 무인증이거나 OAuth2로 보호될 수 있습니다.
Store 관점에서는 이것이 "이 앱은 GiftGenius 상점에서 주문을 생성하고 관리할 수 있습니다" 같은 명확한 문구와, 필요하다면 별도의 확인 단계로 변환됩니다.
4. 사용자와 심사자의 관점에서 본 권한
우리 엔지니어에게 App은 매니페스트, MCP 서버, 그리고 수많은 TypeScript입니다. Store에게 App은 사용자 데이터와 외부 세계에 대해 무엇을 할 수 있는지에 관한 사실들의 집합입니다.
다음과 같이 정리할 수 있습니다:
| 접근 레벨 | GiftGenius 예시 | Store/사용자에게 보이는 설명 |
|---|---|---|
| Model capabilities | Browsing: off, DALL‑E: off | "App은 스스로 인터넷에 접근하거나 미디어를 생성하지 않습니다" |
| OAuth scopes | openid, orders.read | "여러분의 GiftGenius 계정의 주문을 읽습니다" |
| Read‑only MCP tools | search_products, get_price_history | "카탈로그와 가격 보기" |
| Consequential MCP tools | create_order, cancel_order | "주문 생성 및 취소" |
핵심 아이디어: 각 기술 요소는 사람이 이해할 수 있는 동작으로 매핑되어야 합니다. 모듈 계획에서 이를 명시적으로 요구합니다: 기술적 MCP-tool get_user_orders는 리스팅에서 "당사 상점의 주문 목록 보기" 같은 문구로 변환됩니다.
한두 문장으로 권한을 설명할 수 없다면 경고 신호입니다. 불필요한 권한을 요청하고 있거나, 서로 다른 여러 과제를 하나의 App에 섞었을 가능성이 있습니다.
5. 최소 필요 권한 원칙
일반 백엔드 세계에서는 PoLP(Principle of Least Privilege)를 종종 "DB 역할은 나중에 적당히 제한하자" 정도로 받아들입니다. ChatGPT Apps에서는 "나중에"가 아니라, Store 입점 기준이자 사용자 전환에 영향을 주는 요소입니다.
중요 포인트:
- App이 적은 권한을 요청할수록 사용자의 기본 신뢰가 높습니다. ChatGPT 내부의 대화는 사용자가 일정 수준의 프라이버시를 기대하는 공간입니다. 갑자기 계정 전체, 결제, 연락처 접근을 요구하는 App은 수상해 보입니다.
- 권한이 명확하고 좁을수록 리뷰어가 이해하기 쉽습니다. 심사자는 App이 무엇을 하는지, 그것이 정책과 보안 모범 사례에 얼마나 부합하는지 빠르게 파악해야 합니다. 권한이 과도한 App은 "보류 후 추가 질의"의 단골이고, 때로는 거절되기도 합니다.
- 더 최소화되고 "필요 시" 접근을 구현할수록 UX는 부드러워집니다. 인증 화면은 강한 마찰 지점입니다. App이 인증 전에도 유용한 경험을 제공한다면(예: 사용자 연동 없이 인기 선물 미리 보기), 사용자는 이후 확장 권한에 더 쉽게 동의합니다.
그래서 Store에서의 "최소 권한"은 보안만이 아니라 마케팅과 성장의 문제이기도 합니다. 모듈 18에서는 최소 권한이 관료적 형식이 아니라 경쟁 우위임을 별도로 강조합니다.
6. 예시: GiftGenius 권한, "다이어트" 전후
추상론에 그치지 않도록, 우리의 가상의 주인공 GiftGenius를 보겠습니다. "풀옵션"으로 설계했다고 가정하면 필요한 기능 목록은 다음과 같을 수 있습니다:
- 상품 카탈로그를 읽고 선물을 필터링한다.
- 사용자의 주문 내역을 확인한다.
- 새 주문을 생성하고 기존 주문을 취소한다.
- "즐겨찾는 목록"을 사용자 계정에 저장한다.
- 할인 이메일 알림을 보낸다.
구성 수준에서는 다음과 같이 표현될 수 있습니다:
// server/mcp/config/permissions-naive.ts
export const PERMISSIONS_NAIVE = {
capabilities: { webBrowsing: true, dalle: false },
oauthScopes: ["openid", "email", "orders.read", "orders.write"],
tools: {
searchProducts: { consequential: false },
getUserOrders: { consequential: false },
createOrder: { consequential: true },
cancelOrder: { consequential: true },
saveFavoriteList: { consequential: true },
sendDiscountEmail: { consequential: true }
}
};
겉으로는 그럴듯해 보이지만, Store 첫 릴리스에는 과합니다:
- 바로 주문 내역을 읽을 필요는 없습니다. ACP/Instant Checkout을 통한 안전한 체크아웃(결제 플로우는 플랫폼 통제하에 있음)으로 일회성 추천만으로도 시작할 수 있습니다.
- 이메일 알림은 완전히 별개의 주제입니다: 이메일 저장, Privacy Policy 설명, 수신 거부 처리까지 필요합니다. MVP 단계의 GiftGenius에선 대개 과합니다.
최소화 원칙을 적용해 최소 시작 권한 세트를 구성할 수 있습니다:
// server/mcp/config/permissions-v1.ts
export const PERMISSIONS_V1 = {
capabilities: { webBrowsing: false, dalle: false },
oauthScopes: [], // 로그인 없이 익명으로 동작
tools: {
searchProducts: { consequential: false },
createOrder: { consequential: true }
}
};
이 버전에서는 다음과 같습니다:
- App은 사용자 계정에 "침투"하지 않으며, 주문 내역을 읽거나 이메일을 보내지 않습니다.
- 모든 민감한 작업(주문 생성)은 ACP/Instant Checkout을 통해 진행되어, 사용자가 표준 결제 플로우를 보게 됩니다.
리스팅에는 이렇게 솔직하게 적을 수 있습니다: "GiftGenius 상점에서 선물을 추천하고 주문을 생성합니다. 앱은 여러분의 채팅 기록을 저장하지 않으며 이메일 알림을 발송하지 않습니다." 이는 사용자와 리뷰어 모두에게 호의적입니다.
이후 안정적인 트래픽과 신뢰가 쌓이면 추가 권한(주문 내역, 즐겨찾기)을 포함한 업데이트를 출시하고, 리스팅과 Privacy Policy도 이에 맞춰 갱신할 수 있습니다.
7. 리스팅에서 권한을 설명하는 방법
매니페스트와 구성은 기계의 언어입니다. 심사자와 사용자는 전혀 다른 텍스트(제목, 설명, "이 앱이 할 수 있는 것" 블록, Privacy/Terms 링크)를 읽습니다.
모듈 17에서는 매핑을 강조합니다: 기술적 스코프와 도구 → 사람이 이해하는 동작.
GiftGenius v1의 예시는 다음과 같습니다.
기술적으로:
- Browsing: off
- DALL‑E: off
- MCP tools: search_products (read‑only), create_order (consequential)
리스팅에서는:
- "설명이나 파라미터(성별, 나이, 예산, 관심사)에 따라 선물을 추천합니다."
- "ChatGPT 내의 보호된 체크아웃을 통해 GiftGenius 상점에서 주문을 생성할 수 있습니다."
- "이메일이나 주문 내역 접근을 요청하지 않으며, 알림을 발송하지 않습니다."
나중에 OAuth 로그인과 orders.read를 추가하면, 설명을 정직하게 업데이트합니다:
- "GiftGenius 계정을 연결하면 과거 주문을 확인해 더 개인화된 추천을 제공합니다."
App이 하지 않는 것을 약속하지 않는 것, 그리고 중요한 동작을 숨기지 않는 것이 매우 중요합니다. 모듈 18에 모은 문서들은 리스팅 정보가 실제 동작과 정확히 일치해야 함을 강조합니다. 특히 결제나 PII 같은 민감한 부분에서 그렇습니다.
8. Store 요구사항과 여러분 아키텍처의 연결
Store 요구사항은 진공 속에 존재하지 않습니다. 이 모든 요구사항은 "마케팅의 또 다른 폼"이 아닙니다. Store는 본질적으로 여러분이 보안과 프로덕션 모듈에서 이미 해왔어야 할 것을 확인합니다:
- OAuth를 설정하고 신중하게 .well-known 엔드포인트와 토큰 검증을 구현했다면, App이 갑자기 과도한 스코프로 사용자에게 반 인터넷을 요청하는 것은 이상합니다. 이런 App은 over‑permissioned로 심사에서 쉽게 탈락합니다.
- 보존 정책(retention)과 PII‑scrub을 성실히 구현했다면, 사실에 기반한 Privacy Policy를 작성하고 검증을 통과하기가 쉬워집니다. Store와 사용자는 링크를 눌러 여러분의 약속을 실제 프로세스와 대조할 수 있습니다.
- MCP 서버의 안정성, 로그, 메트릭(관측 가능성과 SLO 모듈)을 잘 다듬었다면, 리뷰어의 성능과 도구 오류 관련 질문이 줄어듭니다.
최소 권한은 이 그림을 멋지게 보완합니다: 여러분은 안전하고 안정적일 뿐만 아니라, 사용자 데이터 접근 요청에서도 "절제"되어 있습니다.
9. 미니 실습
이론에만 머물지 않도록, 지금 바로 여러분의 현재 App(또는 GiftGenius)을 단계별로 점검해 보세요.
먼저 App이 할 수 있는 모든 실제 동작을 적어보세요. 예: "선물 추천", "주문 생성", "내역 보기", "즐겨찾기에 저장", "동료에게 이메일 보내기". 이 단계에서는 기술적 세부사항을 아직 생각하지 말고, 일반 텍스트로 정리합니다.
그 다음, 각 동작에 대해 답해 보세요: "사용자 데이터 중 무엇이 관련되는가?" 그리고 "외부 시스템의 상태를 변경하는가?" 이렇게 하면 동작을 자동으로 read‑only와 consequential로 나눌 수 있습니다.
이후 동작을 권한 레벨과 매핑하세요: 어디에는 model capabilities만 필요한지, 어디에는 OAuth 스코프가 필요한지, 어디에는 MCP 도구와 consequential: true 플래그(그리고 필요 시 사용자 확인)가 필요한지.
이제 "가위질"을 해봅시다: 핵심 가치를 해치지 않고 1차 릴리스에서 무엇을 덜어낼 수 있나요? 종종 내역, 즐겨찾기, 이메일 없이도 App은 본연의 일을 잘합니다. 그렇다면 해당 권한은 1.1이나 2.0으로 미뤄도 됩니다.
10. Store 요구사항과 권한에서 흔한 실수
오류 №1: "모든 걸 다 하는 슈퍼 App을 만들고, Store가 알아서 판단하겠지".
개발자가 App을 범용 어시스턴트("재무, 의료, 법률, 쇼핑을 도와줍니다")로 묘사하고, MCP 도구를 한 뭉텅이 붙인 뒤, 최대한의 권한을 요구합니다. 이런 App은 민감한 도메인(의료/재무/법률)을 동시에 건드리고, 많은 데이터를 요청하며, "one job per app" 원칙을 어깁니다. 결과는 뻔합니다: 심사는 질문을 산더미처럼 던지거나, 그냥 반려합니다. 명확한 권한을 가진 좁은 범위의 App 여러 개로 나누는 편이 좋습니다.
오류 №2: "혹시 몰라서" over‑permissioned 인증.
고전적인 실수입니다: App이 email, profile, orders.read, orders.write, billing.read까지 요청하지만, 실제로는 "설명에 따른 선물 추천"만 필요합니다. 사용자 관점에서는 데이터 욕심이 과한 앱처럼 보이고, Store 관점에서는 위험한 앱입니다. Apps 보안 문서에서도 나쁜 사례로 명시됩니다.
오류 №3: 매니페스트와 리스팅 불일치.
매니페스트에는 create_order, cancel_order와 결제 작업 접근이 있지만, 설명에는 "선물을 추천합니다"만 적혀 있습니다. 언젠가 리뷰어나 사용자가 App이 선언한 것보다 더 많은 일을 한다는 것을 알아차릴 것입니다. 이는 신뢰를 훼손하고 Store에서 제외될 수 있습니다.
오류 №4: "무해한" UI 뒤에 민감한 동작 숨기기.
예를 들어, 위젯에 "목록 저장" 버튼을 그려놓고 실제로는 부서 전체에 이메일을 보내거나 외부 시스템에 작업을 생성하면서도 권한 설명에 이를 밝히지 않는 경우입니다. Store는 깜짝 놀랄 "서프라이즈"를 싫어합니다. 개발자 가이드에는 App이 약속한 일만 정확히 수행해야 하며, 숨겨진 동작이 없어야 한다고 명시되어 있습니다.
오류 №5: 불필요한 "입장 시 로그인" 요구.
App이 시작되자마자 계정 연결과 광범위한 접근을 요구하며, "그렇지 않으면 동작하지 않는다"고 하지만, 실제로는 시나리오의 절반은 익명으로도 구현 가능합니다. 이는 전환율을 떨어뜨리고, 데이터를 먼저 모으려는 인상만 줍니다. App이 실제로 유용함을 먼저 보여준 뒤, 왜 추가 권한이 필요한지 설명하는 편이 훨씬 좋습니다.
오류 №6: 조직적 맥락 무시.
사실상 내부용 기업 도구임에도 "모두를 위한" App을 만들기도 합니다. 그 결과 매우 특수한 권한(내부 CRM, 직원의 민감 데이터 등)을 Store에 끌고 오게 되며, 일반 사용자에게 이를 적절히 설명하기가 어렵습니다. 이런 경우에는 공개 Store가 아니라 org‑only 모드와 내부 리뷰를 목표로 했어야 합니다.
GO TO FULL VERSION