MCP 서버 구축 가이드 — Python 편

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가지 역할로 나뉩니다.

구성 요소역할예시
HostAI 애플리케이션 (여러 Client 관리)Claude Desktop, VS Code, Claude Code
Client각 Server와 1:1 연결 유지Host 내부에서 자동 생성
ServerTool/Resource/Prompt를 제공개발자가 직접 구현하는 부분

Server가 제공하는 3가지 프리미티브:

프리미티브설명비유
ToolAI가 호출하는 실행 가능한 함수REST의 POST 엔드포인트
ResourceAI에게 컨텍스트 데이터를 제공REST의 GET 엔드포인트
PromptLLM 상호작용을 구조화하는 템플릿미리 작성된 작업 지시서

설치

# 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 스키마, 파라미터 검증, 에러 처리까지 자동으로 처리됩니다.

이 글이 도움이 되었나요?