AI 에이전트 Koal & KoalStudio

외부 AI에게 우리 서비스를 시킨다 — Aster.duck Skillset 0.4 공개

우리 팀의 AI가 우리 서비스를 만들었어요. 이제 외부 AI가 우리 서비스를 사용해요. Aster.duck Skillset 0.4를 공개하면서, 왜 MCP가 아니라 SKILL.md + OpenAPI 2층 구조를 골랐는지, 어떻게 설계했는지 정리합니다.

사용자 옆에 또 다른 사용자가 있어요 — AI 에이전트

이전 글에서 PO 1명과 에이전트 팀이 주말 동안 사케덕을 만든 과정을 정리했어요. “우리 팀의 AI가 우리 서비스를 만들었다.

그다음 질문이 곧바로 따라왔어요.

사용자도 AI를 통해 우리 서비스를 쓰면 어떻게 되지?

테이스팅 노트를 손가락으로 일일이 입력하는 대신 — “오늘 마신 와인 기록할게, 산미 강하고 타닌 부드러워, 체리·오크 향, 4점” 한 줄로 끝낸다면. 라벨 사진 한 장 던지면 셀러에 들어간다면. “이번 달 안에 마셔야 할 와인 뭐 있어?”가 푸시 알림이 아니라 대화로 풀린다면.

그러려면 외부 AI가 우리 서비스를 사용자처럼 쓸 수 있어야 해요. 우리 서비스의 사용 방식을 AI 에이전트의 언어로 다시 풀어내야 한다는 뜻이에요.

이 글은 그 작업의 결과인 Aster.duck AI Skillset 0.4를 공개하면서, 어떤 결정을 거쳐 지금 모양에 도달했는지 정리해요.

Aster.duck은 와인(WineDuck)과 커피(CoffeeDuck)를 한곳에서 기록하고 다시 꺼내 쓰는 취향 플랫폼이에요. 셀러·테이스팅·시음 적기·취향 기반 추천을 같은 데이터 모델 위에서 굴려요. asterduck.koalstudio.com


두 갈래 길 — MCP vs SKILL.md + OpenAPI

외부 AI에게 서비스를 노출하는 방법은 크게 두 가지가 있었어요.

길 A: MCP (Model Context Protocol) 서버

Anthropic이 만든 오픈 프로토콜이에요. 서비스 측에서 MCP 서버를 띄우면, Claude 같은 AI 클라이언트가 표준 방식으로 연결돼서 툴을 호출할 수 있어요.

장점은 명확해요 — 스펙이 있고, 호환 클라이언트가 늘어날수록 운영 부담은 줄어요.

단점은 — 현 시점에서 클라이언트 풀이 한쪽으로 쏠려요. MCP는 Anthropic 진영이 주도하고, ChatGPT GPTs는 다른 방식(Custom Actions = OpenAPI)을 써요. Cursor는 MCP를 지원하면서도 자체 rules 컨벤션이 따로 있고, Gemini Gems도 결이 또 달라요.

그리고 MCP 서버를 운영하려면 항상 떠 있어야 해요. 우리 BE 외에 별도 서비스 한 축이 더 생기는 셈이에요.

길 B: SKILL.md + OpenAPI 2층 구조

SKILL.md는 AI에게 주는 “사용 매뉴얼”이에요. 마크다운 파일 하나에 “이 스킬은 언제 트리거되고, 어떤 흐름으로 동작하고, 어떤 엔드포인트를 호출하고, 어떤 형식으로 응답을 처리하는지”를 적어두는 방식이에요.

OpenAPI는 엔드포인트의 형식적 스펙(yaml)이에요. 기계가 읽고 자동으로 호출 코드를 만들 수 있는 표준이에요.

이 둘을 같이 두면 — Claude/Cursor 같은 곳은 SKILL.md를 읽어서 자연어로 동작하고, ChatGPT GPTs/Gemini Gems은 OpenAPI를 읽어서 Custom Actions로 동작해요. 하나의 BE, 여러 AI가 같은 길로 들어오게 돼요.

우리가 길 B를 고른 이유

