시리즈 소개: 우리 서비스 데이터 + LLM = 앱 고유 AI 서비스

이 시리즈는 우리 서비스의 DB 데이터와 LLM을 연동하여, 우리 앱에서만 제공할 수 있는 AI 서비스를 만드는 방법을 탐색하는 과정이다.

방법핵심
1편CLI Child ProcessCLI를 자식 프로세스로 실행, 오케스트레이션 위임
2편직접 구현LLM API 직접 호출 + 오케스트레이터 자체 구현
3편Agent SDK벤더 공식 SDK로 오케스트레이션 위임 + 공식 콜백으로 제어
4편 (본문)Agent SDK 내부 선택지Tool 제공 방식(MCP/로컬)과 행동 지침(Skill) 선택 기준

Agent SDK 내부 선택지

3편에서 Agent SDK를 선택했다. 오케스트레이션은 SDK에 위임하고, 승인·히스토리·MCP 연결은 공식 콜백과 옵션으로 제어하는 구조다.

그런데 SDK 안에서도 선택이 남아 있다:

  • Tool을 어떻게 제공할 것인가? → MCP 서버로 분리 vs 로컬 함수로 등록
  • LLM에게 행동 지침을 어떻게 줄 것인가? → Skill(instructions) 추가 여부

이 선택에 따라 배포 구조, 비즈니스 로직 정확도, 유지보수 비용이 달라진다.

해당 개념이 필요한 이유

3편에서 “Agent SDK를 쓰자”까지는 결정했지만, 실제 구현에 들어가면 이런 질문이 생긴다:

  • 우리 서비스의 Todo CRUD를 MCP 서버로 만들어야 하나, 로컬 함수로 등록해도 되나?
  • LLM이 tool을 더 정확하게 선택하도록 **Skill 파일(행동 지침)**을 추가해야 하나?
  • 1~3편에서 다룬 CLI · 직접 구현 · Agent SDK 각각에서 이 선택지가 어떤 의미를 갖는가?

AS-IS (3편 마무리 시점)

sequenceDiagram
  autonumber
  participant D as 개발자
  participant SDK as Agent SDK

  D->>SDK: SDK 선택 완료
  Note right of D: Tool 제공 방식은? (MCP vs 로컬)
  Note right of D: 행동 지침은? (Skill 추가 여부)
  Note right of D: 조합이 너무 많다...

TO-BE (4편에서 정리)

sequenceDiagram
  autonumber
  participant D as 개발자
  participant SDK as Agent SDK

  D->>D: 3x3 비교표로 전체 선택지 파악
  D->>D: 서비스 요구사항에 맞는 조합 선택
  D->>SDK: Tool + Skill + 옵션 설정
  SDK->>SDK: 오케스트레이션 자동 처리
  Note right of D: 명확한 기준으로 선택 완료

3가지 축: Skill · MCP 서버 · 로컬 Tool

Skill (Instructions) — 행동 전략

LLM에게 제공하는 행동 지침 문서다. 실행 코드가 아니다.

Skill = 전략 레이어 (LLM의 의사결정 가이드)
Tool = 실행 레이어 (실제 기능 수행)
SDK = 제어 레이어 (오케스트레이션 루프)
  • Tool만 등록하면 → LLM이 tool schema(이름, 설명)만 보고 매번 추론
  • Skill을 추가하면 → “이 상황에서는 이 tool을 이 순서로 써라”를 명시

코드 레벨에서는 instructions 파라미터로 주입된다:

# OpenAI Agents SDK — Skill은 instructions로 주입
agent = Agent(
    model="gpt-4.1",
    tools=[get_todos, prioritize],
    instructions="""
      사용자가 일정 정리를 요청하면:
      1. get_todos 호출
      2. 긴급도 분석
      3. prioritize 호출
      4. 정렬된 결과 반환
    """,
)

공식 문서 기준: OpenAI Agents SDK는 Agent(instructions=...), Claude Code는 SKILL.md 파일로 Skill을 정의한다. 둘 다 system prompt에 포함되어 LLM의 행동을 가이드하며, 실행 엔진이 아닌 의사결정 가이드 역할이다.

