우공이산(愚公移山)

자신과 세상을 바꾸는 것은 머리좋고 가진것이 많은 사람이 아니라 결코 포기하지 않는 의지로 꾸준히 노력해 가는 사람이다. 오늘이 쌓여 내일을 만들고, 내일이 쌓여 인생을 만든다.

Code Story

MCP 개발 상세 가이드 (튜토리얼)

보노보노 2025. 6. 29. 14:31

Model Context Protocol (MCP) 작성 가이드 - 상세 버전

Model Context Protocol (MCP)는 대형 언어 모델(LLM)이 외부 데이터 소스와 도구에 안전하고 표준화된 방식으로 접근할 수 있게 해주는 개방형 프로토콜입니다^1. 2024년 11월 Anthropic에 의해 발표된 이 프로토콜은 AI 애플리케이션의 USB-C 포트라고 할 수 있으며, 다양한 데이터 소스와 도구들을 표준화된 방식으로 연결할 수 있게 해줍니다^2.

MCP가 해결하는 핵심 문제

기존 AI 시스템들은 각각의 데이터 소스마다 별도의 커스텀 통합이 필요했습니다^3. 이는 개발 복잡도를 증가시키고, 확장성을 제한하며, 보안 문제를 야기했습니다^4. MCP는 이러한 문제들을 해결하기 위해 다음과 같은 핵심 가치를 제공합니다^1:

  • 표준화된 통합: 하나의 프로토콜로 다양한 데이터 소스 연결
  • 확장성: 새로운 데이터 소스 추가 시 기존 코드 재사용 가능
  • 상호운용성: 다양한 LLM 제공업체 간 호환성
  • 보안: 프로토콜에 내장된 보안 기능으로 API 키 공유 불필요

MCP 아키텍처 및 핵심 구성 요소

클라이언트-서버 아키텍처

MCP는 JSON-RPC 2.0 프로토콜을 기반으로 한 클라이언트-서버 아키텍처를 따릅니다^6^8. 이 구조는 다음과 같은 주요 구성 요소들로 이루어집니다:

MCP 호스트 (Host): AI 모델이 내장된 애플리케이션으로, Claude Desktop, IDE 어시스턴트, 채팅 애플리케이션 등이 해당됩니다^3. 호스트는 사용자 요청을 받아 모델에게 전달하고, 응답을 사용자에게 보여주는 전체 흐름을 조율합니다.

MCP 클라이언트 (Client): 호스트 애플리케이션 내부에서 동작하며, 하나의 MCP 서버와 1:1 연결을 담당하는 컴포넌트입니다^3. 클라이언트는 AI 모델 측에서 MCP 프로토콜을 구현하며, 서버로 요청을 보내고 응답을 받아 모델에 전달합니다.

MCP 서버 (Server): 외부 데이터나 기능을 제공하는 백엔드 서비스입니다^3. 서버는 특정 서비스나 데이터 소스를 감싸서 모델이 이해할 수 있는 형태로 컨텍스트를 제공합니다.

JSON-RPC 2.0 메시지 포맷

MCP의 모든 통신은 JSON-RPC 2.0 규격을 따릅니다^6^8. 기본 메시지 구조는 다음과 같습니다:

{
  "jsonrpc": "2.0",
  "id": "request-1234",
  "method": "methodName",
  "params": {
    // 메서드별 파라미터
  }
}

응답 메시지는 다음과 같은 형태를 가집니다:

{
  "jsonrpc": "2.0",
  "id": "request-1234",
  "result": {
    // 메서드별 결과
  }
}

에러 발생 시에는 다음과 같은 구조로 응답합니다:

{
  "jsonrpc": "2.0",
  "id": "request-1234",
  "error": {
    "code": -32000,
    "message": "Server error",
    "data": {
      // 추가 오류 정보
    }
  }
}

MCP 서버 개발 단계별 가이드

1. 개발 환경 설정

MCP 서버 개발을 위해서는 다음과 같은 환경이 필요합니다^9:

  • Python 3.10 이상 또는 Node.js 20 이상
  • MCP SDK 또는 FastMCP 등 개발 라이브러리
  • VS Code, Cursor AI 등 코드 편집기