세 가지였어요.

  1. AI 종속을 피하고 싶었어요. Claude만 지원하면 Cursor 쓰는 개발자 사용자, ChatGPT 쓰는 일반 사용자, Gemini로 자기 워크플로 짜는 사람들이 다 빠져요. 우리 서비스의 본질은 “취향 데이터를 다루는 곳”이지 “특정 모델 전용 서비스”가 아니에요.

  2. 운영 부담을 줄이고 싶었어요. 별도 MCP 서버를 띄우면 — 우리 BE 한 곳에 모여 있던 모니터링·배포·장애 대응이 둘로 나뉘어요. 1인 PO 운영에서 인프라 분기를 늘리는 건 비싼 선택이에요.

  3. 사용자 입장에서 설치가 단순했어요. 한 줄 git clone + cp로 끝나요. 서버 URL을 직접 등록하거나 별도로 토큰 인프라를 받는 단계가 없어요.

표준이 정착되면 MCP도 함께 지원할 수 있어요. 지금은 호환 범위와 운영 단순성을 우선시한 선택이에요.


Skillset의 구조 — 트리거가 먼저, 호출은 그다음

매니페스트로 시작해요

저장소 README의 첫 섹션은 “Skill Manifest (AI Routing Reference)“예요. AI 에이전트가 사용자의 말에서 어떤 스킬을 부를지 판단하는 라우팅 테이블이에요.

| Skill Name           | Triggers When User Says...           | Auth |
|----------------------|--------------------------------------|------|
| asterduck-auth       | "로그인", "회원가입", "비밀번호"      | No   |
| wineduck-search      | "와인 검색", "부르고뉴", "와인 찾아"  | No   |
| wineduck-cellar      | "셀러", "내 와인", "셀러에 추가"      | Yes  |
| wineduck-tasting     | "와인 마셨", "와인 테이스팅"          | Yes  |
| wineduck-discovery   | "추천 와인", "내 취향", "비슷한 팔레트" | Partial |
| coffeeduck-search    | "커피 검색", "원두 찾아"              | No   |
| ...                  | ...                                   | ...  |

이 매니페스트가 스킬셋의 입구예요. AI는 사용자 발화 → 매니페스트의 트리거 매칭 → 해당 SKILL.md 로딩 → 그 안의 흐름대로 API 호출이라는 순서로 움직여요.

SKILL.md 한 장의 안

각 스킬은 SKILL.md 한 장에 들어가요. 그 안에 있는 항목들:

  • 언제 트리거되는가 (사용자 발화 패턴, 매니페스트와 동기화)
  • 선행 조건 (인증 필요 여부, 사전 호출이 필요한 다른 스킬 있는지)
  • 흐름 (보통 3~5단계 — 입력 파싱 → 검증 → API 호출 → 응답 정리 → 사용자에게 보고)
  • 호출하는 엔드포인트 (path, method, 요청 형식, 응답 형식의 핵심 필드)
  • 에러 처리 (어떤 케이스에 어떻게 사용자에게 설명할지)
  • 예시 대화 (실제 사용자 입력과 AI가 어떻게 처리하는지)

**원칙은 “AI가 헷갈리지 않게 만든다”**예요. 추측 여지를 남기지 않고, 분기마다 명시적인 가이드를 넣어요. 한 번 잘못 트리거되면 사용자 데이터가 엉뚱하게 들어가니까요.

OpenAPI 한 파일

openapi/asterduck-api.yaml 한 파일에 38개 엔드포인트 전체 스펙이 들어가요. GPTs/Gemini 사용자는 이 파일 URL만 등록하면 끝이에요.

Open: https://raw.githubusercontent.com/ChoiKoal/asterduck_skillset/main/openapi/asterduck-api.yaml
Paste into: Actions / Custom Tools
Auth: Bearer Token (get via /api/auth/login)

OpenAPI는 기계 가독성, SKILL.md는 인간(또는 LLM)의 자연어 이해성 — 둘이 같은 BE를 다른 결로 설명하는 거예요.


인증 — 24시간 토큰, Bearer 패턴

테이스팅 노트와 셀러는 개인 데이터예요. 검색은 모두에게 열려있지만 — “내 셀러에 와인 추가” 같은 동작은 사용자 본인만 할 수 있어야 해요.

그래서 모든 쓰기/개인 데이터 스킬은 JWT Bearer Token 인증을 거치게 했어요.