MCP 서버 — Tool을 외부 서버로 분리

Tool을 별도 프로세스의 표준 서버로 분리하는 방식이다.

  • JSON-RPC 2.0 over stdio로 통신
  • 별도 프로세스로 실행 → 언어 독립적 (TypeScript MCP 서버 + Python Agent 가능)
  • 여러 Agent/클라이언트에서 공유 가능
graph LR
  A[Agent Wrapper] -->|MCP client| B[MCP Server<br/>별도 프로세스]
  B --> C[(DB)]
  B --> D[외부 API]

로컬 Tool — 같은 프로세스 내 함수

Tool을 Agent와 같은 프로세스의 함수로 등록하는 방식이다.

  • SDK가 함수를 직접 실행 — 네트워크 통신 없음
  • 가장 단순한 구조
graph LR
  A[Agent Wrapper] -->|함수 호출| B[Tool 함수<br/>같은 프로세스]
  B --> C[(DB)]

오케스트레이션 주체 3x3 비교표

“누가 오케스트레이션(LLM 호출 → tool 판단 → 실행 → 재호출 루프)을 하느냐” 관점에서, Tool 제공 방식(가로)과 아키텍처 방법(세로)을 교차 비교한다.

  • Skill.md: 행동 지침만 제공. 실행 가능한 tool 없이 LLM 텍스트 응답만 가능
  • MCP 서버 O: Tool을 MCP 서버로 제공
  • MCP 서버 X: MCP 없이 로컬 함수 또는 직접 구현
Skill.mdMCP 서버 OMCP 서버 X
CLI🟢 CLI — Skill을 LLM 컨텍스트로 로드. built-in 도구(Read, Edit, Bash 등)는 사용 가능하지만, 커스텀 tool이 필요하면 MCP 등록 필수🟢 CLI — MCP 서버 자동 연결 + tool discovery → 실행 → 재호출 전부 CLI가 자동 처리🟡 부분 가능 — CLI에 로컬 함수 직접 등록 불가. 단, built-in 도구(Read, Edit, Bash, Glob, Grep 등)는 MCP 없이 동작. 커스텀 tool만 MCP 필수
직접 구현🔴 개발자 — Skill을 system prompt에 포함. LLM 판단 가이드만 제공하며 실행은 별도 구현 필요🟡 개발자 — MCP로 tool 실행은 표준화되지만, 오케스트레이션 루프(LLM 호출→판단→재호출)는 개발자 구현🔴 개발자 — tool 정의 + 오케스트레이션 전부 직접 구현. 완전 커스텀
Agent SDK🟢 SDK — Skill을 instructions로 주입. tool 없이 대화형 응답. tool 추가 시 SDK가 자동 오케스트레이션🟢 SDK + MCP — 완전 위임 구조. tool discovery → 실행 → 재호출 전부 SDK가 처리🟢 SDK — 로컬 tool 등록 + SDK가 함수 직접 실행 + 오케스트레이션 자동

CLI 행 — 공식 문서 기반 보정

“CLI는 단순 실행기다”라는 설명이 일부에서 있지만, 이는 잘못된 전제다.

Claude Code CLI는 두 종류의 tool을 제공한다:

  1. Built-in 도구: Read, Edit, Bash, Glob, Grep, WebSearch, WebFetch 등 — MCP 없이 기본 탑재
  2. 커스텀 tool: MCP 서버 형태로만 등록 가능 — 로컬 함수 직접 등록 API는 없음

MCP 서버가 등록되면 오케스트레이션을 자동으로 처리한다:

  • MCP 서버 spawn → tool 목록 수집 → LLM에 전달 → tool 판단 → 실행 → 재호출

“CLI는 MCP 없으면 tool 실행 불가”가 아니라, **“커스텀 tool 확장에 MCP가 필수”**인 것이다. 이것이 CLI의 구조적 제약이며, Agent SDK와의 핵심 차이점이다:

CLI:       커스텀 Tool = MCP 서버만 가능 (built-in 도구는 MCP 불필요)
Agent SDK: Tool = MCP 서버 + 로컬 함수 모두 가능