Python 환경 설정 (uv 사용)^10:

# uv 설치 (macOS)
curl -LsSf https://astral.sh/uv/install.sh | sh

# 프로젝트 생성
uv init my-mcp-server
cd my-mcp-server

# 가상환경 생성 및 활성화
uv venv
source .venv/bin/activate

# MCP 라이브러리 설치
uv add "mcp[cli]" httpx

Node.js 환경 설정^12:

# MCP 프레임워크 전역 설치
npm install -g mcp-framework

# 새 프로젝트 생성
mcp create my-mcp-server
cd my-mcp-server

# 종속성 설치
npm install

2. FastMCP를 활용한 Python 서버 구현

FastMCP는 Python에서 MCP 서버를 쉽게 구축할 수 있게 해주는 고수준 라이브러리입니다^9. 다음은 기본적인 서버 구현 예시입니다:

from fastmcp import FastMCP
import logging

# 로깅 설정
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("mcp-server")

# MCP 서버 인스턴스 생성
mcp = FastMCP("Demo Server", debug=True)

# 리소스(Resource) 구현: 정적 데이터 제공
@mcp.resource("config://app")
def get_config() -> str:
    """애플리케이션 설정 정보를 제공하는 리소스"""
    logger.info("Config resource accessed")
    return "App configuration here"

# 동적 리소스: 매개변수를 받는 리소스
@mcp.resource("users://{user_id}/profile")
def get_user_profile(user_id: str) -> str:
    """사용자 프로필 정보를 제공하는 동적 리소스"""
    logger.info(f"User profile requested: {user_id}")
    return f"Profile data for user {user_id}"

# 도구(Tool) 구현: 계산 기능
@mcp.tool()
def add_numbers(a: int, b: int) -> int:
    """두 숫자를 더하는 도구"""
    logger.info(f"Adding {a} and {b}")
    return a + b

# 프롬프트(Prompt) 구현: 템플릿 제공
@mcp.prompt()
def echo_prompt(message: str) -> str:
    """메시지를 에코하는 프롬프트 템플릿"""
    logger.info(f"Echo prompt with message: {message}")
    return f"Please process this message: {message}"

# 서버 실행
if __name__ == "__main__":
    logger.info("Starting MCP server...")
    mcp.run()

3. TypeScript를 활용한 서버 구현

TypeScript로 MCP 서버를 구현하는 경우, 공식 MCP SDK를 사용할 수 있습니다^14:

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

// 서버 인스턴스 생성
const server = new Server({
  name: "example-mcp-server",
  version: "1.0.0",
}, {
  capabilities: {
    resources: {},
    tools: {},
    prompts: {}
  }
});

// 리소스 핸들러 등록
server.setRequestHandler("resources/list", async () => {
  return {
    resources: [
      {
        uri: "note:///1",
        mimeType: "text/plain",
        name: "First Note",
        description: "A text note: First Note"
      }
    ]
  };
});

// 도구 핸들러 등록
server.setRequestHandler("tools/list", async () => {
  return {
    tools: [
      {
        name: "create_note",
        description: "Create a new note",
        inputSchema: {
          type: "object",
          properties: {
            title: { type: "string", description: "Title of the note" },
            content: { type: "string", description: "Content of the note" }
          },
          required: ["title", "content"]
        }
      }
    ]
  };
});

// 서버 시작
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

main().catch(console.error);

MCP의 핵심 기능 요소

1. 리소스 (Resources)

리소스는 LLM이 읽을 수 있는 읽기 전용 데이터를 제공합니다^15. REST API의 GET 엔드포인트와 유사하며, 복잡한 계산이나 부작용 없이 단순히 구조화된 데이터를 반환합니다^16.

리소스의 특징:

  • 순수 데이터 제공 목적
  • 캐싱 가능
  • URI 패턴으로 동적 리소스 지원
  • 다양한 MIME 타입 지원 (text/plain, application/json 등)

2. 도구 (Tools)

도구는 LLM이 호출할 수 있는 실행 가능한 함수입니다^15. 계산을 수행하거나 외부 시스템에 변경을 가할 수 있는 명령적 함수로 작동합니다^16.

