MCP (Model Context Protocol) 완전 가이드 | AI 에이전트 표준 프로토콜

MCP (Model Context Protocol) 완전 가이드 | AI 에이전트 표준 프로토콜

이 글의 핵심

MCP(Model Context Protocol)는 AI 모델이 파일 시스템, 데이터베이스, 웹 API 등 외부 리소스와 표준화된 방식으로 통신하는 오픈 프로토콜입니다. Claude Desktop, Cursor, VS Code 등에서 지원합니다.

MCP란?

MCP(Model Context Protocol)는 Anthropic이 2024년 말 공개한 오픈소스 프로토콜입니다. AI 모델이 파일 시스템, 데이터베이스, 웹 API, 개발 도구 등 외부 리소스와 표준화된 방식으로 통신할 수 있게 합니다.

클라이언트 (Claude Desktop, Cursor, VS Code)
    ↕ MCP 프로토콜
서버 (파일 시스템, DB, 웹 API, Git, Slack...)

왜 MCP인가?

이전에는 각 AI 앱마다 외부 도구 연동 코드를 따로 작성해야 했습니다. MCP는 USB처럼 표준 인터페이스를 제공해서 한 번 만든 MCP 서버를 모든 호환 클라이언트에서 재사용할 수 있습니다.

이전 방식MCP 방식
앱마다 API 연동 코드 별도 작성MCP 서버 한 번 작성 → 모든 클라이언트 사용
모델 변경 시 코드 수정 필요모델/클라이언트 무관하게 동작
보안/인증 중복 구현서버 측에서 일관되게 처리

MCP 아키텍처

┌─────────────────────────────────────────┐
│           MCP 클라이언트                 │
│  (Claude Desktop / Cursor / VS Code)    │
└────────────────┬────────────────────────┘
                 │ JSON-RPC over stdio / SSE
┌────────────────▼────────────────────────┐
│              MCP 서버                    │
│  ┌──────────┐  ┌──────────┐  ┌───────┐ │
│  │  Tools   │  │Resources │  │Prompts│ │
│  └──────────┘  └──────────┘  └───────┘ │
└────────────────┬────────────────────────┘

     ┌───────────┼───────────┐
     ▼           ▼           ▼
  파일시스템    데이터베이스   웹 API

3가지 핵심 개념

  • Tools: AI가 호출할 수 있는 함수 (Function Calling과 유사)
  • Resources: AI가 읽을 수 있는 데이터 (파일, DB 레코드 등)
  • Prompts: 재사용 가능한 프롬프트 템플릿

빠른 시작: Python MCP 서버 만들기

설치

pip install mcp
# 또는 uv 사용 (권장)
pip install uv
uv add mcp

첫 번째 MCP 서버

아래는 list_tools 함수 구현 예제입니다. 위에서 설명한 핵심 로직을 담고 있습니다.

# server.py
from mcp.server import Server
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import mcp.types as types

# 서버 인스턴스 생성
server = Server("my-first-server")

@server.list_tools()
async def list_tools() -> list[Tool]:
    """사용 가능한 도구 목록 반환"""
    return [
        Tool(
            name="calculate",
            description="수식을 계산합니다",
            inputSchema={
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "계산할 수식 (예: 2 + 3 * 4)"
                    }
                },
                "required": ["expression"]
            }
        ),
        Tool(
            name="get_current_time",
            description="현재 시간을 반환합니다",
            inputSchema={
                "type": "object",
                "properties": {}
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    """도구 호출 처리"""
    if name == "calculate":
        expression = arguments.get("expression", "")
        try:
            # 안전한 수식 평가 (실제 프로덕션에서는 더 안전한 방법 사용)
            result = eval(expression, {"__builtins__": {}}, {})
            return [TextContent(type="text", text=f"{expression} = {result}")]
        except Exception as e:
            return [TextContent(type="text", text=f"오류: {str(e)}")]
    
    elif name == "get_current_time":
        from datetime import datetime
        now = datetime.now().strftime("%Y년 %m월 %d일 %H:%M:%S")
        return [TextContent(type="text", text=f"현재 시간: {now}")]
    
    raise ValueError(f"알 수 없는 도구: {name}")

async def main():
    # stdio 모드로 서버 실행 (로컬 Claude Desktop 연결용)
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="my-first-server",
                server_version="1.0.0",
                capabilities=server.get_capabilities(
                    notification_options=None,
                    experimental_capabilities={}
                )
            )
        )

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