직접 구현 행

MCP 서버를 사용하면 tool 실행은 표준화되지만, 오케스트레이션 루프는 여전히 개발자 책임이다. MCP는 “tool 실행 표준”이지 “오케스트레이션 엔진”이 아니다.

Agent SDK 행

어떤 조합이든 오케스트레이션 주체는 SDK다. MCP 서버를 쓰든 로컬 tool을 쓰든, tool call → 실행 → 결과 재주입 → 재판단 루프는 SDK가 자동 처리한다. 차이는 tool 실행 위치(외부 서버 vs 같은 프로세스)뿐이다.

Agent SDK 내에서의 선택 기준

4편의 핵심은 Agent SDK 행이다. 오케스트레이션은 SDK가 처리하는 것이 동일하지만, 3가지 축의 조합에 따라 구현과 아키텍처가 달라진다.

선택 흐름

graph TD
  A[Agent SDK 선택 완료] --> B{Tool을 외부 서버로<br/>분리해야 하나?}
  B -->|여러 앱에서 공유<br/>독립 배포 필요<br/>언어 혼합| C[MCP 서버]
  B -->|단일 앱<br/>공유 불필요<br/>단순 구조| D[로컬 Tool]
  C --> E{반복적 비즈니스<br/>흐름이 있나?}
  D --> E
  E -->|흐름이 고정적<br/>tool 선택 정확도 중요| F[Skill + Tool]
  E -->|단순 CRUD<br/>tool 설명만으로 충분| G[Tool만]

MCP 서버 vs 로컬 Tool

기준MCP 서버로컬 Tool
Tool 위치별도 프로세스같은 프로세스
통신 방식JSON-RPC 2.0 (stdio)함수 호출
배포 구조분산 가능단일 애플리케이션
언어 독립성✅ 가능❌ 동일 언어
여러 Agent에서 공유✅ 가능❌ 불가
복잡도높음낮음
독립 테스트✅ 용이⚠️ Agent와 결합

Skill(Instructions) 추가 여부

기준Tool만 등록Skill + Tool
LLM 판단 기준tool schema(이름, 설명)schema + 행동 지침
판단 자유도넓음좁아짐 (가이드됨)
Tool 선택 정확도보통높음
Multi-step 안정성중간높음
토큰 사용량낮음증가 (instructions 크기만큼)
유지보수단순Skill 파일 관리 필요

Skill과 Tool은 경쟁이 아니라 보완 관계다. Skill은 LLM을 똑똑하게 만들고(의사결정 가이드), Tool은 시스템을 자동화한다(실행 기능). Skill이 tool을 대체하지 않는다.

핵심 구현 코드: Agent SDK 내부 3가지 조합

1. Agent SDK + MCP 서버

Tool을 외부 MCP 서버로 분리한 구조. 3편의 기본 구조와 동일하다.

// Claude Agent SDK — MCP 서버로 tool 제공
const { query: claudeQuery } = await import("@anthropic-ai/claude-agent-sdk");
 
for await (const msg of claudeQuery({
  prompt: userMessage,
  options: {
    mcpServers: {
      "our-service": {                        // 우리 서비스 MCP
        command: "node",
        args: ["./mcp-server/dist/index.js"],
      },
      ...parsedUserMcpConfig,                 // 사용자 기존 MCP
    },
    allowedTools: ["mcp__our-service__*"],  // MCP tool 허용 (와일드카드)
  },
})) {
  yield msg;
}
// SDK가 MCP 서버에서 tool schema 수집 → LLM 호출 → tool 실행 → 재호출 전부 자동

적합한 경우: tool 서버를 독립 배포하거나, 여러 앱에서 공유하거나, 1편의 CLI 방식에서도 동일한 tool을 사용하고 싶을 때.

2. Agent SDK + 로컬 Tool (MCP 없이)

Tool을 같은 프로세스의 함수로 등록한 구조. MCP 서버 없이 가장 단순하다.

# OpenAI Agents SDK — 로컬 함수를 tool로 등록
from agents import Agent, Runner, function_tool
 
