이번 코드는 **“MCP 서버가 제공하는 Tool을 LLM이 사용하고, 그 위에 로컬 SKILL.md를 시스템 지침으로 얹는 구조”**를 이해하는 것이 핵심입니다.
특히 이번 예제에서 가장 중요한 것은 MCP와 Skill이 서로 같은 것이 아니라 역할이 완전히 다르다는 점입니다.
- MCP 서버 → 실제 기능과 데이터를 제공
- MCP 클라이언트 → 서버와 LLM 사이를 연결하고 도구를 전달·실행
- OpenAI LLM → 무엇을 해야 할지 판단하고 결과를 문장으로 만듦
- SKILL.md → LLM에게 “이 업무를 어떤 절차와 기준으로 수행할지” 알려주는 업무 지침서
아래에서 코드 한 줄씩 → 실행 흐름 → 전체 구조 → MCP Prompt/Resource와 Skill의 차이 → 실제 실행 예시 순서로 설명하겠습니다.
이번 예제는 지금까지 공부하신 AI Agent → Tool → MCP → A2A로 이어지는 흐름에서 상당히 중요한 단계입니다.
1. 먼저 전체 그림부터 잡겠습니다
현재 구조를 아주 단순하게 그리면 이렇습니다.
┌─────────────────────────┐
│ 사용자 │
│ "서울 날씨 확인하고 │
│ 정책에 맞춰 브리핑해줘" │
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ MCP Client │
│ │
│ ① SKILL.md 읽기 │
│ ② MCP 서버 연결 │
│ ③ Tool 목록 가져오기 │
│ ④ LLM에게 전달 │
│ ⑤ Tool 실행 요청 │
└────────────┬────────────┘
│
┌────────────────┴────────────────┐
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ SKILL.md │ │ OpenAI LLM │
│ │ │ │
│ "어떻게 일할지" │ │ "무엇을 할지 │
│ 업무 지침서 │ │ 판단" │
└─────────────────┘ └────────┬─────────┘
│
get_city_weather
호출하라고 결정
│
▼
┌─────────────────────────┐
│ MCP Server │
│ │
│ get_city_weather() │
│ │
│ Open-Meteo API 호출 │
│ 서울 날씨 조회 │
└────────────┬────────────┘
│
▼
{"temperature":"22도",
"humidity":"55%",
"condition":"맑음"}
│
▼
MCP Client → LLM
│
▼
┌─────────────────────────┐
│ 최종 브리핑 │
│ │
│ 현재 날씨 │
│ 활동 적합도 │
│ 추천 준비물 │
└─────────────────────────┘
여기서 가장 중요한 포인트가 하나 있습니다.
SKILL.md는 Tool이 아닙니다.
그리고
MCP 서버도 LLM이 아닙니다.
각자의 역할이 다릅니다.
2. 이 시스템을 사람 조직으로 비유해 보겠습니다
이 비유를 기억하면 MCP와 Skill을 훨씬 쉽게 이해할 수 있습니다.
사용자
회사 사장님이라고 생각해 보겠습니다.
"서울 날씨 확인하고 정책에 맞춰 브리핑해줘."
LLM
실제 일을 판단하는 직원입니다.
하지만 직원에게 아무런 업무 지침이 없다면
"서울 날씨는 맑고 22도입니다."
정도만 말할 수도 있습니다.
SKILL.md
그 직원에게 주는 업무 매뉴얼입니다.
예를 들어:
- 도시명을 추출하라.
- 날씨 Tool을 호출하라.
- 기온과 습도를 분석하라.
- 조깅 적합도를 판단하라.
- 결과를 정해진 형식으로 작성하라.
즉,
"어떻게 일해야 하는가?"
를 알려줍니다.
MCP Server
실제 회사의 전문 부서입니다.
예를 들어 기상정보 담당 부서가 있다고 합시다.
"서울 날씨 알려줘."
그러면 기상 담당자가 실제 API를 조회해서
서울 22도, 습도 55%, 맑음
을 가져옵니다.
MCP Tool
그 부서가 가지고 있는 실제 업무 기능입니다.
get_city_weather(location)
즉,
"도시를 주면 날씨를 조회해 주는 기능"
입니다.

