๐ค ChatGPT Apps SDK๋ฅผ ํ์ฉํ ์๋ชจ๋ ๋ชฐ ํตํฉ - MCP ์๋ฒ ๊ธฐ๋ฐ AI ์ผํ ์ด์์คํดํธ ๊ตฌ์ถ
OpenAI ChatGPT Apps SDK์ Model Context Protocol(MCP)์ ํ์ฉํ์ฌ ChatGPT ๋ด์์ ์๋ชจ๋ ๋ชฐ ์ํ์ ์ถ์ฒํ๊ณ ๊ฒ์ํ ์ ์๋ AI ์ผํ ์ด์์คํดํธ๋ฅผ ๊ตฌ์ถํ ์ํคํ ์ฒ์ ๊ตฌํ ๊ณผ์ ์ ๊ณต์ ํฉ๋๋ค.

๐ ๊ธ ๊ฐ์
OpenAI๊ฐ ChatGPT์ Apps ๊ธฐ๋ฅ์ ๋์ ํ๋ฉด์, AI ์ด์์คํดํธ๊ฐ ์ง์ ์ธ๋ถ ์๋น์ค์ ์ฐ๋ํ์ฌ ์ค์๊ฐ ์ ๋ณด๋ฅผ ์ ๊ณตํ ์ ์๋ ์๋ก์ด ๊ฐ๋ฅ์ฑ์ด ์ด๋ ธ์ต๋๋ค. OpenAI์ Apps in ChatGPT ์๊ฐ์ ๋ฐ๋ฅด๋ฉด, ๊ฐ๋ฐ์๋ค์ **Model Context Protocol (MCP)**์ ํตํด ChatGPT์ ์์ ์ ์๋น์ค๋ฅผ ์ฐ๊ฒฐํ ์ ์๊ฒ ๋์์ต๋๋ค.
์ด๋ฌํ ๊ธฐ์ ์ ๋ฐฐ๊ฒฝ์์, ์ ํฌ๋ ์๋ชจ๋ ๋ชฐ์ ์ํ ์ ๋ณด๋ฅผ ChatGPT์์ ์ง์ ์กฐํํ๊ณ ์ถ์ฒํ ์ ์๋ AI ์ผํ ์ด์์คํดํธ๋ฅผ ๊ตฌ์ถํ์ต๋๋ค. ์ฌ์ฉ์๋ ChatGPT ๋ํ์ฐฝ์์ ์์ฐ์ด๋ก "์ธ๊ธฐ ์ํ ์ถ์ฒํด์ค" ๋๋ "์คํ์ ์ ํ ์ฐพ์์ค"๋ผ๊ณ ์์ฒญํ๋ฉด, ChatGPT๊ฐ ์๋ชจ๋ ๋ชฐ MCP ์๋ฒ๋ฅผ ํตํด ์ค์๊ฐ์ผ๋ก ์ํ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ ์๊ฐ์ ์ธ ์์ ฏ์ผ๋ก ํ์ํฉ๋๋ค.
๐ก ์ด ๊ธ์์ ๋ค๋ฃฐ ๋ด์ฉ
- ChatGPT Apps SDK์ MCP(Model Context Protocol) ๊ฐ์
- ์๋ชจ๋ ๋ชฐ MCP ์๋ฒ ์ํคํ ์ฒ ์ค๊ณ
- Python FastMCP ์๋ฒ์ React Widget ํตํฉ ๊ตฌ์กฐ
- ๋ฐ์ดํฐ ํ๋ก์ฐ ๋ฐ ํต์ ๋ฉ์ปค๋์ฆ
- ์ค์ ๊ตฌํ ๋ฐ๋ชจ ๋ฐ ์ฌ์ฉ ์ฌ๋ก
๐ฏ ChatGPT Apps SDK์ MCP ๊ฐ์
๐ค Apps in ChatGPT๋?
OpenAI์ ๊ณต์ ๋ฐํ์ ๋ฐ๋ฅด๋ฉด, ChatGPT Apps๋ AI ์ด์์คํดํธ๊ฐ ์ธ๋ถ ์๋น์ค์ ์ง์ ํต์ ํ์ฌ ์ค์๊ฐ ์ ๋ณด๋ฅผ ์ ๊ณตํ ์ ์๊ฒ ํด์ฃผ๋ ๊ธฐ๋ฅ์ ๋๋ค. ์ด๋ฅผ ํตํด ChatGPT๋ ๋จ์ํ ๋ํํ AI๋ฅผ ๋์ด์ ์ค์ ์ก์ ์ ์ํํ๋ ์์ด์ ํธ๋ก ์งํํ ์ ์์ต๋๋ค.
ํต์ฌ ํน์ง:
- Model Context Protocol (MCP): ChatGPT์ ์ธ๋ถ ์๋น์ค๋ฅผ ์ฐ๊ฒฐํ๋ ํ์ค ํ๋กํ ์ฝ
- Widget ๊ธฐ๋ฐ UI: ์๋ฒ์์ ๋ฐํํ ๋ฐ์ดํฐ๋ฅผ ์๊ฐ์ ์ธ ์์ ฏ์ผ๋ก ๋ ๋๋ง
- ์์ฐ์ด ์ธํฐํ์ด์ค: ์ฌ์ฉ์๋ ์์ฐ์ด๋ก ์์ฒญํ๊ณ , ChatGPT๊ฐ ์ ์ ํ Tool์ ์๋์ผ๋ก ํธ์ถ
๐ Model Context Protocol (MCP)
MCP๋ ChatGPT์ ์ธ๋ถ ์๋น์ค ๊ฐ์ ํต์ ์ ์ํ ํ์ค ํ๋กํ ์ฝ์ ๋๋ค. ๋ค์๊ณผ ๊ฐ์ ํต์ฌ ๊ฐ๋ ์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค:
- Tools: ChatGPT๊ฐ ํธ์ถํ ์ ์๋ ํจ์ (์:
get_popular_products,search_products) - Resources: Widget UI ๋งํฌ์
์ ์ ๊ณตํ๋ ๋ฆฌ์์ค (์:
ui://widget/product-carousel.html) - Streamable HTTP: HTTP ๊ธฐ๋ฐ์ ์ค์๊ฐ ํต์ ๋ฉ์ปค๋์ฆ
๐๏ธ ์ํคํ ์ฒ ์ค๊ณ
์๋ชจ๋ ๋ชฐ ChatGPT Apps ํตํฉ์ ๋ค์๊ณผ ๊ฐ์ ๊ณ์ธต ๊ตฌ์กฐ๋ก ์ค๊ณ๋์์ต๋๋ค:
๐ ์ํคํ ์ฒ ๊ตฌ์ฑ ์์
1. MCP Server (Python FastMCP)
- ์ญํ : ChatGPT์์ ํต์ ์ ๋ด๋นํ๋ ์๋ฒ
- ๊ธฐ์ ์คํ: Python, FastMCP, Pydantic, uvicorn
- ์ฃผ์ ๊ธฐ๋ฅ:
- Tool ๋ชฉ๋ก ์ ๊ณต (
list_tools) - Resource ๋ชฉ๋ก ์ ๊ณต (
list_resources) - Tool ์คํ ์ฒ๋ฆฌ (
call_tool) - Resource ์ฝ๊ธฐ ์ฒ๋ฆฌ (
read_resource)
- Tool ๋ชฉ๋ก ์ ๊ณต (
2. Widget UI (React + TypeScript)
- ์ญํ : ์ํ ์ ๋ณด๋ฅผ ์๊ฐ์ ์ผ๋ก ํ์ํ๋ ์์ ฏ
- ๊ธฐ์ ์คํ: React, TypeScript, Vite, Tailwind CSS
- ์ฃผ์ ์ปดํฌ๋ํธ:
ProductCarousel: ์ธ๊ธฐ ์ํ ์บ๋ฌ์ (Inline Carousel)ProductCard: ๋จ์ผ ์ํ ์์ธ ์นด๋ (Inline Card)CartSummary: ์ฅ๋ฐ๊ตฌ๋ ์์ฝ (Inline Card)
3. Product Service
- ์ญํ : Amoremall API Gateway๋ฅผ ํตํ ์ํ ์ ๋ณด Data API์์ ํต์ ๋ฐ ๋น์ฆ๋์ค ๋ก์ง ์ฒ๋ฆฌ
- ๊ธฐ๋ฅ:
- ์ธ๊ธฐ ์ํ ์กฐํ
- ์ํ ๊ฒ์
- ์ํ ์์ธ ์ ๋ณด ์กฐํ
4. Amoremall API Gateway
- ์ญํ : ์ํ ์ ๋ณด Data API์์ ํต์ ์ ๋ด๋นํ๋ ๊ฒ์ดํธ์จ์ด
- ๊ธฐ๋ฅ:
- API ์์ฒญ ๋ผ์ฐํ ๋ฐ ์ธ์ฆ ์ฒ๋ฆฌ
- ์ํ ์ ๋ณด Data ์กฐํ ๋ฐ ๋ฐํ
๐ ๋ฐ์ดํฐ ํ๋ก์ฐ
ChatGPT์์ ์ฌ์ฉ์๊ฐ ์์ฒญ์ ๋ณด๋ด๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋ฐ์ดํฐ ํ๋ก์ฐ๊ฐ ๋ฐ์ํฉ๋๋ค:
๐ ํต์ฌ ํต์ ๋ฉ์ปค๋์ฆ
1. Tool ์ ์ ๋ฐ ๋ฉํ๋ฐ์ดํฐ
๊ฐ Tool์ _meta ํ๋์ Widget ์ ๋ณด๋ฅผ ํฌํจํฉ๋๋ค:
types.Tool(
name="get_popular_products",
description="์๋ชจ๋ ๋ชฐ์์ ์ธ๊ธฐ ์๋ ์ถ์ฒ ์ํ ๋ชฉ๋ก์ ์กฐํํฉ๋๋ค.",
inputSchema={...},
_meta={
"openai/outputTemplate": "ui://widget/product-carousel.html",
"openai/widgetAccessible": True,
}
)
2. Tool ์คํ ๊ฒฐ๊ณผ
Tool ์คํ ์ structuredContent์ ๋ฐ์ดํฐ๋ฅผ ํฌํจํ์ฌ ๋ฐํํฉ๋๋ค:
types.CallToolResult(
content=[types.TextContent(type="text", text="์ธ๊ธฐ ์ํ์ ์ฐพ์์ต๋๋ค.")],
structuredContent={
"products": [...],
"meta": {...}
}
)
3. Widget HTML ๋ก๋
ChatGPT๋ outputTemplate URI๋ฅผ ๊ธฐ๋ฐ์ผ๋ก Resource๋ฅผ ์์ฒญํ๊ณ , HTML์ ๋ ๋๋งํฉ๋๋ค.
๐ป ๊ตฌํ ์์ธ
๐ MCP Server ๊ตฌํ
FastMCP ๊ธฐ๋ฐ ์๋ฒ ๊ตฌ์กฐ:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP(
name="amoremall-app",
stateless_http=True,
)
# Tool ๋ชฉ๋ก ์ ๊ณต
@mcp._mcp_server.list_tools()
async def _list_tools() -> List[types.Tool]:
return [
types.Tool(
name="get_popular_products",
description="...",
inputSchema=GET_POPULAR_PRODUCTS_SCHEMA,
_meta={
"openai/outputTemplate": "ui://widget/product-carousel.html",
"openai/widgetAccessible": True,
}
)
]
# Tool ์คํ ์ฒ๋ฆฌ
async def _call_tool_request(req: types.CallToolRequest) -> types.ServerResult:
tool_name = req.params.name
arguments = req.params.arguments or {}
if tool_name == "get_popular_products":
# Amoremall API Gateway๋ฅผ ํตํด ์ํ ์ ๋ณด Data API ํธ์ถ
products = await product_service.get_popular_products(
category=arguments.get("category", "all"),
limit=arguments.get("limit", 6),
)
return types.ServerResult(
types.CallToolResult(
structuredContent={
"products": [p.model_dump() for p in products],
}
)
)
โ๏ธ Widget UI ๊ตฌํ
React ์ปดํฌ๋ํธ ๊ตฌ์กฐ:
// widgets/src/components/ProductCarousel.tsx
export const ProductCarousel = memo(function ProductCarousel({ products }) {
return (
<div className="product-carousel">
{products.map((product) => (
<ProductCard key={product.onlineProdSn} product={product} />
))}
</div>
);
});
// widgets/src/entries/product-carousel.tsx
import { ProductCarousel } from '@/components';
import { useOpenAI } from '@/hooks';
function App() {
const { toolOutput } = useOpenAI<{ products: Product[] }>();
return <ProductCarousel products={toolOutput?.products || []} />;
}
Vite ๋น๋ ์ค์ :
Widget์ Vite์ Single File Plugin์ ์ฌ์ฉํ์ฌ ๋จ์ผ HTML ํ์ผ๋ก ๋น๋๋ฉ๋๋ค:
// vite.config.ts
export default defineConfig({
plugins: [
viteSingleFile({ removeViteModuleLoader: true }),
],
build: {
rollupOptions: {
input: 'src/entries/product-carousel.tsx',
output: {
entryFileNames: '[name].html',
},
},
},
});
๐ฌ ์ค์ ์ฌ์ฉ ๋ฐ๋ชจ
๋ค์์ ChatGPT์์ ์๋ชจ๋ ๋ชฐ ์ํ์ ์กฐํํ๋ ์ค์ ์ฌ์ฉ ์์์ ๋๋ค:
์ฌ์ฉ ์๋๋ฆฌ์ค:
-
์ธ๊ธฐ ์ํ ์ถ์ฒ
์ฌ์ฉ์: "@amoremall ์ธ๊ธฐ์๋ ์ํ์ ์ถ์ฒํด์ค" ChatGPT: [ProductCarousel ์์ ฏ ํ์] -
์นดํ ๊ณ ๋ฆฌ๋ณ ๊ฒ์
์ฌ์ฉ์: "์คํจ์ผ์ด ๋ฒ ์คํธ์ ๋ฌ ๋ณด์ฌ์ค" ChatGPT: [์คํจ์ผ์ด ์นดํ ๊ณ ๋ฆฌ์ ์ธ๊ธฐ ์ํ ์บ๋ฌ์ ํ์] -
ํค์๋ ๊ฒ์
์ฌ์ฉ์: "์คํ์ ์ ํ ์ฐพ์์ค" ChatGPT: [์คํ์ ๋ธ๋๋ ์ํ ๊ฒ์ ๊ฒฐ๊ณผ ์บ๋ฌ์ ํ์] -
์ํ ์์ธ ์กฐํ
์ฌ์ฉ์: "์ด ์ํ์ ์์ธ ์ ๋ณด ์๋ ค์ค" ChatGPT: [ProductCard ์์ ฏ์ผ๋ก ์์ธ ์ ๋ณด ํ์]
โจ ์ฃผ์ ํน์ง ๋ฐ ์ด์
๐ฏ 1. ์์ฐ์ด ์ธํฐํ์ด์ค
์ฌ์ฉ์๋ ๋ณต์กํ ๊ฒ์ ์กฐ๊ฑด์ ์ ๋ ฅํ ํ์ ์์ด, ์์ฐ์ค๋ฌ์ด ๋ํ๋ก ์ํ๋ ์ํ์ ์ฐพ์ ์ ์์ต๋๋ค.
๐ 2. ์ค์๊ฐ ๋ฐ์ดํฐ ์ฐ๋
ChatGPT๊ฐ ์ง์ ์๋ชจ๋ ๋ชฐ API๋ฅผ ํธ์ถํ์ฌ ์ต์ ์ํ ์ ๋ณด๋ฅผ ์ ๊ณตํฉ๋๋ค.
๐จ 3. ์๊ฐ์ ์์ ฏ UI
ํ ์คํธ ๊ธฐ๋ฐ ์๋ต์ ๋์ด์, ์ค์ ์ํ ์ด๋ฏธ์ง์ ์ ๋ณด๊ฐ ํฌํจ๋ ์ธํฐ๋ํฐ๋ธํ ์์ ฏ์ ์ ๊ณตํฉ๋๋ค.
๐ง 4. ํ์ฅ ๊ฐ๋ฅํ ์ํคํ ์ฒ
์๋ก์ด Tool๊ณผ Widget์ ์ถ๊ฐํ์ฌ ๊ธฐ๋ฅ์ ํ์ฅํ ์ ์์ต๋๋ค.
๐ ๏ธ ๊ฐ๋ฐ ํ๊ฒฝ ์ค์
ํ์ ์๊ตฌ์ฌํญ
- Python 3.10 ์ด์
- Node.js 20 ์ด์
- pnpm (ํจํค์ง ๋งค๋์ )
- ngrok (ChatGPT ์ฐ๋์ฉ)
์ค์น ๋ฐ ์คํ
# 1. Python ๊ฐ์ํ๊ฒฝ ์ค์
cd apps/amoremall-in-sdk
uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"
# 2. Widget UI ์์กด์ฑ ์ค์น
cd widgets
pnpm install
pnpm build:all
# 3. MCP ์๋ฒ ์คํ
cd ..
python -m server.main
# 4. ngrok ํฐ๋ ์ค์ (ChatGPT ์ฐ๋์ฉ)
ngrok http 8787 --host-header=localhost:8787
# 5. ChatGPT์ ์ปค๋ฅํฐ ๋ฑ๋ก
# Settings โ Connectors โ Create
# URL: https://YOUR_NGROK_URL/mcp
๐ OpenAI Guidelines ์ค์
UX Principles
- โ Atomic actions: ๊ฐ Tool์ ๋จ์ผ ๋ชฉ์ ์ํ
- โ Conversational leverage: ์์ฐ์ด ์์ฒญ ์ง์
- โ Native fit: ChatGPT ๋ํ ํ๋ฆ์ ์์ฐ์ค๋ฝ๊ฒ ํตํฉ
- โ
readOnlyHint: ์ฝ๊ธฐ ์ ์ฉ Tool์
readOnlyHint=True์ค์
UI Guidelines
- โ Display Modes: Inline Carousel, Inline Card ์ฌ์ฉ
- โ System Colors: ChatGPT ์์คํ ์์ ์ฌ์ฉ
- โ Typography: ์์คํ ํฐํธ ์คํ ์ฌ์ฉ
- โ Accessibility: WCAG AA ์ค์, ํค๋ณด๋ ์ ๊ทผ์ฑ
- โ
MIME Type:
text/html+skybridge์ฌ์ฉ
๐ ๋ง๋ฌด๋ฆฌ
ChatGPT Apps SDK์ MCP๋ฅผ ํ์ฉํ ์๋ชจ๋ ๋ชฐ ํตํฉ์ ํตํด, AI ์ด์์คํดํธ๊ฐ ์ค์ ์ผํ ์๋น์ค์ ์ฐ๋ํ์ฌ ์ฌ์ฉ์์๊ฒ ์ค์๊ฐ ์ํ ์ ๋ณด๋ฅผ ์ ๊ณตํ ์ ์๋ ์๋ก์ด ๊ฒฝํ์ ๊ตฌํํ์ต๋๋ค.
์ด๋ฌํ ์ ๊ทผ ๋ฐฉ์์ ๋จ์ํ ์ ๋ณด ์ ๊ณต์ ๋์ด์, AI๊ฐ ์ค์ ์ก์ ์ ์ํํ๋ ์์ด์ ํธ๋ก ์งํํ๋ ๋ฏธ๋๋ฅผ ๋ณด์ฌ์ค๋๋ค. ํนํ ์ด์ปค๋จธ์ค ๋ถ์ผ์์๋ ์ฌ์ฉ์๊ฐ ์์ฐ์ด๋ก ์ํ๋ ์ํ์ ์ฐพ๊ณ , AI๊ฐ ์ค์๊ฐ์ผ๋ก ์ถ์ฒํ๊ณ ๋น๊ตํด์ฃผ๋ ์ฐจ์ธ๋ ์ผํ ๊ฒฝํ์ ์ ๊ณตํ ์ ์์ต๋๋ค.
๐ญ ํฅํ ์ ๋ง
- ๊ฐ์ธํ ์ถ์ฒ: ์ฌ์ฉ์์ ๋ํ ํ์คํ ๋ฆฌ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ๋ง์ถคํ ์ํ ์ถ์ฒ
- ์ฅ๋ฐ๊ตฌ๋ ํตํฉ: ChatGPT ๋ด์์ ์ง์ ์ฅ๋ฐ๊ตฌ๋์ ์ถ๊ฐํ๋ ๊ธฐ๋ฅ
- ๊ฒฐ์ ์ฐ๋: AI ์ด์์คํดํธ๋ฅผ ํตํ ์ํด๋ฆญ ๊ฒฐ์ ๊ฒฝํ
- ๋ฉํฐ ๋ธ๋๋ ํ์ฅ: ์๋ชจ๋ ๋ชฐ๋ฟ๋ง ์๋๋ผ ๋ค์ํ ๋ธ๋๋์์ ํตํฉ
๐ ๊ฐ์ฌ์ ๋ง์
์ด ๊ธ์ ์ฝ์ผ์๋ ๋ถ๋ค๊ป์๋ ์ ๊ธ์ด ์กฐ๊ธ์ด๋๋ง ๋์์ด ๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค. ๊ฐ์ฌํฉ๋๋ค.
๐ ๊ด๋ จ ๋งํฌ
OpenAI Apps SDK Quickstart
MCP ์๋ฒ ๊ตฌ์ถ์ ์ํ ๋น ๋ฅธ ์์ ๊ฐ์ด๋
์น์ฌ์ดํธ ๋ฐฉ๋ฌธ โ๐ท๏ธ ํ๊ทธ
#chatgpt
#openai
#apps_sdk
#mcp
#ai
#ecommerce
#amorepacific
#react
#python