로컬 앱과 운영 앱은 다르다
로컬에서 Apps SDK 앱이 잘 동작한다고 해서 바로 사용자에게 열 수 있는 것은 아니다. 로컬에서는 개발자가 서버를 직접 켜고, 터널을 열고, 로그를 눈으로 확인한다. 운영 환경에서는 이 모든 일이 자동으로 돌아가야 한다. 서버는 항상 HTTPS로 접근 가능해야 하고, 위젯 자산은 안정적으로 내려가야 하며, 인증 콜백 URL과 허용 오리진도 실제 도메인에 맞아야 한다.
운영 배포의 목표는 단순하다. ChatGPT가 호출할 MCP 서버를 공개 URL로 제공하고, 그 서버가 반환하는 위젯 HTML·JS·CSS를 사용자가 빠르게 받을 수 있게 만드는 것이다. OpenAI 예제 저장소도 배포 시 BASE_URL과 API_BASE_URL 같은 환경 변수를 설정해 위젯 HTML과 클라이언트 API URL을 운영 도메인 기준으로 생성하도록 안내한다1.

MCP 서버와 위젯 자산을 어디에 둘 것인가
가장 먼저 정할 것은 MCP 서버와 위젯 정적 자산을 같은 서비스에 둘지, 분리할지다.
작은 앱이라면 같은 서비스에 두는 편이 쉽다. 예를 들어 Node나 Python 서버가 /mcp 엔드포인트를 제공하고, /assets/* 경로로 빌드된 위젯 파일을 서빙한다. 배포 단위가 하나라서 환경 변수, 로그, 롤백을 관리하기 쉽다. 초기 검증이나 사내 도구에는 이 방식이 충분하다.
반대로 위젯 파일이 크거나, 사용자 수가 많거나, 정적 자산 캐싱이 중요하다면 분리하는 편이 낫다. MCP 서버는 도구 호출과 인증, 데이터 접근만 담당하고, 위젯의 HTML·JS·CSS는 CDN이나 정적 호스팅에서 제공한다. 이렇게 하면 위젯 로딩 속도를 개선할 수 있고, 서버 부하도 줄어든다.
기준은 다음처럼 잡으면 된다.
같은 서비스: 초기 제품, 내부 테스트, 트래픽이 작고 배포 단순성이 중요한 경우
분리 배포: 위젯 번들이 크고, 전 세계 사용자에게 빠르게 제공해야 하며, CDN 캐시 전략이 필요한 경우
혼합 방식: MCP 서버는 컨테이너로 운영하고, 빌드된 정적 파일만 객체 스토리지와 CDN으로 배포하는 경우
중요한 것은 위젯 URL이 항상 운영 도메인을 기준으로 생성되어야 한다는 점이다. 로컬의 localhost, 임시 터널 주소, 개발용 정적 서버 주소가 운영 응답의 _meta 안에 남아 있으면 ChatGPT 안에서 위젯이 깨진다.
운영 환경 변수 설계
운영 배포에서 가장 흔한 장애는 코드 문제가 아니라 설정값 문제다. 로컬에서는 .env 하나로 끝나지만, 운영에서는 도메인, 인증, API, 데이터베이스, 캐시, 로그 설정이 모두 분리된다.
기본적으로 다음 값은 환경 변수로 관리한다.
NODE_ENV=production
BASE_URL=https://app.example.com
API_BASE_URL=https://api.example.com
MCP_PATH=/mcp
ASSET_BASE_URL=https://cdn.example.com
DATABASE_URL=postgres://...
REDIS_URL=redis://...
OPENAI_API_KEY=...
OAUTH_CLIENT_ID=...
OAUTH_CLIENT_SECRET=...
OAUTH_CALLBACK_URL=https://app.example.com/oauth/callback
ALLOWED_ORIGINS=https://chatgpt.com,https://app.example.com
MCP_ALLOWED_HOSTS=app.example.com
MCP_ALLOWED_ORIGINS=https://app.example.comBASE_URL은 MCP 서버가 자기 자신을 가리킬 때 사용한다. API_BASE_URL은 위젯이 별도 API를 호출할 때 사용한다. ASSET_BASE_URL은 위젯 번들이 CDN에 있을 때 필요하다. 인증을 사용한다면 OAuth 콜백 URL도 반드시 운영 도메인으로 등록해야 한다.
Python MCP SDK처럼 DNS 리바인딩 방어를 기본으로 적용하는 구현에서는 터널이나 운영 도메인을 허용 호스트와 허용 오리진에 명시해야 한다. 예제 저장소도 ngrok 같은 터널을 쓸 때 MCP_ALLOWED_HOSTS, MCP_ALLOWED_ORIGINS를 설정하라고 안내한다2. 운영에서는 이 값을 넓게 열지 말고 실제 도메인만 허용해야 한다.
비밀값은 코드에 넣지 않는다. 클라우드 플랫폼의 Secret Manager, 환경 변수 그룹, Key Vault, Docker secret 같은 기능을 사용한다. 그리고 개발·스테이징·운영 값을 분리한다. 같은 OAuth 클라이언트와 같은 데이터베이스를 여러 환경이 공유하면 테스트 중 실제 사용자 데이터가 바뀔 수 있다.
클라우드 플랫폼에 올릴 때 확인할 것
Apps SDK 앱은 일반 웹 서비스와 비슷하게 배포할 수 있다. Render, Azure App Service, Azure Container Apps, Cloud Run, Fly.io, ECS, Kubernetes 등 어떤 플랫폼이든 가능하다. 다만 다음 네 가지는 반드시 확인해야 한다.
첫째, HTTPS다. ChatGPT가 접근하는 MCP 서버와 위젯 자산은 공개 HTTPS URL이어야 한다. 플랫폼에서 자동 인증서를 제공하면 가장 단순하다.
둘째, 헬스 체크다. /healthz 같은 경로를 만들고 데이터베이스 연결, 캐시 연결, 필수 환경 변수 존재 여부를 빠르게 검사한다. 단, 외부 API 전체를 매번 호출하면 헬스 체크가 느려지고 비용이 든다. 헬스 체크는 "프로세스가 요청을 받을 수 있는가"를 확인하는 수준이면 충분하다.
app.get("/healthz", async (req, res) => {
const ok = Boolean(process.env.BASE_URL && process.env.DATABASE_URL);
if (!ok) return res.status(500).json({ status: "error" });
return res.json({ status: "ok" });
});셋째, 로그다. MCP 서버는 도구 호출의 입력 스키마 검증 실패, 외부 API 실패, 위젯 메타데이터 생성 오류를 구분해서 남겨야 한다. Apps SDK 앱에서 흔한 문제는 응답 모양이 맞지 않아 ChatGPT 쪽에서 일반적인 오류로 보이는 경우다. 실제 운영 로그에는 도구 이름, 요청 ID, 사용자 또는 테넌트 식별자, 외부 API 상태 코드, 응답 생성 시간을 남겨야 한다. 단, 토큰, 이메일 본문, 결제 정보 같은 민감 데이터는 그대로 기록하지 않는다.
넷째, 롤백이다. 위젯과 MCP 서버는 계약으로 연결된다. 서버가 새 structuredContent를 반환하는데 사용자의 브라우저가 이전 위젯 번들을 캐시하고 있으면 화면이 깨질 수 있다. 정적 자산 파일명에는 해시를 붙이고, 서버 응답의 위젯 템플릿 URI도 버전이 명확해야 한다. 문제가 생기면 서버와 자산을 함께 이전 버전으로 되돌릴 수 있어야 한다. Render 사례에서도 Postgres, Valkey, Node MCP 서버, 정적 위젯 자산을 하나의 운영 묶음으로 배포하고, HTTPS·헬스 체크·로그·롤백을 기본 운영 요소로 다룬다3.
데이터베이스와 캐시 붙이기
사용자 상태가 조금이라도 있다면 메모리에 저장하지 않는다. 서버가 재시작되거나 여러 인스턴스로 늘어나면 메모리 상태는 사라지거나 서로 달라진다.
PostgreSQL은 다음 데이터를 저장하기에 적합하다.
사용자별 설정과 선호도
OAuth 계정 연결 정보
장바구니, 북마크, 작업 목록 같은 지속 상태
도구 호출 결과 중 나중에 다시 보여줘야 하는 데이터
감사 로그와 권한 변경 이력
Redis 또는 Valkey는 짧게 보관할 데이터에 적합하다.
외부 API 응답 캐시
OAuth state와 nonce
짧은 세션 상태
중복 요청 방지 키
rate limit 카운터
예를 들어 영화 추천 앱이 외부 영화 API를 호출한다면, 검색 결과는 Valkey에 몇 분간 캐시하고, 사용자의 watchlist는 PostgreSQL에 저장한다. Render의 Apps SDK 실전 사례도 PostgreSQL을 영속 저장소로, Valkey를 외부 API 캐시로 사용했다4.
캐시는 성능을 높이지만 정답을 오래 붙잡을 수 있다. 가격, 재고, 일정, 권한처럼 자주 바뀌는 값은 TTL을 짧게 잡는다. 사용자별 권한이 적용되는 응답은 캐시 키에 사용자나 테넌트 범위를 포함해야 한다. 그렇지 않으면 A 사용자의 결과가 B 사용자에게 보일 수 있다.
전송 방식과 타임아웃
운영에서는 MCP 전송 방식도 명확히 정해야 한다. 새로 만드는 원격 서버라면 Streamable HTTP를 우선 고려한다. OpenAI Agents SDK 문서도 새 통합에는 Streamable HTTP나 stdio를 선호하고, SSE는 레거시 서버에 유지하라고 설명한다5. Apps SDK 예제는 서버가 스트리밍 HTTP 또는 SSE로 호스팅될 수 있다고 설명하지만6, 장기적으로는 새 표준에 맞춰가는 편이 좋다.
타임아웃도 필요하다. 외부 API가 30초 동안 응답하지 않으면 사용자는 ChatGPT 안에서 멈춘 것처럼 느낀다. 도구별로 제한 시간을 두고, 실패하면 모델이 이해할 수 있는 짧은 오류 메시지를 반환한다.
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 8000);
try {
const response = await fetch(url, { signal: controller.signal });
return await response.json();
} finally {
clearTimeout(timeout);
}읽기 도구는 재시도할 수 있지만, 쓰기 도구는 조심해야 한다. 결제, 예약, 삭제처럼 부작용이 있는 작업은 멱등성 키를 사용한다. 같은 요청이 두 번 들어와도 한 번만 처리되게 만드는 값이다.
성능을 나누어 관찰하기
Apps SDK 앱의 체감 속도는 한 구간으로 설명되지 않는다. 사용자의 프롬프트가 들어온 뒤 실제 위젯이 보이기까지 여러 단계가 있다.
모델이 사용자의 의도를 해석하는 시간
모델이 도구 호출을 결정하는 시간
ChatGPT와 MCP 서버 사이의 네트워크 왕복 시간
MCP 서버 내부 처리 시간
외부 API 호출 시간
데이터베이스와 캐시 접근 시간
모델이 도구 결과를 해석하는 시간
위젯 HTML·JS·CSS 로딩과 렌더링 시간
따라서 "느리다"는 로그만으로는 부족하다. 최소한 다음 타임스탬프를 남긴다.
tool.request_received
tool.validation_finished
db.query_finished
external_api.finished
tool.response_sent
widget.asset_loaded
widget.render_finished서버 로그에는 request_id를 붙이고, 위젯에서도 같은 ID를 _meta나 structuredContent의 추적용 필드로 전달하면 좋다. 그러면 특정 사용자 요청이 서버에서 얼마나 걸렸고, 브라우저 위젯에서 얼마나 걸렸는지 이어서 볼 수 있다. Render의 현장 기록도 전체 ChatGPT 루프에는 모델 추론, MCP 처리, 외부 API, 위젯 렌더링이 모두 포함되므로 각 구간을 나누어 관찰해야 최적화 대상을 알 수 있다고 강조한다7.
운영 배포 체크리스트
배포 전에는 다음을 확인한다.
MCP 서버 URL이 공개 HTTPS로 접근 가능한가
/mcp엔드포인트가 스테이징과 운영에서 분리되어 있는가위젯 자산 URL이
localhost나 임시 터널 주소를 포함하지 않는가BASE_URL,API_BASE_URL, 인증 콜백 URL이 운영 도메인과 일치하는가허용 오리진과 허용 호스트가 과도하게 넓지 않은가
데이터베이스 마이그레이션이 자동 또는 명시적 절차로 실행되는가
Redis·Valkey 캐시에 사용자 간 데이터가 섞이지 않는가
로그에 민감 정보가 남지 않는가
헬스 체크 실패 시 배포가 중단되거나 알림이 가는가
이전 버전으로 롤백할 수 있는가
운영 환경 구성은 화려한 기능이 아니다. 하지만 이 기반이 약하면 좋은 도구 설계와 멋진 위젯도 사용자에게 안정적으로 도달하지 못한다. Apps SDK 앱을 실제 제품으로 만들려면 MCP 서버, 위젯 자산, 데이터 저장소, 캐시, 환경 변수, 관측성을 하나의 운영 시스템으로 보아야 한다.
참고
1OpenAI Apps SDK Examples - Deploy your MCP server
2OpenAI Apps SDK Examples - tunneling and allowed hosts
3Building with the OpenAI Apps SDK: A Field Guide
4Building with the OpenAI Apps SDK: A Field Guide
5Model context protocol (MCP) - OpenAI Agents SDK
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.