LLM(대형 언어 모델) 은 기본적으로 텍스트를 받아 텍스트를 내뱉는 시스템입니다. 하지만 외부 데이터에 접근하거나, 실시간 정보를 얻거나, 파일 시스템을 조작하려면 도구(Tool)가 필요합니다. MCP(Model Context Protocol) 는 LLM 이 외부 도구와 안전하게 통신할 수 있게 하는 표준 프로토콜입니다.
이 글에서는 "오늘 날짜를 알려주는 MCP 서버"를 예로 들어, MCP 서버를 직접 코딩하고, 설정하고, LLM 이 이를 호출하는 전체 과정을 단계별로 설명합니다.
1. MCP(Model Context Protocol) 란?
MCP 는 Anthropic 이 제안한 개방형 프로토콜로, LLM 애플리케이션과 외부 데이터 소스·도구들을 연결하는 표준 인터페이스입니다. USB 포트가 다양한 주변기기를 연결하듯, MCP 는 어떤 LLM 이든 어떤 MCP 서버든 연결할 수 있게 합니다.
MCP 가 없으면 각 LLM 플랫폼마다 전용 플러그인을 만들어야 합니다. MCP 가 있으면 한 번 만든 서버가 Claude, Cursor, Hermes Agent 등 모든 LLM 플랫폼에서 바로 동작합니다.
MCP 의 핵심 구성 요소
- MCP Host: LLM 을 실행하는 애플리케이션 (예: Hermes Agent, Claude Desktop, Cursor)
- MCP Server: 외부 도구나 데이터를 제공하는 서버 (예: 오늘 날짜를 반환하는 서버)
- Transport: 호스트와 서버 간 통신 방식 (stdio, HTTP/SSE)
- Tools: 서버가 호스트에 제공하는 기능 목록 (예: get_today)
MCP 가 해결하는 문제
- 재사용: 한 번 작성한 MCP 서버가 모든 LLM 플랫폼에서 동작
- 보안: LLM 은 MCP 서버를 통해만 외부에 접근 — 직접 네트워크/파일시스템 접근 불가
- 표준화: JSON-RPC 기반의 통일된 프로토콜 — 각 플랫폼별 커스텀 플러그인 불필요
2. MCP 서버 코드 작성하기 — 날짜 서버 만들기
먼저 Python 으로 "오늘 날짜를 알려주는" MCP 서버를 직접 만들어보겠습니다. mcp Python 패키지를 사용하면 몇 줄로도 서버를 만들 수 있습니다.
2-1. 환경 준비
먼저 MCP SDK 를 설치합니다.
pip install mcp
2-2. 날짜 서버 전체 코드
#!/usr/bin/env python3
"""
today_mcp_server.py — 오늘 날짜를 알려주는 MCP 서버
사용법:
python today_mcp_server.py # stdio 모드로 실행
python today_mcp_server.py --http # HTTP 모드로 실행 (포트 8090)
"""
import argparse
from datetime import datetime
from zoneinfo import ZoneInfo
from mcp.server import Server
app = Server("today-server")
# ── 비즈니스 로직 ──────────────────────────────────────────
def get_today(timezone: str = "Asia/Seoul") -> dict:
"""지정된 시간대의 현재 날짜와 시간을 반환합니다."""
try:
tz = ZoneInfo(timezone)
now = datetime.now(tz)
weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday"]
return {
"datetime": now.strftime("%Y-%m-%d %H:%M:%S"),
"timezone": timezone,
"weekday": weekdays[now.weekday()],
"date_only": now.strftime("%Y-%m-%d"),
"time_only": now.strftime("%H:%M:%S")
}
except Exception as e:
return {"error": str(e)}
def get_formatted_date(format_str: str, timezone: str = "Asia/Seoul") -> str:
"""strftime 포맷으로 날짜를 반환합니다."""
try:
tz = ZoneInfo(timezone)
now = datetime.now(tz)
return now.strftime(format_str)
except Exception as e:
return f"Error: {e}"
# ── MCP 서버 ───────────────────────────────────────────────
@app.tool()
def get_today_tool(timezone: str = "Asia/Seoul") -> dict:
"""현재 날짜와 시간을 반환합니다.
Args:
timezone: 시간대 (기본값: Asia/Seoul)
예: America/New_York, Europe/London, UTC
Returns:
날짜, 시간, 요일, 시간대 정보
"""
return get_today(timezone)
@app.tool()
def get_formatted_date_tool(format_str: str, timezone: str = "Asia/Seoul") -> str:
"""지정된 포맷으로 날짜를 반환합니다.
Args:
format_str: strftime 포맷 (예: "%Y년 %m월 %d일")
timezone: 시간대 (기본값: Asia/Seoul)
Returns:
포맷팅된 날짜 문자열
"""
return get_formatted_date(format_str, timezone)
# ── 실행 ───────────────────────────────────────────────────
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Today MCP Server")
parser.add_argument("--http", action="store_true", help="Run in HTTP mode")
args = parser.parse_args()
if args.http:
from mcp.server.http import run_server_as_async_http_app
import http.server
import threading
# Simple HTTP server for testing
from wsgiref.simple_server import make_server
from mcp.server.http import run_server_as_async_http_app
async def main():
app_instance = run_server_as_async_http_app(app)
httpd = make_server("localhost", 8090, app_instance)
print("MCP HTTP Server running on http://localhost:8090")
httpd.serve_forever()
import asyncio
asyncio.run(main())
else:
from mcp.server.stdio import stdio_server
import asyncio
async def main():
async with stdio_server() as (read, write):
await app.run(read, write)
asyncio.run(main())
코드 설명
이 코드는 크게 두 부분으로 나뉩니다.
1) 비즈니스 로직 (상단)
- get_today(): 지정된 시간대의 현재 날짜/시간을 반환
- get_formatted_date(): strftime 포맷으로 사용자 정의 날짜 형식 반환
- 에러 처리: 잘못된 시간대 이름도 try/except 로 안전하게 처리
2) MCP 서버 (하단)
- @app.tool(): 함수를 MCP 도구로 등록 — LLM 이 호출할 수 있음
- stdio 모드: 기본 방식. 호스트가 서버를 자 프로세스로 실행하고 stdin/stdout 으로 통신
- HTTP 모드: 서버를 별도 HTTP 엔드포인트로 실행 — 원격 호스트에서 연결 가능
테스트 해보기
이렇게 하면 서버를 거치지 않고 함수가 정상 동작하는지 확인할 수 있습니다.
# 서버를 거치지 않고 직접 테스트
from today_mcp_server import get_today, get_formatted_date
print(get_today())
# {"datetime": "2025-06-13 14:30:00", "timezone": "Asia/Seoul", ...}
print(get_formatted_date("%Y년 %m월 %d일 (%A)"))
# 2025년 06월 13일 (Friday)
3. MCP 서버 설정하기
서버 코드를 만들었다면, 이제 LLM 애플리케이션에 연결해야 합니다. Hermes Agent 를 예로 들어 설명합니다.
3-1. config.yaml 에 등록
~/.hermes/config.yaml 파일에 mcp_servers 섹션을 추가합니다.
mcp_servers:
today:
command: python
args:
- ~/Developer/scripts/today_mcp_server.py
여기서 today 는 서버 이름입니다. 이 이름이 도구 이름 앞에 붙습니다:
- 서버의 get_today 도구 → today.get_today(로 인식됨)
- 서버의 get_formatted_date 도구 → today.get_formatted_date(로 인식됨)
3-2. 설정 옵션 전체
mcp_servers:
today:
command: python # 실행할 명령어
args: # 명령어 인수
- ~/Developer/scripts/today_mcp_server.py
env: # 환경 변수 (선택)
API_KEY: "sk-xxx"
timeout: 30 # 연결 타임아웃 (초)
disabled: false # 서버 비활성화
3-3. HTTP 방식 서버도 지원
stdio 대신 HTTP 로 통신하는 원격 MCP 서버도 연결할 수 있습니다.
mcp_servers:
remote-server:
url: http://your-server.com/mcp # HTTP 엔드포인트
3-4. Hermes Agent 재시작
설정을 추가한 후 Hermes Agent 를 재시작하면:
- 자동 감지: config.yaml 의 mcp_servers 를 읽어 MCP 서버 프로세스를 시작
- 도구 등록: 서버가 제공하는 도구 목록(LLM 이 볼 수 있는 설명 포함)을 가져옴
- 즉시 사용: 사용자가 대화를 시작하면 LLM 이 MCP 도구들을 사용할 수 있음
- 에러 피드백: 서버가 시작 실패하면 Hermes Agent 가 콘솔에 에러를 출력
- 핫 리로드: 서버 코드를 수정하면 다음 Agent 세션에서 자동으로 반영
4. LLM 이 MCP 를 호출하는 전 과정
이제 가장 중요한 부분입니다. 사용자가 "오늘 날짜 알려줘"라고 입력했을 때, 내부에서 어떤 일이 일어나는지 단계별로 추적해보겠습니다.
단계 1: 사용자 입력
메시지가 LLM 애플리케이션(Hermes Agent) 에 전달됩니다.
"오늘 날짜 알려줘"
단계 2: LLM 이 도구 사용 여부를 판단
LLM 은 다음 정보를 함께 받습니다:
- 사용자의 질문
- 등록된 MCP 도구 목록 (이름 + 설명 + 파라미터)
- 시스템 프롬프트 (도구 사용 가이드라인)
단계 3: LLM 이 도구 호출을 결정
LLM 은 "오늘 날짜"라는 키워드를 보고 get_today 도구를 사용해야 한다고 판단합니다.
Tool: today.get_today
Description: 현재 날짜와 시간을 반환합니다.
Parameters:
- timezone: 시간대 (기본값: Asia/Seoul)
단계 4: Hermes Agent 가 MCP 서버에 요청 전달
LLM 이 도구 호출을 결정하면, Hermes Agent 가 실제 MCP 서버에 요청을 보냅니다. JSON-RPC 2.0 프로토콜을 사용합니다.
Tool Call:
tool_name: today.get_today
arguments: {"timezone": "Asia/Seoul"}
이 요청은 MCP 서버 프로세스의 stdin 으로 전달됩니다.
실제 JSON-RPC 메시지는 다음과 같습니다:
// Hermes Agent → MCP Server
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_today",
"arguments": {"timezone": "Asia/Seoul"}
}
}
// MCP Server → Hermes Agent
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{
"type": "text",
"text": "{"datetime": "2025-06-13 14:30:00", "timezone": "Asia/Seoul", "weekday": "Friday", ...}"
}],
"isError": false
}
}
단계 5: 결과 LLM 에 전달
MCP 서버의 응답을 LLM 에 다시 전달합니다.
Tool Result (today.get_today):
현재 시간: 2025-06-13 14:30:00
시간대: Asia/Seoul
요일: Friday
단계 6: LLM 이 최종 답변 생성
LLM 은 도구 결과를 바탕으로 자연스러운 답변을 생성합니다.
"지금 한국 시간으로 2025년 6월 13일 금요일 오후 2시 30분입니다."
전체 흐름 다이어그램
┌─────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 사용자 │────▶│ Hermes Agent │────▶│ LLM (qwen3.6) │
│ │ │ │ │ │
│ "오늘 날짜 │ │ MCP 서버 관리 │ │ 1. 질문 분석 │
│ 알려줘" │◀────│ 프로세스 실행 │◀────│ 2. 도구 호출 │
│ │ │ │ │ 결정 │
└─────────────┘ └────────┬─────────┘ └──────────────────┘
│
┌────────▼─────────┐
│ MCP Server │
│ (today) │
│ │
│ get_today() │
│ 실행 │
└────────┬─────────┘
│
┌────────▼─────────┐
│ JSON-RPC 응답 │
│ │
│ {"datetime": │
│ "2025-06-13 │
│ 14:30:00", ...}│
└────────┬─────────┘
│
┌────────▼─────────┐
│ Hermes Agent │
│ │
│ 결과를 LLM 에 │
│ 다시 전달 │
└────────┬─────────┘
│
┌────────▼─────────┐ ┌─────────────┐
│ LLM │────▶│ 사용자 │
│ │ │ │
│ 최종 답변 생성 │ │ "지금 한국 │
│ │ │ 시간으로 │
└──────────────────┘ │ 2025년 6월 │
│ 13일 금요일 │
│ 오후 2시 │
│ 30분입니다" │
└─────────────┘
5. MCP 서버 작성 시 주의사항
5-1. docstring 은 LLM 이 보는 설명서
MCP 서버에서 docstring은 단순 주석이 아닙니다. LLM 이 도구 사용 여부를 결정하는 유일한 정보원입니다.
@app.tool()
def get_today(timezone: str = "Asia/Seoul") -> str:
"""현재 날짜와 시간을 반환합니다.
Args:
timezone: 시간대 (기본값: Asia/Seoul)
예: America/New_York, Europe/London, UTC
Returns:
현재 날짜와 시간 문자열
"""
docstring 이 명확해야 LLM 이 언제 이 도구를 호출해야 하는지 정확히 알 수 있습니다. 모호한 설명은 LLM 이 도구를 사용하지 않거나 잘못된 파라미터로 호출하는 원인이 됩니다.
5-2. 입력 파라미터 정의하기
@app.tool()
def search_files(directory: str, pattern: str) -> list:
"""지정된 디렉토리에서 패턴에 맞는 파일 목록을 반환합니다.
Args:
directory: 검색할 디렉토리 경로
pattern: 파일 이름 패턴 (예: "*.py", "test_*")
Returns:
일치하는 파일 경로 목록
"""
- 타입 힌트 필수: str, int, bool 등 명확한 타입 지정
- 기본값 설정: 가능한 한 파라미터에 기본값 제공
- Args 설명: 각 파라미터의 용도와 예시를 docstring 에 포함
5-3. 에러 처리는 반드시
@app.tool()
def get_weather(city: str) -> str:
"""도시의 현재 날씨를 반환합니다."""
try:
response = requests.get(f"https://api.weather.com/{city}")
response.raise_for_status()
return json.dumps(response.json(), ensure_ascii=False)
except Exception as e:
return f"날씨 조회 실패: {e}"
에러가 발생해도 서버가 크래시되지 않도록 try/except로 감싸야 합니다. 에러 메시지도 LLM 에 전달되므로, 의미 있는 에러 메시지를 반환하세요.
5-4. 보안 — 환경 변수는 명시적으로 전달
mcp_servers:
weather:
command: python
args: [~/scripts/weather_server.py]
env:
WEATHER_API_KEY: "***" # API 키는 여기에서 전달
API 키나 비밀번호는 코드에 하드코딩하지 말고 env 옵션으로 전달하세요. config.yaml 은 버전 컨트롤에 포함되지 않도록 .gitignore 에 추가하세요.
6. 실전 — 다른 MCP 서버 예시
파일 시스템 서버
mcp_servers:
filesystem:
command: npx
args:
- "-y"
- "@modelcontextprotocol/server-filesystem"
- "/Users/mulpats/Developer" # 접근 허용 디렉토리
지정된 디렉토리 내의 파일을 읽고, 검색하고, 목록을 확인할 수 있습니다. 접근 허용 디렉토리만 지정해야 보안상 안전합니다.
GitHub 서버
mcp_servers:
github:
command: npx
args:
- "-y"
- "@modelcontextprotocol/server-github"
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_xxx"
GitHub 리포지토리의 코드를 읽고, 이슈를 확인하고, PR 을 검토할 수 있습니다.
시간대 서버 (uvx)
mcp_servers:
timezone:
command: uvx
args:
- "mcp-server-timezone"
uvx 로 설치 없이 바로 실행할 수 있는 MCP 서버도 많습니다. uvx는 Python 패키지를 가상 환경에서 즉시 실행하는 도구입니다.
7. 문제 해결 가이드
MCP 도구가 인식되지 않음
# config.yaml 의 mcp_servers 섹션 확인
cat ~/.hermes/config.yaml | grep -A 5 mcp_servers
# Hermes Agent 재시작 후 로그 확인
hermes start # 또는 재시작
- config.yaml 의 YAML 들여쓰기가 정확한지 확인 (스페이스 2개 사용)
- 서버 경로를 절대 경로로 변경해보기
- Hermes Agent 를 재시작했는지 확인
"MCP SDK not available" 오류
# Python 환경 확인
which python
python --version
# MCP SDK 설치 확인
pip show mcp
- pip install mcp 로 최신 버전 설치
- 가상 환경이 활성화되어 있는지 확인
- Python 버전이 3.9 이상인지 확인
서버 연결 실패
# 서버 직접 실행 (디버깅)
python ~/Developer/scripts/today_mcp_server.py
# HTTP 모드로 실행 테스트
python ~/Developer/scripts/today_mcp_server.py --http
# 브라우저에서 http://localhost:8090 접속
- 서버를 직접 실행해서 에러 메시지 확인
- 포트가 이미 사용 중인지 확인 (lsof -i :8090)
- 방화벽 설정 확인 (macOS: 시스템 설정 → 네트워크 → 방화벽)
요약
- MCP 는 LLM 과 외부 도구를 연결하는 표준 프로토콜
- Python mcp 패키지로 몇 줄로도 MCP 서버 작성 가능
- config.yaml 에 등록하면 Hermes Agent 가 자동으로 관리
- LLM 은 도구 설명(docstring) 을 보고 언제 호출할지 결정
- JSON-RPC 2.0 으로 호스트와 서버 간 통신
- 에러 처리와 보안(API 키 관리) 을 반드시 고려
댓글 없음:
댓글 쓰기