MCP 개념
Model Context Protocol(MCP)은 AI 어시스턴트가 외부 데이터 소스 및 도구와 통신할 수 있도록 하는 개방형 프로토콜입니다. MCP를 통해 Claude와 같은 LLM은 파일 시스템, 데이터베이스, API 등 다양한 컨텍스트 소스에 표준화된 방식으로 접근할 수 있습니다.
- MCP는 Anthropic이 개발한 오픈 프로토콜
- JSON-RPC 2.0 기반의 클라이언트-서버 아키텍처
- stdio, HTTP SSE 등 다양한 전송 계층 지원
- Tools, Resources, Prompts 세 가지 핵심 개념
MCP란 무엇인가?
정의
Model Context Protocol(MCP)은 AI 모델이 외부 데이터 소스 및 도구와 상호작용할 수 있도록 설계된 개방형 표준 프로토콜입니다. 2024년 Anthropic이 공개한 MCP는 AI 어시스턴트의 컨텍스트 제공 방식을 표준화하여, 다양한 데이터 소스를 통합하고 확장 가능한 생태계를 구축하는 것을 목표로 합니다.
┌─────────────────────────────────────────────────────────┐
│ MCP의 핵심 가치 │
├─────────────────────────────────────────────────────────┤
│ 1. 표준화: 모든 데이터 소스에 일관된 인터페이스 제공 │
│ 2. 확장성: 새로운 도구와 리소스를 쉽게 추가 │
│ 3. 재사용성: 한 번 작성한 서버를 여러 클라이언트에서 사용 │
│ 4. 분리: 데이터 제공자와 AI 모델의 독립적 개발 │
└─────────────────────────────────────────────────────────┘
개발 배경
전통적으로 AI 어시스턴트는 다음과 같은 문제에 직면했습니다:
- 컨텍스트 제한: 모델의 지식 컷오프 이후 정보나 실시간 데이터에 접근 불가
- 통합 복잡도: 각 데이터 소스마다 커스텀 통합 코드 필요
- 유지보수 부담: API 변경 시 모든 통합 코드 수정 필요
- 재사용 불가: 한 어시스턴트용 통합을 다른 어시스턴트에서 재사용 어려움
MCP는 이러한 문제를 해결하기 위해 설계되었습니다.
| 측면 | 기존 방식 | MCP |
|---|---|---|
| 통합 방식 | 각 데이터 소스마다 커스텀 코드 | 표준 프로토콜 |
| 재사용성 | 낮음 | 높음 |
| 유지보수 | 복잡 | 단순 |
| 확장성 | 제한적 | 우수 |
주요 사용 사례
// 1. 파일 시스템 접근
"프로젝트 src/ 디렉토리의 모든 TypeScript 파일을 분석해줘"
→ MCP 파일시스템 서버가 파일 목록과 내용 제공
// 2. 데이터베이스 쿼리
"지난 30일간 매출 상위 10개 제품을 보여줘"
→ MCP PostgreSQL 서버가 데이터베이스 쿼리 실행
// 3. API 통합
"GitHub에서 open 상태인 이슈 중 'bug' 레이블이 있는 것들을 찾아줘"
→ MCP GitHub 서버가 GitHub API 호출
// 4. 도구 실행
"이 Python 코드를 실행하고 결과를 보여줘"
→ MCP 실행 환경 서버가 코드 실행
MCP 아키텍처
전체 구조
핵심 컴포넌트
1. MCP 클라이언트
AI 어시스턴트 애플리케이션이 MCP 서버와 통신하기 위해 구현하는 컴포넌트입니다.
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
// 클라이언트 생성
const client = new Client({
name: "my-ai-client",
version: "1.0.0"
}, {
capabilities: {
tools: {}
}
});
// 서버에 연결 (stdio 전송)
const transport = new StdioClientTransport({
command: "python",
args: ["server.py"]
});
await client.connect(transport);
// 사용 가능한 도구 목록 조회
const tools = await client.listTools();
console.log(tools);
2. MCP 서버
실제 데이터 소스나 도구에 접근하여 AI 클라이언트에게 기능을 제공하는 서비스입니다.
from mcp.server import Server
from mcp.server.stdio import stdio_server
# 서버 생성
server = Server("my-mcp-server")
# 도구 등록
@server.tool()
async def read_file(path: str) -> str:
"""파일 내용을 읽습니다."""
with open(path, 'r') as f:
return f.read()
# 서버 실행
async def main():
async with stdio_server() as streams:
await server.run(
streams[0],
streams[1],
server.create_initialization_options()
)
3. 전송 계층 (Transport Layer)
클라이언트와 서버 간 메시지 전송을 담당합니다.
| 전송 방식 | 특징 | 사용 사례 |
|---|---|---|
| stdio | 표준 입출력 사용, 프로세스 간 통신 | 로컬 서버, CLI 도구 |
| HTTP SSE | Server-Sent Events, 단방향 스트리밍 | 원격 서버, 웹 애플리케이션 |
| WebSocket (계획 중) | 양방향 실시간 통신 | 대화형 애플리케이션 |
프로토콜 계층
MCP vs 기존 방식
Function Calling과의 비교
| 측면 | Function Calling | MCP |
|---|---|---|
| 정의 위치 | API 요청 시 함수 정의 전달 | 서버에서 도구 정의 관리 |
| 실행 주체 | 클라이언트 애플리케이션 | MCP 서버 |
| 재사용성 | 낮음 (매번 정의 필요) | 높음 (서버 재사용) |
| 상태 관리 | 클라이언트 | 서버 (가능) |
| 복잡도 | 간단한 도구에 적합 | 복잡한 통합에 적합 |
// Function Calling 방식
const response = await anthropic.messages.create({
model: "claude-4-5",
messages: [{ role: "user", content: "파일 읽어줘" }],
tools: [{
name: "read_file",
description: "파일을 읽습니다",
input_schema: {
type: "object",
properties: {
path: { type: "string" }
}
}
}]
});
// 도구 실행은 클라이언트가 직접 처리
if (response.stop_reason === "tool_use") {
const result = await readFile(toolUse.input.path);
// 결과를 다시 Claude에게 전달...
}
// ─────────────────────────────────────
// MCP 방식
const client = new MCPClient();
await client.connect(fileSystemServer);
// 도구 목록은 서버에서 자동 제공
const tools = await client.listTools();
// 도구 실행도 서버가 처리
const result = await client.callTool("read_file", {
path: "/path/to/file"
});
Tool Use와의 관계
MCP는 Tool Use를 보완하고 확장하는 프로토콜입니다:
- Tool Use: AI 모델이 도구를 호출하도록 요청하는 기능 (모델 레벨)
- MCP: 도구를 정의하고 실행하는 표준 프로토콜 (인프라 레벨)
Claude의 Tool Use 기능과 MCP를 함께 사용하면:
- MCP 서버가 사용 가능한 도구 목록 제공
- 클라이언트가 이를 Tool Use 형식으로 변환하여 Claude에게 전달
- Claude가 도구 사용 결정
- 클라이언트가 MCP 서버를 통해 도구 실행
- 결과를 Claude에게 반환
직접 API 통합과의 비교
// 직접 API 통합 (각 클라이언트에서 반복)
class GitHubIntegration {
constructor(token) {
this.token = token;
}
async listIssues(repo) {
const response = await fetch(`https://api.github.com/repos/${repo}/issues`, {
headers: { Authorization: `token ${this.token}` }
});
return response.json();
}
// 수십 개의 메서드...
}
// ─────────────────────────────────────
// MCP 방식 (한 번만 작성, 모든 클라이언트에서 재사용)
const githubServer = await client.connect("github-mcp-server");
// 모든 GitHub 기능을 표준 인터페이스로 사용
const issues = await client.callTool("github_list_issues", {
repo: "anthropics/anthropic-sdk-python"
});
프로토콜 개요
JSON-RPC 2.0
MCP는 JSON-RPC 2.0 표준을 기반으로 합니다.
// 요청 메시지
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": {
"path": "/home/user/example.txt"
}
}
}
// 응답 메시지
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "Hello, MCP!"
}
]
}
}
// 에러 응답
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"details": "File not found"
}
}
}
메시지 유형
| 유형 | 설명 | 예제 |
|---|---|---|
| Request | 클라이언트 → 서버 요청 | tools/list, tools/call |
| Response | 서버 → 클라이언트 응답 | 성공 시 result, 실패 시 error |
| Notification | 응답 불필요한 알림 | notifications/progress |
핵심 메서드
1. 초기화
// initialize 요청
{
"jsonrpc": "2.0",
"id": 0,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"clientInfo": {
"name": "Claude Desktop",
"version": "1.0.0"
},
"capabilities": {
"tools": {}
}
}
}
// initialize 응답
{
"jsonrpc": "2.0",
"id": 0,
"result": {
"protocolVersion": "2024-11-05",
"serverInfo": {
"name": "filesystem-server",
"version": "1.0.0"
},
"capabilities": {
"tools": {},
"resources": {}
}
}
}
2. 도구 관련
// tools/list - 도구 목록 조회
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
// 응답
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "read_file",
"description": "파일 내용을 읽습니다",
"inputSchema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "파일 경로"
}
},
"required": ["path"]
}
}
]
}
}
// tools/call - 도구 실행
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": {
"path": "/etc/hosts"
}
}
}
3. 리소스 관련
// resources/list - 리소스 목록
{
"jsonrpc": "2.0",
"id": 3,
"method": "resources/list"
}
// resources/read - 리소스 읽기
{
"jsonrpc": "2.0",
"id": 4,
"method": "resources/read",
"params": {
"uri": "file:///path/to/document.txt"
}
}
4. 프롬프트 관련
// prompts/list - 프롬프트 목록
{
"jsonrpc": "2.0",
"id": 5,
"method": "prompts/list"
}
// prompts/get - 프롬프트 가져오기
{
"jsonrpc": "2.0",
"id": 6,
"method": "prompts/get",
"params": {
"name": "code-review",
"arguments": {
"language": "python"
}
}
}
전송 계층
stdio 전송
가장 일반적인 전송 방식으로, 서버를 자식 프로세스로 실행하고 표준 입출력으로 통신합니다.
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
// Python 서버 실행
const transport = new StdioClientTransport({
command: "python",
args: ["-m", "mcp_server_filesystem"],
env: {
...process.env,
"PYTHONPATH": "/path/to/server"
}
});
await client.connect(transport);
장점:
- 간단한 설정
- 로컬 프로세스로 보안 우수
- 플랫폼 독립적
단점:
- 원격 서버 지원 불가
- 프로세스 생명주기 관리 필요
HTTP SSE 전송
Server-Sent Events를 사용한 HTTP 기반 전송입니다.
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
// 원격 서버 연결
const transport = new SSEClientTransport(
new URL("https://mcp-server.example.com/sse")
);
await client.connect(transport);
장점:
- 원격 서버 지원
- HTTP 표준 기술 활용
- 방화벽 친화적
단점:
- 단방향 스트리밍 (서버 → 클라이언트)
- 네트워크 오버헤드
전송 방식 비교
| 특성 | stdio | HTTP SSE |
|---|---|---|
| 위치 | 로컬 | 로컬/원격 |
| 설정 복잡도 | 낮음 | 중간 |
| 지연시간 | 매우 낮음 | 낮음~중간 |
| 보안 | 프로세스 격리 | TLS, 인증 필요 |
| 확장성 | 제한적 | 우수 |
핵심 개념
Tools (도구)
클라이언트가 호출할 수 있는 함수나 작업입니다.
@server.tool()
async def calculate(expression: str) -> str:
"""수학 표현식을 계산합니다.
Args:
expression: 계산할 수학 표현식 (예: "2 + 2")
Returns:
계산 결과
"""
try:
result = eval(expression)
return str(result)
except Exception as e:
return f"에러: {e}"
특징:
- 실행 가능한 작업 (부작용 가능)
- 입력 스키마 정의 필요
- 동기/비동기 실행 지원
Resources (리소스)
서버가 제공하는 데이터나 컨텐츠입니다.
@server.resource("file://{path}")
async def read_file_resource(path: str) -> str:
"""파일을 리소스로 제공합니다."""
with open(path, 'r') as f:
return f.read()
특징:
- 읽기 전용 데이터
- URI로 식별
- 컨텍스트 제공 목적
Prompts (프롬프트)
재사용 가능한 프롬프트 템플릿입니다.
@server.prompt()
async def code_review_prompt(language: str, code: str) -> str:
"""코드 리뷰 프롬프트를 생성합니다."""
return f"""다음 {language} 코드를 리뷰해주세요:
```{language}
{code}
```
다음 관점에서 검토해주세요:
1. 코드 품질
2. 성능
3. 보안
4. 베스트 프랙티스
"""
특징:
- 템플릿 기반
- 매개변수화 가능
- 재사용성 높음
개념 비교
| 개념 | 목적 | 읽기/쓰기 | 예제 |
|---|---|---|---|
| Tools | 작업 수행 | 읽기/쓰기 | 파일 쓰기, 이메일 전송 |
| Resources | 데이터 제공 | 읽기 전용 | 파일 내용, DB 레코드 |
| Prompts | 프롬프트 템플릿 | 읽기 전용 | 코드 리뷰, 번역 |
연결 생명주기
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
async function main() {
// 1. 클라이언트 생성
const client = new Client({
name: "example-client",
version: "1.0.0"
}, {
capabilities: {
tools: {}
}
});
// 2. 전송 계층 설정
const transport = new StdioClientTransport({
command: "python",
args: ["server.py"]
});
try {
// 3. 연결 및 초기화
await client.connect(transport);
console.log("연결 성공!");
// 4. 작업 수행
const tools = await client.listTools();
console.log("사용 가능한 도구:", tools);
const result = await client.callTool("read_file", {
path: "/etc/hosts"
});
console.log("결과:", result);
} finally {
// 5. 종료
await client.close();
console.log("연결 종료");
}
}
main().catch(console.error);
Capabilities (기능 선언)
클라이언트와 서버는 초기화 시 지원하는 기능을 선언합니다.
클라이언트 Capabilities
{
"capabilities": {
"tools": {}, // 도구 호출 지원
"resources": {
"subscribe": true // 리소스 변경 구독
},
"prompts": {}, // 프롬프트 사용 지원
"sampling": {} // 샘플링 요청 수신 (서버→클라이언트)
}
}
서버 Capabilities
{
"capabilities": {
"tools": {
"listChanged": true // 도구 목록 변경 알림
},
"resources": {
"subscribe": true, // 리소스 구독 지원
"listChanged": true // 리소스 목록 변경 알림
},
"prompts": {
"listChanged": true // 프롬프트 목록 변경 알림
},
"logging": {} // 로그 메시지 전송
}
}
클라이언트와 서버는 초기화 시 capabilities를 교환하여 공통으로 지원하는 기능만 사용합니다. 예를 들어, 서버가 resources.subscribe를 지원하지 않으면 클라이언트는 리소스 구독 기능을 사용할 수 없습니다.
에러 처리
표준 에러 코드
| 코드 | 의미 | 설명 |
|---|---|---|
| -32700 | Parse error | JSON 파싱 실패 |
| -32600 | Invalid request | 잘못된 요청 형식 |
| -32601 | Method not found | 지원하지 않는 메서드 |
| -32602 | Invalid params | 잘못된 매개변수 |
| -32603 | Internal error | 내부 서버 오류 |
커스텀 에러
from mcp.server import McpError
@server.tool()
async def read_file(path: str) -> str:
if not os.path.exists(path):
raise McpError(
code=-32001,
message="File not found",
data={"path": path}
)
with open(path, 'r') as f:
return f.read()
클라이언트 에러 처리
try {
const result = await client.callTool("read_file", {
path: "/nonexistent.txt"
});
} catch (error) {
if (error.code === -32001) {
console.error("파일을 찾을 수 없습니다:", error.data.path);
} else {
console.error("예상치 못한 오류:", error.message);
}
}
다음 단계
- MCP 서버 개발: MCP 서버 개발 가이드에서 Python과 TypeScript로 서버를 만드는 방법 학습
- MCP 클라이언트: MCP 클라이언트 가이드에서 Claude Desktop, Claude CLI 설정 방법 학습
- 실전 예제: MCP 실전 예제에서 파일 시스템, 데이터베이스, API 통합 예제 확인
- 생태계 탐색: MCP 생태계에서 공개 서버 및 리소스 확인
- 고급 주제: MCP 고급 주제에서 보안, 성능, 배포 전략 학습
추가 리소스
핵심 정리
- MCP 개념의 핵심 개념과 흐름을 정리합니다.
- MCP란 무엇인가?를 단계별로 이해합니다.
- 실전 적용 시 기준과 주의점을 확인합니다.
실무 팁
- 입력/출력 예시를 고정해 재현성을 확보하세요.
- MCP 개념 범위를 작게 잡고 단계적으로 확장하세요.
- MCP란 무엇인가? 조건을 문서화해 대응 시간을 줄이세요.