LabHub

블로그

MCP (Model Context Protocol) 완전 가이드: AI와 외부 세계를 연결하는 새로운 표준

한국어English日本語

AI 에이전트를 만들다 보면 반드시 이런 순간이 옵니다: "이 에이전트가 우리 데이터베이스에 접근할 수 있으면 좋겠는데." 그러면 LangChain 툴을 하나 만들고, OpenAI function calling 형식으로 래핑하고, Claude에는 또 다른 방식으로 연결하고... AI 모델마다 통합 코드를 따로 작성하는 악순환에 빠집니다.

MCP는 이 문제를 해결하기 위해 Anthropic이 2024년에 발표한 오픈 표준입니다.

MCP란 무엇인가?

MCP(Model Context Protocol)는 AI 모델과 외부 도구/데이터 소스를 연결하는 표준화된 프로토콜입니다. 컴퓨터에서 USB-C가 어떤 장치든 하나의 포트로 연결하는 것처럼, MCP는 어떤 AI 모델이든 어떤 외부 도구든 하나의 인터페이스로 연결합니다.

핵심 특징:

MCP 이전의 세상 (문제점)

왜 이런 표준이 필요했을까요? 상황을 보면 명확합니다.

기존 방식의 M×N 문제:

모델 수 × 도구 수 = 통합 코드 수

Claude + (Slack + GitHub + DB + Notion) = 4개의 Claude 통합
GPT-4 + (Slack + GitHub + DB + Notion) = 4개의 GPT 통합
Gemini + (Slack + GitHub + DB + Notion) = 4개의 Gemini 통합

12개의 서로 다른 통합 코드

MCP 이후:

도구 수 = MCP 서버  (모델과 무관하게 재사용)

Slack MCP 서버 1개 → Claude, GPT-4, Gemini 모두 사용 가능
GitHub MCP 서버 1개 → 모든 모델에서 사용 가능

한번 만든 MCP 서버는 MCP를 지원하는 모든 AI 클라이언트에서 동작합니다.

MCP 아키텍처

┌─────────────────────┐         ┌──────────────────────┐
MCP Client      │◄───────►│     MCP Server  (Claude, Cursor,JSON     (your tools, DBs,IDE,)RPCAPIs, files, etc.)└─────────────────────┘         └──────────────────────┘

MCP Server가 노출하는 3가지:

├── Resources (읽기 전용 데이터)
│   └── 파일, DB 쿼리 결과, API 응답 등
├── Tools (실행 가능한 액션)
│   └── 파일 쓰기, API 호출, DB 수정 등
└── Prompts (재사용 가능한 프롬프트 템플릿)
    └── "코드 리뷰해줘" 같은 구조화된 템플릿

MCP는 서버-클라이언트 모델을 따릅니다:

MCP Server 만들기 (Python)

실제로 동작하는 MCP 서버를 만들어봅시다. 고객 데이터베이스에 접근하는 서버입니다.

from mcp.server import Server
from mcp.server.models import InitializationOptions
import mcp.types as types
import json
import asyncio

app = Server("my-database-server")

# Resources 등록: 읽기 전용 데이터 노출
@app.list_resources()
async def list_resources() -> list[types.Resource]:
    return [
        types.Resource(
            uri="db://customers",
            name="Customer Database",
            description="Access customer records. Returns up to 100 records.",
            mimeType="application/json"
        ),
        types.Resource(
            uri="db://orders",
            name="Order History",
            description="Access order history for all customers",
            mimeType="application/json"
        )
    ]

@app.read_resource()
async def read_resource(uri: str) -> str:
    if uri == "db://customers":
        # 실제 DB에서 데이터를 가져옴
        customers = await db.fetch_all(
            "SELECT id, name, email, created_at FROM customers LIMIT 100"
        )
        return json.dumps([dict(c) for c in customers])

    elif uri == "db://orders":
        orders = await db.fetch_all(
            "SELECT id, customer_id, total, status, created_at FROM orders LIMIT 100"
        )
        return json.dumps([dict(o) for o in orders])

    raise ValueError(f"Unknown resource: {uri}")

# Tools 등록: 실행 가능한 액션 노출
@app.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="create_customer",
            description="Create a new customer record in the database",
            inputSchema={
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Customer's full name"
                    },
                    "email": {
                        "type": "string",
                        "description": "Customer's email address"
                    },
                    "phone": {
                        "type": "string",
                        "description": "Customer's phone number (optional)"
                    }
                },
                "required": ["name", "email"]
            }
        ),
        types.Tool(
            name="search_customers",
            description="Search customers by name or email",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "Search query (name or email)"
                    }
                },
                "required": ["query"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "create_customer":
        result = await db.execute(
            "INSERT INTO customers (name, email, phone) VALUES (:name, :email, :phone)",
            {
                "name": arguments["name"],
                "email": arguments["email"],
                "phone": arguments.get("phone", None)
            }
        )
        return [types.TextContent(
            type="text",
            text=f"Customer created successfully. ID: {result.lastrowid}"
        )]

    elif name == "search_customers":
        query = f"%{arguments['query']}%"
        customers = await db.fetch_all(
            "SELECT id, name, email FROM customers WHERE name LIKE :q OR email LIKE :q",
            {"q": query}
        )
        if not customers:
            return [types.TextContent(type="text", text="No customers found")]

        result = "\n".join([f"ID: {c.id}, Name: {c.name}, Email: {c.email}" for c in customers])
        return [types.TextContent(type="text", text=result)]

    raise ValueError(f"Unknown tool: {name}")

# 서버 실행
async def main():
    from mcp.server.stdio import stdio_server
    async with stdio_server() as (read_stream, write_stream):
        await app.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="my-database-server",
                server_version="0.1.0"
            )
        )

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