# 1. 로그인 → 토큰
TOKEN=$(curl -s -X POST .../api/auth/login \
  -d '{"username":"YOUR_ID","password":"YOUR_PW"}' | jq -r '.token')

# 2. 이후 모든 호출
curl -H "Authorization: Bearer $TOKEN" .../api/cellar

토큰은 24시간 유효해요. 만료되면 재로그인. 길게 살아있는 토큰은 잃어버렸을 때 위험이 크고, 짧으면 사용자 경험이 나빠져요. 24시간은 “하루 한 번 로그인하면 그날은 안 끊긴다”는 절충이에요.

매니페스트에서 Auth 컬럼이 Yes/No/Partial로 명시돼 있어요. Partial은 wineduck-discovery처럼 일부 기능은 인증 없이 동작하고(커뮤니티 평균 팔레트), 일부는 본인 토큰 필요한 경우(개인 추천)예요. AI가 “어디서 토큰을 받아야 하는지”를 매니페스트에서 바로 알 수 있어야 흐름이 끊기지 않아요.


한 줄 설치 — 사용자가 처음 만나는 순간

README 상단에 “AI Agent — 한줄 설치” 섹션을 두고, Claude Code / Cursor / ChatGPT 환경별 명령어를 그대로 복사할 수 있게 했어요.

# Claude Code 설치 (한 줄)
git clone https://github.com/ChoiKoal/asterduck_skillset.git /tmp/asterduck_skillset && \
mkdir -p ~/.claude/skills && \
cp -rn /tmp/asterduck_skillset/claude/auth ~/.claude/skills/asterduck-auth && \
cp -rn /tmp/asterduck_skillset/claude/wineduck/cellar ~/.claude/skills/wineduck-cellar
# ... 나머지 스킬 cp 라인은 저장소 README 참조

여기서 한 가지 작은 결정이 있었어요. 사용자가 직접 명령어를 입력하지 않아도 되게 만들었어요.

사용자는 자신의 AI에게 이렇게만 말하면 돼요:

https://github.com/ChoiKoal/asterduck_skillset 이거 설치해줘”

그러면 AI가 README를 읽고 — README 안에 “If you are an AI assistant” 섹션이 있어서 — AI 자신이 정확한 명령어를 골라 실행해요. 사용자 입장에서는 한 문장으로 끝나는 거예요.

설치 후 동작 검증도 자연어로 가능해요.

"와인 Pegaso Zeta 2023 셀러에 등록해줘"
"오늘 마신 와인 기록할게. 산미 강하고 타닌 부드러워. 체리랑 오크 향. 4점."
"라벨 사진 보여줄게. 셀러에 추가해줘. 어제 8만원에 샀어."
"이번 달 안에 마셔야 할 와인 뭐 있어?"
"오늘 푸타네스카 하는데 어울리는 와인 뭐가 좋을까?"

0.1 → 0.4 — 무엇이 늘었나

v0.1.0  wineduck 4종 (auth · search · tasting · wine)
v0.2.0  + coffeeduck 3종 / + OpenAI Codex CLI 호환 트랙 / + OpenAPI 통합
v0.3.0  + wineduck-cellar 가이드 강화 (drink_window / status 다중값 / expiring)
v0.4.0  + wineduck-discovery 신규 (취향 추천 + 커뮤니티 평균 팔레트)

이 진화를 한 줄로 정리하면 — 점점 더 “취향을 다루는 일”에 가까워졌어요.

0.1은 단순한 CRUD였어요. 와인을 등록하고 검색하고 기록하는 것. 0.2는 커피로 확장하면서 “여러 도메인을 다루는 플랫폼”이 됐어요. 0.3은 셀러 운영을 진지하게 — “음용 적기 임박”, “곧 마실 와인” 같은 시점 기반 로직이 들어왔어요. 0.4에서 마침내 취향이 등장했어요 — “내 팔레트와 비슷한 사람들이 좋아한 와인”, “내가 좋아할 만한 와인” 같은 추천이 가능해졌어요.

스킬셋의 진화 방향은 서비스 자체의 진화 방향과 같아요. 서비스가 “기록 도구”에서 “취향 플랫폼”으로 옮겨가는 만큼, 스킬셋도 같은 방향을 따라가요.


운영 시점에서 배운 것

1. 트리거가 흔들리면 모든 게 무너져요