도구의 특징:

  • 액션 수행 가능
  • 입력 스키마 정의 필수
  • 부작용 허용
  • JSON Schema로 매개변수 검증

3. 프롬프트 (Prompts)

프롬프트는 재사용 가능한 템플릿으로, LLM과의 상호작용을 구조화하고 일관성을 제공합니다^15. 동적 입력을 받아 컨텍스트를 포함한 메시지를 생성할 수 있습니다.

프롬프트의 활용:

  • 시스템 프롬프트 템플릿
  • 사용자 입력 템플릿
  • 워크플로우 가이드
  • 복잡한 상호작용 체인 구성

4. 전송 계층 (Transports)

MCP는 다양한 전송 방식을 지원합니다^17:

STDIO Transport: 표준 입출력을 통한 로컬 통신으로, 개발 및 테스트에 적합합니다^17.

HTTP+SSE Transport: 웹 환경에서의 원격 통신을 위한 방식으로, HTTP POST와 Server-Sent Events를 결합합니다^17.

WebSocket Transport: 실시간 양방향 통신을 위한 방식입니다^17.

MCP 서버 테스트 및 디버깅

MCP Inspector 활용

MCP Inspector는 서버를 테스트하고 디버깅할 수 있는 공식 도구입니다^19:

# Inspector 실행 (UI 모드)
npx @modelcontextprotocol/inspector

# 특정 서버와 함께 실행
npx @modelcontextprotocol/inspector node build/index.js

# CLI 모드로 실행
npx @modelcontextprotocol/inspector --cli node build/index.js

Inspector의 주요 기능:

  • 서버 연결 상태 확인
  • 도구, 리소스, 프롬프트 테스트
  • 실시간 메시지 모니터링
  • 에러 진단 및 로깅

로깅 및 모니터링

효과적인 디버깅을 위해서는 적절한 로깅이 필수입니다^10:

import logging

# 상세한 로깅 설정
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(sys.stdout)
    ]
)

logger = logging.getLogger("mcp-server")

@mcp.tool()
def example_tool(param: str) -> str:
    logger.info(f"Tool called with parameter: {param}")
    try:
        result = process_data(param)
        logger.info(f"Tool completed successfully: {result}")
        return result
    except Exception as e:
        logger.error(f"Tool failed with error: {e}")
        raise

MCP 서버 배포 및 설정

Claude Desktop과의 연동

Claude Desktop에서 MCP 서버를 사용하기 위해서는 설정 파일을 수정해야 합니다^20^22:

  1. Claude Desktop 실행 후 Settings → Developer → Edit Config 클릭
  2. claude_desktop_config.json 파일 편집

설정 예시^21:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/path/to/your/directory"
      ]
    },
    "my-custom-server": {
      "command": "python",
      "args": ["path/to/your/server.py"],
      "env": {
        "API_KEY": "your-api-key"
      }
    }
  }
}

환경별 설정 관리

개발, 테스트, 프로덕션 환경별로 다른 설정을 관리할 수 있습니다^23:

개발 환경 (.env.development):

MCP_SERVER_NAME=dev-server
MCP_DEBUG=true
MCP_LOG_LEVEL=debug
MCP_ROOT_DIR=./workspace
MCP_ALLOW_SYMLINKS=true

프로덕션 환경 (.env.production):

MCP_SERVER_NAME=prod-server
MCP_DEBUG=false
MCP_LOG_LEVEL=info
MCP_ROOT_DIR=/var/mcp/workspace
MCP_ALLOW_SYMLINKS=false
MCP_AUTH_REQUIRED=true

고급 MCP 서버 구현 패턴

복합 기능 서버 예시

실제 업무에서 활용할 수 있는 복합 기능 서버의 예시입니다^15:

from fastmcp import FastMCP
import requests
import json
import sqlite3
from typing import List, Dict

mcp = FastMCP("Business Analytics Server")

# 데이터베이스 연결 설정
@mcp.resource("database://connection")
def get_db_status() -> str:
    """데이터베이스 연결 상태 확인"""
    try:
        conn = sqlite3.connect('business.db')
        cursor = conn.cursor()
        cursor.execute("SELECT COUNT(*) FROM customers")
        count = cursor.fetchone()[^0]
        conn.close()
        return f"Database connected. Total customers: {count}"
    except Exception as e:
        return f"Database connection failed: {e}"