@function_tool
def get_today_todos(user_id: str) -> list:
    """사용자의 오늘 할 일 목록을 조회합니다."""
    return db.query("SELECT * FROM todos WHERE date = today AND user_id = ?", [user_id])
 
@function_tool
def prioritize_todos(todos: list) -> list:
    """할 일 목록을 긴급도 기준으로 정렬합니다."""
    return sorted(todos, key=lambda t: t["urgency"], reverse=True)
 
agent = Agent(
    model="gpt-4.1",
    tools=[get_today_todos, prioritize_todos],
)
 
result = await Runner.run(agent, "오늘 할 일 정리해줘")
# SDK가 함수 schema 자동 생성 → LLM 호출 → 함수 직접 실행 → 재호출 전부 자동

@function_tool 데코레이터는 OpenAI Agents SDK의 공식 패턴이다. 함수의 docstring과 type hint에서 tool schema를 자동 생성한다.

적합한 경우: 단일 애플리케이션, 단순 CRUD 중심, tool 공유 불필요, 빠른 프로토타이핑.

3. Agent SDK + Skill(Instructions) + 로컬 Tool

로컬 Tool에 행동 지침(Skill)을 추가한 구조. 오케스트레이션 루프는 동일하지만, LLM의 tool 선택 정확도와 일관성이 높아진다.

# Skill을 instructions로 주입 — 비즈니스 로직 명확화
from agents import Agent, Runner, function_tool
 
@function_tool
def get_today_todos(user_id: str) -> list:
    """사용자의 오늘 할 일 목록을 조회합니다."""
    return db.query(...)
 
@function_tool
def prioritize_todos(todos: list) -> list:
    """할 일 목록을 긴급도 기준으로 정렬합니다."""
    return sorted(...)
 
# 방법 A: Skill 파일을 읽어서 instructions로 주입
with open("skills/todo_planner.md") as f:
    skill_text = f.read()
 
agent = Agent(
    model="gpt-4.1",
    tools=[get_today_todos, prioritize_todos],
    instructions=skill_text,  # ✅ Skill = instructions 레이어
)
 
result = await Runner.run(agent, "오늘 할 일 정리해줘")

Skill 파일 예시: SDK별로 형식이 다르다.

OpenAI Agents SDK — skills/todo_planner.md (순수 텍스트, instructions=에 직접 주입):

# Todo Planner
 
사용자가 일정 관련 요청을 하면:
1. get_today_todos를 호출하여 오늘의 할 일 목록을 가져온다
2. 각 항목의 마감일과 중요도를 분석한다
3. prioritize_todos를 호출하여 우선순위를 정렬한다
4. 정렬된 결과를 사용자에게 자연어로 설명한다
 
주의: 할 일이 없는 경우 "오늘은 할 일이 없습니다"로 응답한다.

Claude Code — SKILL.md (YAML frontmatter + markdown body 2파트 구조 필수):

---
name: todo-planner
description: 사용자의 할 일 목록을 관리하고 우선순위를 정렬한다
allowed-tools:
  - Read
  - Edit
  - mcp__our-service__*
---
 
사용자가 일정 관련 요청을 하면:
1. get_today_todos를 호출하여 오늘의 할 일 목록을 가져온다
2. 각 항목의 마감일과 중요도를 분석한다
3. prioritize_todos를 호출하여 우선순위를 정렬한다
4. 정렬된 결과를 사용자에게 자연어로 설명한다
 
주의: 할 일이 없는 경우 "오늘은 할 일이 없습니다"로 응답한다.

Claude Code의 SKILL.md는 --- YAML frontmatter가 필수다. allowed-tools로 해당 Skill이 사용할 수 있는 도구를 제한할 수 있으며, built-in 도구(Read, Edit 등)와 MCP 도구(mcp__서버명__*) 모두 지정 가능하다.

적합한 경우: 반복적 비즈니스 흐름이 명확하고, tool 선택 순서가 중요하며, LLM의 일관된 동작이 필요할 때.

3가지 조합 비교