스킬셋 운영에서 가장 흔한 사고는 — 잘못된 스킬이 트리거되는 것이에요.

“커피 추천해줘”는 coffeeduck-search로 가야 하지만, wineduck-discovery가 잘못 잡힐 수 있어요. “셀러에 추가”는 wineduck-cellar인데 등록 의미로 해석돼 wineduck-wine이 잘못 잡힐 수도 있어요.

그래서 매니페스트의 트리거 키워드를 반복해서 다듬어요. 사용자 실제 발화 로그가 쌓이면서, “이 표현은 이 스킬로 가야 한다”가 점점 명확해져요. 매니페스트는 한 번 정하고 끝이 아니라, 지속적으로 캘리브레이션되는 라우팅 정책이에요.

2. 응답 형식은 AI를 위한 게 아니라 사용자를 위한 거예요

API 응답을 그대로 사용자에게 보여주면 — JSON이 노출돼서 사용자 경험이 나빠져요.

각 SKILL.md에는 **“응답 정리 패턴”**이 들어가요. “와인 검색 결과는 상위 5개만, 핵심 3개 필드(생산자/빈티지/평점)로 정리해서 보여줘”처럼요. AI가 알아서 잘하리라 기대하지 않고, 응답 형식까지 SKILL.md에 명시해요.

3. 에러 메시지는 사용자에게 다음 행동을 알려줘야 해요

토큰이 만료됐을 때 “401 Unauthorized”만 던지면 사용자는 멈춰요. SKILL.md에서는 — “401이 떴을 때는 사용자에게 ‘로그인이 만료됐어요, 다시 로그인해주세요’라고 안내하고, asterduck-auth로 즉시 갈아타도록 유도하라”고 명시해요.

에러는 시스템의 사정이고, 사용자에게는 다음 행동만 보이면 돼요.


다음 단계

0.5 후보들

  • wineduck-cellar 페이지네이션 표준화per_page=100 + pagination 검증을 모든 list 호출의 default 패턴으로 가이드 강화. (운영 중 페이지네이션 누락으로 일부 데이터가 안 나오는 케이스를 잡으면서 추가.)
  • 샴페인 RM/NM/CM/RC/ND/MA schema — 와인 등록 시 생산자 타입을 분류하는 메타 추가. 컬트 RM 도멘들이 늘면서 필요.
  • 사진 → 라벨 OCR 표준 흐름 — 셀러 사진 추가는 이미 동작하지만, OCR 인식 신뢰도 가이드를 SKILL.md에 명문화.

1.0의 모양

1.0은 — **“AI를 안 쓰는 사용자와 AI를 쓰는 사용자가 같은 데이터를 본다”**가 완성됐을 때 찍을 거예요. 지금도 같은 BE를 보긴 하지만, AI 경로로 등록된 데이터의 메타 정밀도가 손으로 입력한 것과 완전히 동일해야 해요. 그 보장이 검증되면 그때가 1.0이에요.


정리

우리 팀의 AI가 우리 서비스를 만들었어요. 이게 사케덕 빌드 로그까지의 이야기였어요.

이제 외부 AI가 우리 서비스를 사용해요. 이게 Skillset 0.4의 이야기예요.

이 둘은 같은 흐름이에요. AI를 “사람의 도구”가 아니라 새로운 종류의 사용자로 보는 시점. 잘 설계된 서비스라면 사람이든 AI든 같은 방식으로 쓸 수 있어야 해요.

스킬셋은 그 가설을 확인하는 도구예요. 사용자가 “손가락으로 직접 입력”과 “AI에게 자연어로 요청” 사이를 자유롭게 오갈 수 있다면 — 그게 우리가 만들고 싶었던 플랫폼의 모양이에요.

손가락으로 먼저 써보고 싶으면 asterduck.koalstudio.com에서 가입하고 셀러부터 만들어보면 돼요. 같은 셀러를 그날부터 AI에게도 시킬 수 있어요.

다음 글에서는 — 스킬셋이 실제로 운영되면서 발견한 트리거 라우팅 사고 사례, 그리고 그걸 통해 다듬어진 매니페스트 진화를 정리할 예정이에요.


링크

이전 시리즈 글

댓글

댓글 남기기

댓글 삭제 시 사용됩니다
공개되지 않습니다
0/2000
← 목록으로