Claude Desktop에서 MCP 서버 사용하기

서버를 만들었으면 Claude Desktop에 등록합니다:

// macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
// Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "my-database": {
      "command": "python",
      "args": ["/path/to/your/mcp_server.py"],
      "env": {
        "DATABASE_URL": "postgresql://user:pass@localhost/mydb"
      }
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/username/projects"]
    }
  }
}

설정 후 Claude Desktop을 재시작하면 Claude가 여러분의 데이터베이스에 직접 접근할 수 있습니다. "고객 목록 보여줘", "홍길동이라는 고객 추가해줘" 같은 자연어 명령이 동작합니다.

기존 통합 방식과의 차이점

OpenAI Function Calling

# OpenAI function calling — OpenAI API에 종속
tools = [{"type": "function", "function": {"name": "search", ...}}]
response = openai.chat.completions.create(tools=tools, ...)

특정 모델 API에 묶여 있습니다. Claude에서 쓰려면 다시 구현해야 합니다.

LangChain Tools

# LangChain — 프레임워크에 종속
from langchain.tools import tool

@tool
def search(query: str) -> str:
    """Search the web"""
    return web_search(query)

LangChain을 쓰지 않는 환경에서는 동작하지 않습니다.

MCP

# MCP — 어떤 MCP 클라이언트에서도 동작
@app.list_tools()
async def list_tools():
    return [types.Tool(name="search", ...)]

Claude Desktop, Cursor, 직접 만든 앱 — MCP를 지원하는 어디서든 동작합니다.

특성OpenAI Function CallingLangChain ToolsMCP
모델 독립성X (OpenAI 전용)부분적O
표준화XXO (오픈 표준)
재사용성낮음중간높음
설정 복잡도낮음중간중간
생태계OpenAI 생태계LangChain 생태계성장 중

MCP 생태계 현황 (2025-2026)

공식적으로 제공되는 MCP 서버들이 빠르게 늘고 있습니다:

공식 서버 (Anthropic 제공):

커뮤니티 서버:

설치는 간단합니다:

# npm으로 설치 (Node.js 서버의 경우)
npx -y @modelcontextprotocol/server-github

# pip으로 설치 (Python 서버의 경우)
pip install mcp

실전 팁: MCP 서버를 잘 만드는 법

1. Tool description을 구체적으로 작성하라

# 나쁜 예
types.Tool(name="query_db", description="Query database")

# 좋은 예
types.Tool(
    name="query_customers",
    description=(
        "Search customer records. Use this when you need to find customers "
        "by name, email, or ID. Returns customer ID, name, email, and creation date. "
        "Use search_orders for order-related queries."
    )
)

2. 에러를 툴 결과로 반환하라

@app.call_tool()
async def call_tool(name: str, arguments: dict):
    try:
        result = await execute_operation(arguments)
        return [types.TextContent(type="text", text=json.dumps(result))]
    except Exception as e:
        # 에러도 정상 응답으로 반환 — LLM이 에러를 보고 대응할 수 있음
        return [types.TextContent(
            type="text",
            text=f"Error: {str(e)}. Please check the input and try again."
        )]

3. 읽기와 쓰기를 명확히 분리하라

Resources = 읽기 전용 (DB SELECT, 파일 읽기, API GET) Tools = 쓰기 작업 (DB INSERT/UPDATE/DELETE, 파일 쓰기, API POST)

이 구분이 명확할수록 AI가 안전하게 판단합니다. "이 작업은 데이터를 변경하는 건가?" 같은 의사결정이 아키텍처 수준에서 명확해집니다.

마치며

MCP는 아직 성숙 중인 표준입니다. 하지만 방향은 분명합니다 — AI 생태계의 공통 언어가 될 가능성이 높습니다. Cursor, Zed, VS Code 같은 IDE들이 MCP 지원을 추가하고 있고, 서드파티 서버 생태계가 빠르게 성장하고 있습니다.

지금 AI 앱을 만들고 있다면, 툴 통합을 MCP 형식으로 구현해두는 것을 추천합니다. 나중에 다른 AI 모델을 지원할 때 재사용할 수 있습니다.

다음 글에서는 여러 에이전트를 조율하는 Multi-Agent 시스템 — AutoGen, CrewAI, LangGraph를 비교합니다.

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다