3. 그래서 MCP와 Skill의 관계는?
아주 중요합니다.
SKILL.md
│
│ "어떻게 할 것인가?"
▼
LLM
│
│ "그럼 get_city_weather를 호출해야겠군."
▼
MCP Tool
│
│ 실제 실행
▼
MCP Server
│
│ Open-Meteo API
▼
실제 날씨 데이터
즉,
Skill
업무 방법
Tool
업무 수단
MCP Server
업무 수단을 실제로 실행하는 곳
LLM
전체 업무를 판단하고 조정하는 두뇌
입니다.
4. 이제 클라이언트 코드 처음부터 보겠습니다
4-1. import
import asyncio
import json
import os
import pathlib
각각의 역할을 보면:
asyncio
비동기 프로그래밍을 위한 모듈입니다.
이번 MCP 통신은 네트워크 통신이기 때문에:
await session.initialize()
await session.list_tools()
await session.call_tool(...)
같은 비동기 코드가 등장합니다.
json
Tool 호출 결과가 JSON 문자열로 오기 때문에 필요합니다.
또한:
json.loads(tool_call.arguments)
를 통해 LLM이 전달한 JSON 형태의 arguments를 Python 객체로 바꿉니다.
예:
{"location":"서울특별시"}
↓
{
"location": "서울특별시"
}
os
환경변수를 읽기 위해 사용합니다.
os.getenv("OPENAI_API_KEY")
pathlib
파일 경로를 다루기 편하게 해줍니다.
이번에는:
SKILL_PATH = BASE_DIR / "skills" / "weather-consultant" / "SKILL.md"
를 만들 때 사용합니다.
5. MCP 관련 import
from mcp import ClientSession
from mcp.client.sse import sse_client
여기가 중요합니다.
MCP 클라이언트의 핵심입니다.
sse_client
sse_client(server_url)
MCP 서버와 SSE 방식으로 통신할 수 있도록 연결을 만들어 줍니다.
현재 서버는:
mcp.run(
transport="sse",
host="127.0.0.1",
port=8000
)
로 실행되고 있습니다.
따라서 클라이언트에서는:
server_url = "http://localhost:8000/sse"
로 접속합니다.
즉:
MCP Client
│
│ HTTP/SSE
▼
localhost:8000
│
▼
MCP Server
입니다.
6. ClientSession
async with ClientSession(read, write) as session:
이것은 쉽게 말하면
"MCP 서버와 대화하기 위한 세션 객체"
입니다.
이 session을 통해 다음과 같은 일을 합니다.
session.initialize()
session.list_tools()
session.call_tool()
session.get_prompt()
session.read_resource()
즉 MCP의 핵심 기능을 사용할 수 있습니다.
7. .env 읽기
load_dotenv(override=True)
.env 파일에 있는 환경변수를 읽습니다.
예를 들어:
OPENAI_API_KEY=sk-......
OPENAI_MODEL=gpt-5.4-mini
가 있다면 Python에서:
os.getenv("OPENAI_API_KEY")
로 가져올 수 있습니다.
8. SKILL.md 위치를 지정합니다
BASE_DIR = pathlib.Path(__file__).parent
현재 Python 파일이 있는 폴더를 의미합니다.
예를 들어 현재 파일이:
C:\Dev\book_agentic_ai\test_\client.py
라면:
BASE_DIR
는 대략:
C:\Dev\book_agentic_ai\test_
가 됩니다.
그리고:
SKILL_PATH = BASE_DIR / "skills" / "weather-consultant" / "SKILL.md"
가 되면:
C:\Dev\book_agentic_ai\test_\skills\weather-consultant\SKILL.md
가 됩니다.
9. load_skill() 함수
def load_skill(skill_path):
if os.path.exists(skill_path):
with open(skill_path, "r", encoding="utf-8") as f:
return f.read()
raise FileNotFoundError(...)
이 함수는 아주 단순합니다.
SKILL.md 파일을 읽어서 문자열로 가져오는 함수
입니다.
예를 들어 SKILL.md가:
# Weather Consultant Skill
## Instructions
1. 도시명을 추출한다.
2. get_city_weather를 호출한다.
3. 기온과 습도를 분석한다.
...
라면
skill_content
이라는 변수 안에 문서 전체가 문자열로 들어갑니다.
즉:
skill_content = load_skill(SKILL_PATH)
이후에는
skill_content
│
▼
"너는 날씨 컨설턴트다.
도시명을 추출하고...
기온을 분석하고..."
가 됩니다.
10. 여기서 중요한 사실
SKILL.md는 특별한 MCP 기능이 아닙니다.
즉 Python이 그냥 파일을 읽습니다.
with open(...)
그리고 그 내용을:
{"role":"system","content":skill_content}
으로 LLM에게 전달합니다.
이게 이번 예제에서 Skill의 본질입니다.
11. 핵심 함수 시작
async def run_gpt_mcp_skill_agent():
이 함수 안에서 전체 과정이 진행됩니다.
12. MCP 서버 주소
server_url = "http://localhost:8000/sse"
서버가:
localhost
port 8000
SSE
로 떠 있기 때문입니다.
13. Skill 읽기
skill_content = load_skill(SKILL_PATH)
여기서:
SKILL.md
↓
Python
↓
skill_content
↓
LLM의 system message
가 됩니다.
14. MCP 서버 연결
async with sse_client(server_url) as (read, write):
여기서 실제 통신 통로를 만듭니다.
쉽게:
read ← 서버가 보내는 것을 받음
write → 서버에게 요청을 보냄
이라고 생각하면 됩니다.
15. MCP 세션 생성
async with ClientSession(read, write) as session:
이제 session이라는 MCP 통신 객체가 만들어집니다.
그리고:
await session.initialize()
를 실행합니다.
16. initialize()는 무엇인가?
쉽게 말하면:
"안녕하세요. 저는 MCP Client입니다. 서로 어떤 MCP 기능을 지원하는지 확인합시다."
라는 최초 악수(handshake)입니다.
즉:
Client Server
│ │
│──── initialize ────────>│
│ │
│<──── capabilities ─────│
│ │
│ 연결 완료 │
라고 생각하면 됩니다.
17. 그런데 여기서 중요한 부분이 주석 처리되어 있습니다
현재 코드:
# mcp_prompt = await session.get_prompt(...)
# mcp_resource = await session.read_resource(...)
입니다.
즉 현재 실행에서는:
MCP Prompt
사용하지 않습니다.
MCP Resource
사용하지 않습니다.
현재 사용하는 것은:
MCP Tool
입니다.
이 차이를 반드시 기억해야 합니다.
18. MCP에는 크게 세 가지가 있습니다
이번 서버에는:
MCP Server
│
├── Tool
│
├── Resource
│
└── Prompt
가 모두 있습니다.
Tool
@mcp.tool()
def get_city_weather(...)
실제로 무엇인가를 수행합니다.
예:
서울 날씨 조회
Resource
@mcp.resource("mcp://weather/policy")
정보를 읽어옵니다.
예:
폭염 기준
불쾌지수 기준
야외활동 권장 기준
Prompt
@mcp.prompt()
def weather_briefing(...)
프롬프트 템플릿을 제공합니다.
예:
너는 대한민국 최고의 기상 캐스터다.
19. 현재 클라이언트는 이 세 가지 중 무엇을 쓰는가?
현재는:
Tool O
Resource X
Prompt X
Skill O
입니다.
따라서 구조는:
SKILL.md
│
▼
LLM
│
▼
MCP Tool
│
▼
MCP Server
입니다.
20. input_items가 매우 중요합니다
input_items = [
{"role":"system","content":skill_content},
{
"role":"user",
"content":"현재 서울특별시 날씨 확인하고, 정책에 맞춰 브리핑해줘."
},
]
이것이 LLM에게 전달되는 실제 대화 내용입니다.
결국 LLM 입장에서는:
SYSTEM
--------------------------------
[SKILL.md 전체 내용]
사용자의 위치 정보와 날씨 데이터를 결합하여...
1. 도시명을 추출하고...
2. get_city_weather를 호출하고...
3. 기온과 습도를 분석하고...
...
--------------------------------
USER
--------------------------------
현재 서울특별시 날씨 확인하고,
정책에 맞춰 브리핑해줘.
--------------------------------
라고 보게 됩니다.
21. 여기서 Skill의 진짜 역할이 드러납니다
사용자가 단순히:
서울 날씨 알려줘.
라고 했다고 생각해 봅시다.
LLM은 일반적으로:
서울은 현재 22도이고 맑습니다.
라고 할 수도 있습니다.
그런데 SKILL.md가 있으면:
단순 날씨 조회가 아니라 활동 적합도까지 분석해야 한다.
라고 알게 됩니다.
즉 Skill은 Agent의 행동 방식을 규정하는 업무 지침입니다.
22. 이제 MCP Tool 목록을 가져옵니다
mcp_tools = await session.list_tools()
이것은 서버에게:
"당신이 제공하는 Tool들을 알려주세요."
라고 묻는 것입니다.
서버에는 현재:
@mcp.tool()
def get_city_weather(location: str) -> str:
가 있으므로 이 Tool이 반환됩니다.
23. 아주 중요한 변환
gpt_tools = [
{
"type":"function",
"name":t.name,
"description":t.description,
"parameters":t.input_schema,
}
for t in mcp_tools.tools
]
여기서 MCP Tool → OpenAI Tool 변환이 일어납니다.
이것이 이번 코드에서 가장 중요한 부분 중 하나입니다.
24. 왜 변환해야 하는가?
MCP와 OpenAI는 서로 다른 프로토콜/규격입니다.
MCP 서버가 이해하는 Tool 정의와 OpenAI Responses API가 이해하는 Tool 정의를 연결해야 합니다.
개념적으로:
MCP Server
│
│ MCP Tool 정의
▼
Client
│
│ 변환
▼
OpenAI Tool 정의
│
▼
LLM
입니다.
25. 실제로 어떻게 변환되는가?
MCP에서:
name:
get_city_weather
description:
...
input_schema:
{
"type": "object",
"properties": {
"location": {
"type": "string"
}
}
}
같은 정보가 오게 됩니다.
클라이언트가 이것을 OpenAI 형식으로:
{
"type": "function",
"name": "get_city_weather",
"description": "...",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string"
}
}
}
}
처럼 전달합니다.
26. 그래서 LLM은 Tool을 알게 됩니다
이제 LLM에게:
너에게 다음 도구가 있다.
get_city_weather(location)
라고 알려준 셈입니다.
중요한 것은:
LLM이 Python 함수를 직접 실행하는 것이 아닙니다.
LLM은 단지:
"이 Tool을 사용해야 한다."
라고 결정합니다.
실제 실행은 MCP Client가 합니다.
27. 이 구조가 Agent의 핵심입니다
LLM
│
"날씨 Tool 필요"
│
▼
MCP Client
│
call_tool(...)
│
▼
MCP Server
│
get_city_weather()
│
▼
Open-Meteo API
이것이 바로 Tool Calling Agent입니다.
28. OpenAI Client 생성
openai_client = OpenAI(
api_key=os.getenv("OPENAI_API_KEY","")
)
OpenAI API를 사용할 클라이언트를 만듭니다.
그리고:
model = os.getenv(
"OPENAI_MODEL",
"gpt-5.4-mini"
)
환경변수에 모델명이 없으면 기본값으로 gpt-5.4-mini를 사용합니다.
29. 첫 번째 LLM 호출
response = openai_client.responses.create(
model=model,
input=input_items,
tools=gpt_tools if gpt_tools else None,
tool_choice="required",
)
여기서 매우 중요한 일이 벌어집니다.
LLM에게:
Skill
+
사용자 질문
+
사용 가능한 Tool
을 모두 줍니다.
즉:
LLM에게 전달
┌──────────────────────────────┐
│ System │
│ SKILL.md │
├──────────────────────────────┤
│ User │
│ 서울 날씨 확인해줘 │
├──────────────────────────────┤
│ Tools │
│ get_city_weather(location) │
└──────────────────────────────┘
30. tool_choice="required"는 무엇인가?
이 부분도 중요합니다.
tool_choice="required"
는 사실상:
"이번 요청에서는 반드시 Tool을 사용해라."
라는 의미입니다.
따라서 LLM은 그냥:
서울은 날씨가 좋습니다.
라고 답하는 것이 아니라 Tool을 호출해야 합니다.
31. 그러면 LLM이 하는 일
LLM은 SKILL.md를 보고:
1. 도시명을 추출
2. get_city_weather 호출
3. 기온/습도 분석
4. 활동 적합도 판단
5. 지정된 형식으로 출력
이라는 업무를 이해합니다.
그래서 Tool 호출을 생성합니다.
개념적으로:
{
"name": "get_city_weather",
"arguments": {
"location": "서울특별시"
}
}
가 됩니다.
32. 하지만 여기서 아주 중요한 점
LLM은 날씨를 직접 가져오지 않습니다.
LLM은 단지:
"get_city_weather를 서울특별시라는 인자로 호출해야겠다."
라고 결정합니다.
그 결과가:
response.output
에 들어옵니다.
33. 이제 Tool Call을 찾습니다
input_items.extend(response.output)
기존 대화 기록에 LLM의 응답을 추가합니다.
그리고:
tool_calls = [
item
for item in response.output
if item.type == "function_call"
]
를 통해 Tool 호출만 찾아냅니다.
34. Tool Call을 실행합니다
for tool_call in tool_calls:
그리고:
tool_name = tool_call.name
예:
get_city_weather
가 됩니다.
35. 인자를 JSON에서 Python으로 바꿉니다
tool_args = json.loads(tool_call.arguments)
LLM이:
{
"location": "서울특별시"
}
를 반환했다면 Python에서는:
{
"location": "서울특별시"
}
라는 dictionary가 됩니다.
36. 진짜 MCP 호출
이 줄이 핵심입니다.
result = await session.call_tool(
tool_name,
tool_args
)
즉:
LLM
│
│ "get_city_weather를 호출해"
▼
MCP Client
│
│ session.call_tool()
▼
MCP Server
│
▼
get_city_weather("서울특별시")
입니다.
37. 서버에서는 무슨 일이 벌어지는가?
지난번에 설명했던 서버 코드가 실행됩니다.
def get_city_weather(location: str) -> str:
그리고:
geo_url = ...
Open-Meteo Geocoding API를 호출합니다.
서울의 위도/경도를 가져옵니다.
예:
latitude
37.xxx
longitude
126.xxx
그리고 다시:
Open-Meteo Forecast API
를 호출합니다.
38. 서버가 최종적으로 만들어 주는 데이터
예를 들어:
{
"location": "서울특별시",
"condition": "맑음",
"temperature": "22도",
"humidity": "55%"
}
같은 결과를 반환한다고 생각하면 됩니다.
이 데이터가 MCP를 통해 Client로 돌아옵니다.
39. Client에서 결과를 꺼냅니다
result_text = result.content[0].text
결국:
result_text
=
'{"location":"서울특별시",
"condition":"맑음",
"temperature":"22도",
"humidity":"55%"}'
같은 문자열이 됩니다.
40. 그런데 아직 끝난 것이 아닙니다
이 부분이 Agent Loop의 핵심입니다.
Tool을 실행했다고 바로 사용자에게 보여주는 것이 아닙니다.
LLM에게:
"네가 요청한 Tool을 실행했고 결과가 나왔다."
라고 다시 알려줘야 합니다.
그래서:
input_items.append(
{
"type": "function_call_output",
"call_id": tool_call.call_id,
"output": result_text,
}
)
를 합니다.
41. 이것을 그림으로 보면
1차 LLM 호출
User
↓
Skill
↓
LLM
↓
"get_city_weather 서울 호출"
↓
MCP Client
↓
MCP Server
↓
Open-Meteo
↓
날씨 결과
여기까지가 첫 번째 사이클입니다.
그런데 아직 최종 답변이 없습니다.
그래서:
날씨 결과
↓
LLM
↓
Skill의 규칙에 따라 분석
↓
최종 답변
을 한 번 더 합니다.
42. 두 번째 LLM 호출
final_response = openai_client.responses.create(
model=model,
input=input_items,
)
이번에는 Tool이 실행된 결과까지 포함된 상태입니다.
LLM은 이제:
SKILL.md
+
사용자 질문
+
Tool 호출
+
Tool 실행 결과
를 모두 알고 있습니다.
43. 그래서 Skill의 비즈니스 로직을 적용합니다
SKILL.md에:
조깅:
15~25도
습도 60% 미만
→ 최적
이라고 되어 있습니다.
Tool 결과가:
22도
습도 55%
맑음
이라면 LLM은:
22도 → 15~25도
55% → 60% 미만
을 판단해서:
조깅하기에 최적
이라고 만들 수 있습니다.
44. 최종 출력 형식도 Skill이 결정합니다
SKILL.md:
현재 날씨:
[도시명][기온]도, [상태]
조언:
[활동명]을(를) 하기에 [적합도]한 날씨입니다.
가이드:
[필요한 준비물이나 주의사항]
따라서 LLM이 이런 식으로 답할 수 있습니다.
현재 날씨: 서울특별시 22도, 맑음
조언: 조깅을 하기에 최적의 날씨입니다.
가이드: 가벼운 운동복과 물을 준비하시고,
쾌적한 날씨라도 충분한 수분 섭취를 권장합니다.
45. 이것이 이번 예제의 핵심 전체 Loop입니다
아주 중요하므로 하나의 그림으로 다시 보겠습니다.
┌──────────────────────┐
│ 사용자 │
│ 서울 날씨 브리핑해줘 │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ SKILL.md │
│ │
│ 어떻게 업무를 할지 │
│ 업무 절차/규칙/출력 │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ LLM │
│ │
│ Tool이 필요하다고 판단 │
└──────────┬───────────┘
│
│ Tool Call
▼
┌──────────────────────┐
│ MCP Client │
│ │
│ session.call_tool() │
└──────────┬───────────┘
│
│ MCP
▼
┌──────────────────────┐
│ MCP Server │
│ │
│ get_city_weather() │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Open-Meteo API │
└──────────┬───────────┘
│
▼
날씨 데이터
│
▼
┌──────────────────────┐
│ MCP Client │
│ Tool 결과 전달 │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ LLM │
│ │
│ Skill 규칙 적용 │
│ 날씨 분석 │
│ 브리핑 작성 │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ 사용자 │
│ 최종 브리핑 │
└──────────────────────┘
46. 그런데 여기서 아주 중요한 질문이 생깁니다
"그러면 MCP Resource의 Policy는 왜 안 쓰고 있지?"
맞습니다.
현재 사용자 요청은:
"현재 서울특별시 날씨 확인하고, 정책에 맞춰 브리핑해줘."
입니다.
그런데 현재 코드에서:
mcp_resource = await session.read_resource(
"mcp://weather/policy"
)
가 주석 처리되어 있습니다.
즉 실제로 Policy Resource를 읽지 않습니다.
이것은 이번 코드에서 상당히 중요한 포인트입니다.
47. 현재 코드의 "정책"은 사실 SKILL.md입니다
SKILL.md에는:
조깅 15~25도
습도 60% 미만
30도 이상 또는
습도 70% 이상
→ 주의
라는 규칙이 있습니다.
따라서 현재 실제로 LLM에게 제공되는 정책은:
SKILL.md
입니다.
반면 MCP Server의 Resource에는:
폭염 주의보
불쾌지수 높음
야외 활동 권장
이 별도로 있습니다.
하지만 현재는 읽지 않습니다.
48. 이것은 아주 좋은 학습 포인트입니다
현재 구조는:
MCP Server
┌─────────────┐
│ Tool │
│ Resource │
│ Prompt │
└──────┬──────┘
│
Client가
Tool만 사용
│
▼
LLM
▲
│
SKILL.md
입니다.
즉 MCP Server가 제공하는 모든 기능을 반드시 사용하는 것은 아닙니다.
49. MCP Prompt와 SKILL.md는 상당히 비슷해 보입니다
실제로 그렇습니다.
서버에:
@mcp.prompt()
def weather_briefing(location):
가 있고,
로컬에:
SKILL.md
가 있습니다.
둘 다 LLM에게 지침을 줄 수 있습니다.
하지만 성격이 다릅니다.
50. MCP Prompt
서버가 제공합니다.
MCP Server
│
└── Prompt
│
▼
Client
│
▼
LLM
즉:
서버가 제공하는 프롬프트 템플릿
입니다.
51. SKILL.md
클라이언트/호스트가 로컬에서 관리합니다.
Client
│
└── SKILL.md
│
▼
LLM
즉:
호스트/에이전트가 관리하는 업무 전문 지침
으로 볼 수 있습니다.
52. 쉽게 비교하면
| 목적 | 기능 실행 | 정보 제공 | 프롬프트 제공 | 업무 지침 |
| 핵심 질문 | 무엇을 할 수 있나? | 어떤 정보를 읽을까? | 어떤 프롬프트를 쓸까? | 어떻게 업무할까? |
| 실행 | O | X | X | X |
| 서버에 존재 | O | O | O | 보통 Client/Host |
| LLM 직접 실행 | X | X | X | X |
| 이번 코드 | 사용 | 미사용 | 미사용 | 사용 |
53. 특히 Tool과 Skill의 차이를 확실히 기억하세요
예를 들어 SKILL.md에:
서울 날씨를 확인하라.
라고 적어 놓는다고 해서 날씨가 조회되는 것은 아닙니다.
왜냐하면 Skill은:
지침
이기 때문입니다.
실제 조회는:
get_city_weather()
라는 Tool이 해야 합니다.
따라서:
Skill
"날씨를 조회해야 한다."
↓
LLM
"get_city_weather를 사용해야겠군."
↓
Tool
"실제로 날씨를 조회한다."
입니다.
54. 이것이 Agent의 핵심 철학입니다
AI Agent를 아주 간단히 표현하면:
LLM + Instructions + Tools + Loop
라고 볼 수 있습니다.
이번 예제에서는:
LLM
+
SKILL.md
+
MCP Tool
+
Tool 결과를 다시 LLM에게 전달하는 Loop
입니다.
55. 그런데 여기서 한 단계 더 발전하면
지금은:
tool_choice="required"
라서 무조건 Tool을 사용합니다.
실제 Agent에서는 보통:
사용자 질문
↓
LLM
↓
Tool이 필요한가?
↙ ↘
Yes No
↓ ↓
Tool 실행 바로 답변
↓
결과 분석
↓
최종 답변
처럼 움직입니다.
이것이 보다 일반적인 Agent 구조입니다.
56. 현재 코드의 Agent Loop는 사실 1회성입니다
현재는:
LLM
↓
Tool
↓
LLM
↓
끝
입니다.
즉 Tool을 한 번 호출합니다.
하지만 앞으로 공부하게 될 Agent는:
LLM
↓
Tool A
↓
LLM
↓
Tool B
↓
LLM
↓
Tool C
↓
LLM
↓
최종 답변
도 가능합니다.
더 나아가:
LLM
↓
계획
↓
Tool
↓
관찰
↓
판단
↓
Tool
↓
관찰
↓
판단
↓
완료
가 됩니다.
이것이 우리가 앞서 공부했던 Agent의 Tool Loop와 연결됩니다.
57. A2A와도 연결됩니다
이 부분은 지금까지 공부한 것과 연결해서 이해하면 굉장히 좋습니다.
현재:
사용자
↓
Agent
↓
MCP Tool
↓
Weather Server
입니다.
A2A를 추가하면:
사용자
↓
Manager Agent
↓
├── Weather Agent
│ ↓
│ MCP
│ ↓
│ Weather API
│
├── Travel Agent
│ ↓
│ MCP
│
└── Restaurant Agent
↓
MCP
같은 구조로 발전할 수 있습니다.
58. 즉 MCP와 A2A는 경쟁 관계가 아닙니다
이것도 매우 중요합니다.
A2A
Agent ↔ Agent
MCP
Agent ↔ Tool / Resource
라고 이해하면 됩니다.
예를 들어:
Manager Agent
│
┌────────┴────────┐
▼ ▼
Weather Agent Travel Agent
│ │
│ MCP │ MCP
▼ ▼
Weather Tools Travel Tools
이렇게 조합할 수 있습니다.
59. 그리고 Skill은 그 위에 올라갑니다
예를 들어:
Weather Agent
│
├── SKILL.md
│
├── MCP Tool
│
└── LLM
즉:
A2A = Agent 간 통신
MCP = Agent가 외부 기능을 사용하는 표준 방식
Skill = Agent에게 업무 전문성을 부여하는 지침
LLM = 판단/추론
Tool = 실제 실행
이라는 큰 그림을 잡으시면 됩니다.
60. 이번 예제에서 가장 중요한 7개 개념
이번 코드를 공부하면서 아래 7개만 확실히 머릿속에 남기시면 상당히 잘 이해하신 것입니다.
① MCP Server
"나는 이런 기능과 데이터를 제공한다."
② MCP Tool
"실제로 이것을 실행할 수 있다."
③ MCP Resource
"이 정보를 읽을 수 있다."
④ MCP Prompt
"이런 프롬프트 템플릿을 사용할 수 있다."
⑤ MCP Client
"LLM과 MCP Server 사이를 연결한다."
⑥ SKILL.md
"이 업무는 이런 절차와 기준으로 수행해라."
⑦ LLM
"주어진 지침과 Tool을 이용해 무엇을 해야 할지 판단하고
최종 답변을 만든다."
61. 마지막으로 이번 코드를 한 문장씩 번역해 보면
코드 전체를 사람 말로 번역하면 거의 이렇게 됩니다.
① SKILL.md를 읽는다.
② MCP 서버의 8000번 SSE 주소에 접속한다.
③ MCP 서버와 세션을 초기화한다.
④ MCP 서버가 제공하는 Tool 목록을 가져온다.
⑤ MCP Tool을 OpenAI Tool 형식으로 변환한다.
⑥ SKILL.md를 System Prompt로 넣는다.
⑦ 사용자의 질문을 넣는다.
⑧ LLM에게 질문 + Skill + Tool 목록을 전달한다.
⑨ LLM이 "get_city_weather를 사용해야 한다"고 판단한다.
⑩ Client가 MCP 서버에 get_city_weather를 호출한다.
⑪ MCP Server가 Open-Meteo API를 호출한다.
⑫ 실제 날씨 데이터가 돌아온다.
⑬ Client가 그 결과를 LLM에게 다시 전달한다.
⑭ LLM이 SKILL.md의 규칙에 따라 날씨를 분석한다.
⑮ LLM이 정해진 Output Format으로 최종 브리핑을 만든다.
⑯ 사용자에게 결과를 보여준다.
62. 그리고 지금 단계에서 가장 중요한 개념도를 하나 기억하세요
제가 지금까지 공부하신 내용을 하나로 합치면 다음과 같습니다.
┌───────────────┐
│ 사용자 │
└───────┬───────┘
│
▼
┌───────────────────────┐
│ AI Agent │
│ │
│ LLM │
│ ▲ │
│ │ │
│ SKILL.md │
│ "어떻게 일할까?" │
└─────────┬─────────────┘
│
Tool 선택/호출
│
▼
┌───────────────────────┐
│ MCP Client │
│ │
│ MCP ↔ LLM 변환/연결 │
└─────────┬─────────────┘
│
MCP Protocol
│
▼
┌───────────────────────┐
│ MCP Server │
│ │
│ ┌─────────────────┐ │
│ │ Tool │ │
│ │ get_weather() │ │
│ └─────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ Resource │ │
│ │ Policy │ │
│ └─────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ Prompt │ │
│ │ weather briefing│ │
│ └─────────────────┘ │
└─────────┬─────────────┘
│
▼
┌───────────────────────┐
│ External API / DB │
│ Open-Meteo 등 │
└───────────────────────┘
그리고 A2A까지 연결하면:
사용자
│
▼
┌───────────────┐
│ Manager Agent │
└───────┬───────┘
│ A2A
┌───────────┼───────────┐
▼ ▼ ▼
Weather Agent Travel Agent Other Agent
│
Skill
│
LLM
│
MCP
│
▼
Weather Server
│
Tool
│
▼
External API
이 그림이 앞으로 AI Agent 공부에서 상당히 중요한 전체 지도가 됩니다.
마지막으로 딱 하나만 정리하면
이번 코드에서 가장 중요한 문장은 이것입니다.
SKILL.md는 “무엇을 어떻게 해야 하는지”를 알려주고, MCP Tool은 “실제로 무엇인가를 수행”하며, MCP Client는 둘 사이에서 LLM과 MCP Server를 연결하고, LLM은 전체 과정을 판단·조정한다.
그리고 현재 예제는 MCP Server의 Tool만 실제 사용하고 있으며, Server에 정의된 Resource와 Prompt는 일부러 주석 처리해 놓은 상태입니다.
따라서 다음 단계에서는 이 코드가 더 재미있어집니다. 현재 SKILL.md + MCP Tool 구조에 MCP Resource(Policy)와 MCP Prompt(weather_briefing)까지 실제로 연결하면, “Skill은 로컬 업무 매뉴얼이고 MCP Resource/Prompt는 서버가 제공하는 업무 지식·프롬프트”라는 차이를 직접 체험할 수 있습니다.
### SKILL.md
'Agentic AI > AI_AGENT' 카테고리의 다른 글
| A2A. Weather Client (0) | 2026.10.03 |
|---|---|
| A2A. Weather Agent Server (0) | 2026.10.03 |
| AI Agent(3/3): MCP(Model Context Protocol) 서버와 MCP 클라이언트(LLM 에이전트) (0) | 2026.10.03 |
| AI Agent(2/3): 다단계 도구 호출(Multi-step Tool Calling) 및 자율적 루프(Autonomous Re-act Loop) (0) | 2026.10.03 |
| AI Agent(1/3): Function Calling(도구 호출) 에이전트의 정석 (0) | 2026.10.03 |