실전 예제: 파일 시스템 MCP 서버

아래는 list_tools 함수 구현 예제입니다. 위에서 설명한 핵심 로직을 담고 있습니다.

# file_server.py
from mcp.server import Server
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp.types import Tool, Resource, TextContent, ResourceTemplate
import mcp.types as types
from pathlib import Path
import os

server = Server("file-system-server")
ALLOWED_DIR = Path(os.environ.get("ALLOWED_DIR", "/tmp/mcp-files"))
ALLOWED_DIR.mkdir(exist_ok=True)

@server.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="read_file",
            description="파일 내용을 읽습니다",
            inputSchema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "파일 경로"}
                },
                "required": ["path"]
            }
        ),
        Tool(
            name="write_file",
            description="파일에 내용을 씁니다",
            inputSchema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "파일 경로"},
                    "content": {"type": "string", "description": "저장할 내용"}
                },
                "required": ["path", "content"]
            }
        ),
        Tool(
            name="list_files",
            description="디렉토리의 파일 목록을 반환합니다",
            inputSchema={
                "type": "object",
                "properties": {
                    "directory": {"type": "string", "description": "디렉토리 경로 (생략 시 기본 디렉토리)"}
                }
            }
        )
    ]

def validate_path(path_str: str) -> Path:
    """보안: 허용된 디렉토리 내 경로만 허용"""
    path = (ALLOWED_DIR / path_str).resolve()
    if not str(path).startswith(str(ALLOWED_DIR)):
        raise ValueError("허용된 디렉토리 밖의 경로는 접근할 수 없습니다")
    return path

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    if name == "read_file":
        path = validate_path(arguments["path"])
        if not path.exists():
            return [TextContent(type="text", text=f"오류: 파일을 찾을 수 없습니다: {path}")]
        content = path.read_text(encoding="utf-8")
        return [TextContent(type="text", text=content)]
    
    elif name == "write_file":
        path = validate_path(arguments["path"])
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(arguments["content"], encoding="utf-8")
        return [TextContent(type="text", text=f"파일 저장 완료: {path.name}")]
    
    elif name == "list_files":
        dir_path = validate_path(arguments.get("directory", "."))
        if not dir_path.is_dir():
            return [TextContent(type="text", text="디렉토리가 아닙니다")]
        
        files = []
        for item in sorted(dir_path.iterdir()):
            item_type = "📁" if item.is_dir() else "📄"
            size = item.stat().st_size if item.is_file() else 0
            files.append(f"{item_type} {item.name} ({size:,} bytes)" if item.is_file() else f"{item_type} {item.name}/")
        
        result = "\n".join(files) if files else "파일 없음"
        return [TextContent(type="text", text=result)]
    
    raise ValueError(f"알 수 없는 도구: {name}")

async def main():
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream,
            InitializationOptions(server_name="file-system-server", server_version="1.0.0",
                capabilities=server.get_capabilities(notification_options=None, experimental_capabilities={})))

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

TypeScript로 MCP 서버 만들기

npm install @modelcontextprotocol/sdk

TypeScript/JavaScript 예제 코드입니다.

// server.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import fetch from "node-fetch";

const server = new Server(
  { name: "weather-server", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

// 도구 목록 정의
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "get_weather",
      description: "도시의 현재 날씨를 가져옵니다",
      inputSchema: {
        type: "object",
        properties: {
          city: {
            type: "string",
            description: "도시 이름 (영문)",
          },
        },
        required: ["city"],
      },
    },
  ],
}));

// 도구 호출 처리
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === "get_weather") {
    const city = args?.city as string;
    const apiKey = process.env.OPENWEATHER_API_KEY;

    const response = await fetch(
      `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric&lang=kr`
    );
    const data = (await response.json()) as any;

    if (data.cod !== 200) {
      return {
        content: [{ type: "text", text: `오류: ${data.message}` }],
      };
    }

    return {
      content: [
        {
          type: "text",
          text: `${city} 날씨: ${data.weather[0].description}, 기온 ${data.main.temp}°C, 체감 ${data.main.feels_like}°C`,
        },
      ],
    };
  }

  throw new Error(`알 수 없는 도구: ${name}`);
});