MCP 서버로컬 ToolSkill + 로컬 Tool
Tool 위치외부 프로세스같은 프로세스같은 프로세스
오케스트레이션SDK 자동SDK 자동SDK 자동
Tool 선택 정확도schema 의존schema 의존Skill 가이드
복잡도높음낮음중간
적합한 경우멀티앱 공유, 독립 배포단일앱, 단순 CRUD반복적 비즈니스 흐름

주의사항

Skill 과다 사용의 부작용

Skill(instructions)은 system prompt 토큰을 소비한다. Skill을 많이 넣으면:

  • 토큰 사용량 증가 → 비용 상승
  • 과도한 제약 → 예상치 못한 질문에서 오히려 성능 저하
  • instructions가 길어지면 LLM이 중요한 지침을 놓칠 수 있음

핵심 비즈니스 흐름만 Skill로 정의하고, 단순 CRUD는 tool의 함수명·docstring 설명에 맡기는 것이 균형적이다.

MCP 서버의 오버헤드

단일 애플리케이션에서 MCP 서버를 쓰면:

  • 별도 프로세스 spawn + 관리 필요
  • JSON-RPC 통신 오버헤드 (함수 호출 대비)
  • 에러 핸들링 복잡도 증가 (프로세스 충돌, 통신 타임아웃 등)

tool 공유가 필요 없다면 로컬 tool이 더 효율적이다.

Skill 파일과 Tool 이름의 동기화

Skill에 tool 이름을 하드코딩하므로, tool 이름이 변경되면 Skill 파일도 함께 수정해야 한다:

# ❌ tool 이름이 getTodos → get_today_todos로 변경되면 Skill도 수정 필요
사용자가 일정 관련 요청을 하면:
1. getTodos를 호출   ← 이름이 바뀌면 여기도 수정

Skill이 많아질수록 이 동기화 비용이 증가한다.

CLI에서 커스텀 Tool을 쓰고 싶다면

CLI(Claude Code 등)에는 커스텀 함수를 직접 등록하는 API가 없다. (built-in 도구인 Read, Edit, Bash 등은 기본 제공된다.) 우리 서비스의 비즈니스 로직을 tool로 연결하려면:

  1. MCP 서버로 감싸기: 로컬 함수를 MCP 서버로 만들어 CLI에 등록 (권장)
  2. Agent SDK로 전환: 로컬 함수를 직접 등록할 수 있는 Agent SDK 사용

한계점

1. Skill의 유지보수 비용

Skill 파일에 tool 이름과 호출 순서가 명시되므로, tool이 변경될 때마다 Skill도 동기화해야 한다. 자동 검증 메커니즘이 없으므로 수동 관리에 의존한다.

2. 벤더별 instructions 처리 차이

Agent SDK마다 instructions를 처리하는 방식이 다를 수 있다:

  • OpenAI Agents SDK: Agent(instructions=...)로 직접 주입
  • Claude Agent SDK: instructions 옵션 또는 Skill 파일 로딩 메커니즘

3편의 Agent Wrapper에서 이 차이도 흡수해야 한다.

3. 로컬 Tool의 독립 테스트 어려움

로컬 tool은 Agent와 같은 프로세스에서 실행되므로, tool만 독립적으로 테스트하기 어렵다. MCP 서버는 별도 프로세스이므로 독립 테스트가 용이하다.

4. 조합 폭발

Skill · MCP · 로컬 Tool · 벤더별 차이를 고려하면 조합이 많아진다. 서비스 초기에는 **가장 단순한 조합(Agent SDK + 로컬 Tool)**으로 시작하고, 필요에 따라 MCP 분리 → Skill 추가 순으로 진화하는 것이 현실적이다.


시리즈 정리: [LLM 앱 아키텍처 비교 - CLI Child Process|1편]2편(직접 구현) → 3편(Agent SDK) → 4편(Agent SDK 내부 선택지)으로 이어지는 이 시리즈는, “우리 서비스 데이터 + LLM = 앱 고유 AI 서비스”라는 목표를 달성하기 위한 아키텍처 진화 과정이다. Agent SDK를 선택한 후에도, MCP · 로컬 Tool · Skill이라는 선택지를 서비스 요구사항에 맞게 조합해야 한다.

참고 문서