MCP란 무엇인가
MCP(Model Context Protocol)는 Anthropic이 2024년 11월에 발표한 오픈소스 표준 프로토콜입니다. AI 애플리케이션을 외부 시스템(데이터베이스, API, 파일 시스템 등)에 연결하는 규격입니다.
비유하면 AI를 위한 USB-C 포트입니다. USB-C가 충전, 데이터, 영상 출력을 하나의 규격으로 통일했듯, MCP는 AI가 외부 도구를 호출하는 방식을 표준화합니다. 2025년 12월 Linux Foundation에 기증되었고, OpenAI와 Google DeepMind도 채택했습니다.
아키텍처
MCP는 3가지 역할로 나뉩니다.
| 구성 요소 | 역할 | 예시 |
|---|---|---|
| Host | AI 애플리케이션 (여러 Client 관리) | Claude Desktop, VS Code, Claude Code |
| Client | 각 Server와 1:1 연결 유지 | Host 내부에서 자동 생성 |
| Server | Tool/Resource/Prompt를 제공 | 개발자가 직접 구현하는 부분 |
Server가 제공하는 3가지 프리미티브:
| 프리미티브 | 설명 | 비유 |
|---|---|---|
| Tool | AI가 호출하는 실행 가능한 함수 | REST의 POST 엔드포인트 |
| Resource | AI에게 컨텍스트 데이터를 제공 | REST의 GET 엔드포인트 |
| Prompt | LLM 상호작용을 구조화하는 템플릿 | 미리 작성된 작업 지시서 |
설치
# uv 사용 (권장)
uv add "mcp[cli]"
# pip 사용
pip install "mcp[cli]"
# 설치 확인
python -c "import mcp; print(mcp.__version__)"
# 1.27.0
Tool 구현
Tool은 AI가 직접 호출하는 함수입니다. 데코레이터 하나로 정의합니다.
from mcp.server.fastmcp import FastMCP
# MCP 서버 인스턴스 생성
mcp = FastMCP("MyTools")
@mcp.tool()
def add(a: int, b: int) -> int:
"""두 숫자를 더합니다"""
return a + b
@mcp.tool()
def search_files(directory: str, extension: str = ".py") -> list[str]:
"""지정 디렉토리에서 특정 확장자 파일을 검색합니다
Args:
directory: 검색할 디렉토리 경로
extension: 파일 확장자 (기본: .py)
"""
from pathlib import Path
return [str(f) for f in Path(directory).rglob(f"*{extension}")]
@mcp.tool()
async def fetch_url(url: str) -> str:
"""URL의 내용을 가져옵니다"""
import httpx
async with httpx.AsyncClient() as client:
resp = await client.get(url, timeout=30.0)
return resp.text[:5000] # 최대 5000자
if __name__ == "__main__":
mcp.run(transport="stdio")
Python 타입 힌트와 독스트링에서 MCP 스키마가 자동 생성됩니다. Args: 섹션의 파라미터 설명도 AI에게 전달되므로 상세하게 작성하세요.
Resource 구현
Resource는 AI에게 컨텍스트 데이터를 제공합니다. Tool과 달리 부수효과가 없는 읽기 전용입니다.
from mcp.server.fastmcp import FastMCP
import json
mcp = FastMCP("MyResources")
# 정적 리소스 — 고정된 URI
@mcp.resource("config://app-version")
def get_version() -> str:
"""앱 버전 정보를 반환합니다"""
return "v2.1.0"
# 동적 리소스 — URI 파라미터 사용
@mcp.resource("db://users/{user_id}")
def get_user(user_id: str) -> str:
"""사용자 정보를 JSON으로 반환합니다"""
users = {
"1": {"name": "홍길동", "email": "hong@example.com"},
"2": {"name": "김철수", "email": "kim@example.com"},
}
user = users.get(user_id, {"error": "사용자를 찾을 수 없습니다"})
return json.dumps(user, ensure_ascii=False)
# 파일 기반 리소스
@mcp.resource("file://logs/latest")
def get_latest_log() -> str:
"""최신 로그 파일 내용을 반환합니다"""
from pathlib import Path
log_file = Path("logs/app.log")
if not log_file.exists():
return "로그 파일이 없습니다"
return log_file.read_text(encoding="utf-8")[-2000:] # 마지막 2000자
if __name__ == "__main__":
mcp.run(transport="stdio")
Prompt 구현
Prompt는 AI와의 상호작용을 미리 정의한 템플릿입니다. 반복되는 작업 지시를 재사용할 수 있습니다.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("MyPrompts")
@mcp.prompt()
def code_review(language: str, code: str) -> str:
"""코드 리뷰 프롬프트를 생성합니다"""
return f"""다음 {language} 코드를 리뷰해주세요.
## 검토 항목
1. 버그 가능성
2. 성능 개선 포인트
3. 가독성 및 네이밍
## 코드
```{language}
{code}
```"""
@mcp.prompt()
def sql_query(table_name: str, columns: str = "*") -> str:
"""SQL 쿼리 생성 프롬프트"""
return f"""'{table_name}' 테이블에서 {columns} 컬럼을 조회하는
최적화된 SQL 쿼리를 작성해주세요. 인덱스 활용과 성능을 고려해주세요."""
if __name__ == "__main__":
mcp.run(transport="stdio")
실전 예제: 메모 관리 서버
Tool + Resource를 결합한 실전 예제입니다.
from mcp.server.fastmcp import FastMCP
import json
from datetime import datetime
mcp = FastMCP("NoteServer")
# 인메모리 저장소
notes: dict[str, dict] = {}
@mcp.tool()
def create_note(title: str, content: str) -> str:
"""새 메모를 생성합니다
Args:
title: 메모 제목
content: 메모 내용
"""
note_id = str(len(notes) + 1)
notes[note_id] = {
"title": title,
"content": content,
"created_at": datetime.now().isoformat(),
}
return f"메모 #{note_id} '{title}' 생성 완료"
@mcp.tool()
def delete_note(note_id: str) -> str:
"""메모를 삭제합니다"""
if note_id not in notes:
return f"메모 #{note_id}를 찾을 수 없습니다"
title = notes.pop(note_id)["title"]
return f"메모 #{note_id} '{title}' 삭제 완료"
@mcp.resource("notes://list")
def list_notes() -> str:
"""모든 메모 목록을 반환합니다"""
if not notes:
return "메모가 없습니다"
return json.dumps(notes, ensure_ascii=False, indent=2)
@mcp.resource("notes://{note_id}")
def get_note(note_id: str) -> str:
"""특정 메모를 조회합니다"""
note = notes.get(note_id)
if not note:
return f"메모 #{note_id}를 찾을 수 없습니다"
return json.dumps(note, ensure_ascii=False, indent=2)
if __name__ == "__main__":
mcp.run(transport="stdio")
Claude Desktop에 연결
MCP 서버를 Claude Desktop에 등록하는 설정입니다.
설정 파일 위치:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"note-server": {
"command": "uv",
"args": [
"--directory", "/path/to/note-server",
"run", "server.py"
]
}
}
}
Claude Code에서 연결:
# STDIO 서버 등록
claude mcp add note-server -- uv --directory /path/to/note-server run server.py
# 등록 확인
claude mcp list
# 세션에서 상태 확인
/mcp
주의사항
| 항목 | 설명 |
|---|---|
| print() 금지 | STDIO 전송에서 print()는 JSON-RPC 메시지를 깨뜨림. logging 모듈 사용 |
| 에러 처리 | Tool에서 예외 발생 시 AI에게 에러 메시지가 전달됨. 명확한 에러 메시지 작성 |
| 타입 힌트 필수 | 타입 힌트가 없으면 MCP 스키마 생성 실패 |
| 독스트링 작성 | 함수 설명이 AI에게 전달됨. 상세할수록 AI가 적절한 Tool을 선택 |
| 비동기 지원 | async def로 정의하면 비동기 실행. I/O 작업에 권장 |
정리
| 프리미티브 | 데코레이터 | 용도 |
|---|---|---|
| Tool | @mcp.tool() | AI가 호출하는 함수 (생성, 수정, 삭제, API 호출) |
| Resource | @mcp.resource("uri://pattern") | AI에게 데이터 제공 (조회, 읽기) |
| Prompt | @mcp.prompt() | 반복 작업을 템플릿화 |
Python FastMCP의 장점은 데코레이터 하나로 끝난다는 것입니다. 타입 힌트와 독스트링만 잘 작성하면 MCP 스키마, 파라미터 검증, 에러 처리까지 자동으로 처리됩니다.