# 고객 정보 조회 도구
@mcp.tool()
def search_customers(keyword: str, limit: int = 10) -> List[Dict]:
    """고객 정보를 검색하는 도구"""
    conn = sqlite3.connect('business.db')
    cursor = conn.cursor()

    query = """
    SELECT id, name, email, created_date 
    FROM customers 
    WHERE name LIKE ? OR email LIKE ?
    LIMIT ?
    """

    cursor.execute(query, (f'%{keyword}%', f'%{keyword}%', limit))
    results = cursor.fetchall()
    conn.close()

    return [
        {
            "id": row[^0],
            "name": row[^1],
            "email": row[^2],
            "created_date": row[^3]
        }
        for row in results
    ]

# 보고서 생성 프롬프트
@mcp.prompt()
def generate_report_prompt(report_type: str, date_range: str) -> str:
    """비즈니스 보고서 생성을 위한 프롬프트 템플릿"""
    return f"""
    Generate a {report_type} report for the period: {date_range}

    Please include:
    1. Executive summary
    2. Key metrics and KPIs
    3. Trend analysis
    4. Recommendations

    Use the available customer data and analytics tools to gather the necessary information.
    """

# 외부 API 연동 도구
@mcp.tool()
def get_market_data(symbol: str) -> Dict:
    """주식 시장 데이터를 가져오는 도구"""
    try:
        # 실제 구현에서는 적절한 API 키와 엔드포인트 사용
        response = requests.get(f"https://api.example.com/stock/{symbol}")
        return response.json()
    except Exception as e:
        return {"error": f"Failed to fetch market data: {e}"}

if __name__ == "__main__":
    mcp.run()

에러 처리 및 보안 고려사항

프로덕션 환경에서는 적절한 에러 처리와 보안 조치가 필요합니다:

from fastmcp import FastMCP
import os
from functools import wraps

mcp = FastMCP("Secure Server")

# 인증 데코레이터
def require_auth(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        api_key = os.getenv('MCP_API_KEY')
        if not api_key:
            raise ValueError("Authentication required")
        return func(*args, **kwargs)
    return wrapper

# 입력 검증 및 제한
@mcp.tool()
@require_auth
def secure_file_read(file_path: str) -> str:
    """보안이 적용된 파일 읽기 도구"""
    # 경로 검증
    allowed_dirs = ['/safe/directory', '/public/data']
    if not any(file_path.startswith(allowed_dir) for allowed_dir in allowed_dirs):
        raise ValueError("Access denied: Invalid file path")

    # 파일 크기 제한
    if os.path.getsize(file_path) > 10 * 1024 * 1024:  # 10MB
        raise ValueError("File too large")

    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            return f.read()
    except FileNotFoundError:
        raise ValueError("File not found")
    except PermissionError:
        raise ValueError("Permission denied")

결론 및 향후 발전 방향

MCP는 AI 생태계에서 데이터 소스와 도구를 표준화하는 중요한 프로토콜로 자리잡고 있습니다. FastMCP와 같은 고수준 라이브러리를 활용하면 복잡한 프로토콜 구현 없이도 강력한 MCP 서버를 구축할 수 있습니다^9.

향후 MCP 개발에서 고려해야 할 주요 사항들:

  1. 보안 강화: 인증, 권한 관리, 데이터 암호화 등의 보안 조치
  2. 성능 최적화: 대량 데이터 처리, 캐싱, 연결 풀링 등
  3. 모니터링: 로깅, 메트릭 수집, 알림 시스템 구축
  4. 확장성: 마이크로서비스 아키텍처, 로드 밸런싱 고려
  5. 호환성: 다양한 클라이언트와의 호환성 유지

MCP는 AI 에이전트와 실제 업무 환경을 연결하는 핵심 기술로, 앞으로도 계속해서 발전할 것으로 예상됩니다. 개발자들은 이 표준을 활용하여 더욱 강력하고 유연한 AI 솔루션을 구축할 수 있을 것입니다.