Skip to content

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

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

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

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

MCP란 무엇인가?

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

핵심 특징:

- **오픈 표준**: Anthropic이 만들었지만 특정 회사에 종속되지 않음

- **양방향**: 클라이언트(AI)와 서버(도구) 모두 메시지를 보낼 수 있음

- **JSON-RPC 기반**: 단순하고 검증된 프로토콜

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, 등) │ RPC │ APIs, files, etc.) │

└─────────────────────┘ └──────────────────────┘

MCP Server가 노출하는 3가지:

├── Resources (읽기 전용 데이터)

│ └── 파일, DB 쿼리 결과, API 응답 등

├── Tools (실행 가능한 액션)

│ └── 파일 쓰기, API 호출, DB 수정 등

└── Prompts (재사용 가능한 프롬프트 템플릿)

└── "코드 리뷰해줘" 같은 구조화된 템플릿

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

- **MCP Client**: Claude Desktop, Cursor, 직접 만든 AI 앱 등

- **MCP Server**: 여러분이 만드는 도구 서버 (데이터베이스, API, 파일 시스템 등)

- **Transport**: stdio (로컬) 또는 HTTP/SSE (원격)

MCP Server 만들기 (Python)

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

from mcp.server import Server

from mcp.server.models import InitializationOptions

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 Calling | LangChain Tools | MCP |

|------|------------------------|-----------------|-----|

| 모델 독립성 | X (OpenAI 전용) | 부분적 | O |

| 표준화 | X | X | O (오픈 표준) |

| 재사용성 | 낮음 | 중간 | 높음 |

| 설정 복잡도 | 낮음 | 중간 | 중간 |

| 생태계 | OpenAI 생태계 | LangChain 생태계 | 성장 중 |

MCP 생태계 현황 (2025-2026)

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

**공식 서버 (Anthropic 제공):**

- `@modelcontextprotocol/server-filesystem` — 로컬 파일 시스템

- `@modelcontextprotocol/server-github` — GitHub 저장소

- `@modelcontextprotocol/server-postgres` — PostgreSQL 데이터베이스

- `@modelcontextprotocol/server-brave-search` — Brave Search API

- `@modelcontextprotocol/server-slack` — Slack 메시지

**커뮤니티 서버:**

- Notion, Jira, Linear 연동

- Docker, Kubernetes 관리

- AWS, GCP 리소스 접근

설치는 간단합니다:

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를 비교합니다.

현재 단락 (1/230)

AI 에이전트를 만들다 보면 반드시 이런 순간이 옵니다: "이 에이전트가 우리 데이터베이스에 접근할 수 있으면 좋겠는데." 그러면 LangChain 툴을 하나 만들고, OpenAI...

작성 글자: 0원문 글자: 6,853작성 단락: 0/230