// 서버 시작
const transport = new StdioServerTransport();
await server.connect(transport);

Claude Desktop에 연결하기

설정 파일 예시입니다.

// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
// %APPDATA%\Claude\claude_desktop_config.json (Windows)
{
  "mcpServers": {
    "my-calculator": {
      "command": "python",
      "args": ["/path/to/server.py"],
      "env": {}
    },
    "file-system": {
      "command": "python",
      "args": ["/path/to/file_server.py"],
      "env": {
        "ALLOWED_DIR": "/Users/username/Documents"
      }
    },
    "weather": {
      "command": "node",
      "args": ["/path/to/weather-server/build/index.js"],
      "env": {
        "OPENWEATHER_API_KEY": "your-api-key-here"
      }
    }
  }
}

Claude Desktop을 재시작하면 채팅 창 하단에 🔧 도구 아이콘이 표시됩니다.


Resources와 Prompts 사용

Resources (데이터 읽기)

아래는 list_resources 함수 구현 예제입니다. 위에서 설명한 핵심 로직을 담고 있습니다.

from mcp.types import Resource, BlobResourceContents, TextResourceContents

@server.list_resources()
async def list_resources() -> list[Resource]:
    return [
        Resource(
            uri="file:///config.json",
            name="설정 파일",
            description="애플리케이션 설정",
            mimeType="application/json"
        )
    ]

@server.read_resource()
async def read_resource(uri: str) -> str | bytes:
    if uri == "file:///config.json":
        return '{"version": "1.0", "debug": false}'
    raise ValueError(f"알 수 없는 리소스: {uri}")

Prompts (재사용 가능한 프롬프트)

아래는 list_prompts 함수 구현 예제입니다. 위에서 설명한 핵심 로직을 담고 있습니다.

from mcp.types import Prompt, PromptArgument, GetPromptResult, PromptMessage

@server.list_prompts()
async def list_prompts() -> list[Prompt]:
    return [
        Prompt(
            name="code_review",
            description="코드 리뷰 요청 프롬프트",
            arguments=[
                PromptArgument(name="language", description="프로그래밍 언어", required=True),
                PromptArgument(name="focus", description="리뷰 중점 사항", required=False)
            ]
        )
    ]

@server.get_prompt()
async def get_prompt(name: str, arguments: dict | None) -> GetPromptResult:
    if name == "code_review":
        language = arguments.get("language", "코드") if arguments else "코드"
        focus = arguments.get("focus", "전반적인 품질") if arguments else "전반적인 품질"
        
        return GetPromptResult(
            description=f"{language} 코드 리뷰",
            messages=[
                PromptMessage(
                    role="user",
                    content=TextContent(
                        type="text",
                        text=f"""다음 {language} 코드를 리뷰해주세요. 특히 {focus}에 집중해주세요:
                        
1. 코드 품질 및 가독성
2. 잠재적 버그 및 오류
3. 성능 최적화 기회
4. 보안 취약점
5. 개선 제안 (코드 예제 포함)"""
                    )
                )
            ]
        )
    raise ValueError(f"알 수 없는 프롬프트: {name}")

인기 MCP 서버 모음

Anthropic과 커뮤니티가 제공하는 공식 MCP 서버들입니다.

터미널에서 다음 명령어를 실행합니다.

# 파일시스템 서버 (공식)
npx -y @modelcontextprotocol/server-filesystem /path/to/directory

# SQLite 서버 (공식)
npx -y @modelcontextprotocol/server-sqlite /path/to/database.db

# GitHub 서버 (공식)
npx -y @modelcontextprotocol/server-github

# PostgreSQL 서버
npx -y @modelcontextprotocol/server-postgres postgresql://user:pass@localhost/db

# Slack 서버
npx -y @modelcontextprotocol/server-slack

# Brave Search 서버
npx -y @modelcontextprotocol/server-brave-search

마치며

MCP는 AI 에이전트 생태계의 핵심 인프라로 빠르게 자리잡고 있습니다. 지금 배워두면 AI 에이전트 개발에서 큰 경쟁력을 가질 수 있습니다.

다음 단계: