1. MCP 프로토콜 내부 구조 해부
MCP(Model Context Protocol)는 AI 모델과 외부 시스템을 연결하는 표준 프로토콜입니다. 이번 글에서는 이전 MCP 개요 가이드에서 다루지 못했던 **프로토콜 내부 동작 원리**를 깊이 파헤치고, **직접 MCP 서버를 구축**하는 방법을 알아봅니다.
1-1. JSON-RPC 2.0 기반 메시지 구조
MCP의 모든 통신은 **JSON-RPC 2.0** 사양을 따릅니다. 메시지는 크게 세 가지 유형으로 나뉩니다.
**Request (요청)**: 클라이언트 또는 서버가 상대방에게 작업을 요청할 때 보냅니다.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"city": "Seoul"
}
}
}
**Response (응답)**: 요청에 대한 결과를 반환합니다.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "Seoul: 15 degrees, partly cloudy"
}
]
}
}
**Notification (알림)**: 응답을 기대하지 않는 단방향 메시지입니다. `id` 필드가 없습니다.
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
**Error Response (에러 응답)**: 요청 처리에 실패했을 때 반환합니다.
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Invalid params: city is required"
}
}
주요 에러 코드는 다음과 같습니다:
| 코드 | 의미 | 설명 |
| ------ | ---------------- | -------------------- |
| -32700 | Parse Error | JSON 파싱 실패 |
| -32600 | Invalid Request | 잘못된 요청 형식 |
| -32601 | Method Not Found | 존재하지 않는 메서드 |
| -32602 | Invalid Params | 잘못된 파라미터 |
| -32603 | Internal Error | 서버 내부 오류 |
1-2. 연결 라이프사이클
MCP 연결은 명확한 단계를 거칩니다:
**Phase 1 - Initialize (초기화)**
클라이언트가 서버에 `initialize` 요청을 보내며, 양측의 프로토콜 버전과 기능(Capabilities)을 교환합니다.
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {}
},
"clientInfo": {
"name": "Claude Desktop",
"version": "1.5.0"
}
}
}
서버가 응답합니다:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"prompts": {
"listChanged": true
}
},
"serverInfo": {
"name": "Weather MCP Server",
"version": "1.0.0"
}
}
}
**Phase 2 - Initialized (초기화 완료)**
클라이언트가 `notifications/initialized` 알림을 보내 초기화 완료를 선언합니다.
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
**Phase 3 - Normal Operation (정상 운영)**
이제 클라이언트와 서버가 자유롭게 메시지를 주고받습니다:
- 클라이언트가 `tools/list`로 사용 가능한 도구 목록을 요청
- 클라이언트가 `tools/call`로 특정 도구를 호출
- 클라이언트가 `resources/read`로 리소스를 읽기
- 서버가 `notifications/tools/list_changed`로 도구 목록 변경 알림
**Phase 4 - Shutdown (종료)**
클라이언트가 연결을 종료하면 Transport 레이어가 정리됩니다.
1-3. Transport 방식
MCP는 두 가지 Transport 방식을 지원합니다.
**stdio (Standard Input/Output)**
가장 일반적인 방식으로, 로컬에서 MCP 서버 프로세스를 직접 실행합니다.
- 클라이언트가 서버 프로세스를 spawn
- stdin으로 요청을 보내고 stdout으로 응답을 받음
- 각 메시지는 newline으로 구분
- 디버그 로그는 stderr로 출력
- Claude Desktop, Claude Code에서 주로 사용
Client Server Process
| |
|--- spawn process -------->|
| |
|--- stdin: JSON-RPC ------>|
|<-- stdout: JSON-RPC -----|
| |
|--- stdin: JSON-RPC ------>|
|<-- stdout: JSON-RPC -----|
| |
|--- kill process --------->|
**Streamable HTTP**
원격 서버에 접속할 때 사용합니다. 2025년 3월 스펙 업데이트로 기존 HTTP+SSE 방식을 대체합니다.
- HTTP POST로 요청을 보냄
- 서버가 SSE(Server-Sent Events)로 스트리밍 응답
- 원격 배포, 멀티 테넌트 시나리오에 적합
- 세션 관리를 위한 `Mcp-Session-Id` 헤더 사용
Client HTTP Server
| |
|--- POST /mcp ----------->|
| (initialize) |
|<-- SSE: result -----------|
|<-- Mcp-Session-Id --------|
| |
|--- POST /mcp ----------->|
| (tools/call) |
|<-- SSE: result -----------|
| |
|--- DELETE /mcp ---------->|
| (session close) |
**어떤 Transport를 선택할까?**
| 기준 | stdio | Streamable HTTP |
| ----------- | ---------------------- | ------------------------ |
| 사용 사례 | 로컬 개발, 데스크톱 앱 | 원격 서버, 클라우드 배포 |
| 설정 난이도 | 매우 쉬움 | 중간 |
| 보안 | 로컬 프로세스 격리 | OAuth 2.0, TLS 필요 |
| 다중 사용자 | 불가 | 가능 |
| 네트워크 | 불필요 | 필요 |
| 대표 예시 | Claude Desktop | 팀 공유 MCP 서버 |
1-4. 세 가지 핵심 프리미티브
MCP 서버는 세 가지 유형의 기능을 AI에게 제공합니다.
**Resources (리소스)** - 읽기 전용 데이터
URI를 통해 식별되는 데이터를 제공합니다. 파일 내용, DB 스키마, API 응답 등이 해당합니다.
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/read",
"params": {
"uri": "file:///project/schema.sql"
}
}
응답:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"contents": [
{
"uri": "file:///project/schema.sql",
"mimeType": "text/plain",
"text": "CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(100));"
}
]
}
}
특징:
- 애플리케이션이 컨텍스트로 활용 (사용자 제어)
- URI 기반 식별 (file://, db://, api:// 등)
- 구독을 통해 변경 알림 수신 가능
- 텍스트 또는 바이너리(base64) 컨텐츠 지원
**Tools (도구)** - 실행 가능한 함수
AI 모델이 호출할 수 있는 함수를 정의합니다. JSON Schema로 입력 파라미터를 명시합니다.
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/list"
}
응답:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"tools": [
{
"name": "get_weather",
"description": "Get current weather for a city",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name"
}
},
"required": ["city"]
}
}
]
}
}
특징:
- 모델이 자율적으로 호출 (모델 제어)
- JSON Schema로 파라미터 타입 검증
- 부작용(side effect)을 가질 수 있음
- 사용자 확인이 필요할 수 있음 (보안)
**Prompts (프롬프트 템플릿)** - 재사용 가능한 프롬프트
자주 사용하는 프롬프트 패턴을 미리 정의합니다.
{
"jsonrpc": "2.0",
"id": 4,
"method": "prompts/get",
"params": {
"name": "code_review",
"arguments": {
"language": "python"
}
}
}
응답:
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"description": "Code review prompt for Python",
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Please review the following Python code for best practices, bugs, and performance issues."
}
}
]
}
}
1-5. Capability Negotiation 상세
초기화 시 클라이언트와 서버는 각자 지원하는 기능을 교환합니다. 서버는 제공하려는 프리미티브만 선언하면 됩니다.
**서버 Capabilities 예시**:
{
"capabilities": {
"tools": {
"listChanged": true
}
}
}
위 서버는 Tools만 제공하며, 도구 목록이 변경되면 알림을 보낼 수 있습니다. Resources나 Prompts는 제공하지 않습니다.
**전체 기능을 제공하는 서버**:
{
"capabilities": {
"tools": { "listChanged": true },
"resources": { "subscribe": true, "listChanged": true },
"prompts": { "listChanged": true },
"logging": {}
}
}
**클라이언트 Capabilities 예시**:
{
"capabilities": {
"roots": { "listChanged": true },
"sampling": {}
}
}
- `roots`: 클라이언트가 작업 중인 디렉토리/프로젝트 정보 제공
- `sampling`: 서버가 클라이언트의 LLM을 역으로 호출할 수 있음
1-6. Sampling: 서버에서 LLM 역호출
Sampling은 MCP의 독특한 기능으로, 서버가 클라이언트의 AI 모델에게 역으로 요청을 보낼 수 있습니다.
{
"jsonrpc": "2.0",
"id": 10,
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Summarize this error log: Connection timeout after 30s..."
}
}
],
"maxTokens": 500,
"modelPreferences": {
"hints": [{ "name": "claude-sonnet-4-20250514" }]
}
}
}
활용 시나리오:
- 서버가 수집한 로그 데이터를 AI에게 분석 요청
- 복잡한 데이터를 AI에게 요약 요청
- 에이전트 워크플로우에서 중간 판단 요청
단, Sampling은 보안상 사용자 확인이 필수입니다. 클라이언트가 Human-in-the-loop으로 통제합니다.
2. Python FastMCP로 MCP 서버 만들기 (실습 1)
FastMCP는 Python으로 MCP 서버를 가장 쉽게 만들 수 있는 프레임워크입니다. MCP Python SDK의 공식 고수준 API이기도 합니다.
2-1. 날씨 MCP 서버 (가장 간단한 예제)
**설치**:
pip install fastmcp
**weather_server.py**:
from fastmcp import FastMCP
MCP 서버 인스턴스 생성
mcp = FastMCP("Weather Server")
@mcp.tool()
def get_weather(city: str) -> str:
"""Get current weather for a city.
Args:
city: Name of the city (e.g., Seoul, Tokyo, New York)
"""
실제로는 날씨 API를 호출하겠지만 여기서는 간단히 시뮬레이션
weather_data = {
"Seoul": "15 degrees, partly cloudy",
"Tokyo": "18 degrees, sunny",
"New York": "10 degrees, rainy",
"London": "8 degrees, foggy",
}
result = weather_data.get(city, f"Weather data not available for {city}")
return f"Weather in {city}: {result}"
@mcp.tool()
def get_forecast(city: str, days: int = 3) -> str:
"""Get weather forecast for a city.
Args:
city: Name of the city
days: Number of days to forecast (1-7)
"""
if days < 1 or days > 7:
return "Days must be between 1 and 7"
return f"{days}-day forecast for {city}: Mostly sunny with temperatures 10-20C"
@mcp.resource("weather://cities")
def list_cities() -> str:
"""List all supported cities."""
cities = ["Seoul", "Tokyo", "New York", "London", "Paris"]
return "\n".join(cities)
if __name__ == "__main__":
mcp.run()
**실행 방법**:
직접 실행 (stdio 모드)
python weather_server.py
또는 FastMCP CLI로 실행
fastmcp run weather_server.py
MCP Inspector로 테스트
fastmcp dev weather_server.py
**Claude Desktop 연동** (claude_desktop_config.json):
{
"mcpServers": {
"weather": {
"command": "python",
"args": ["/path/to/weather_server.py"]
}
}
}
**Claude Code 연동** (.mcp.json):
{
"mcpServers": {
"weather": {
"command": "python",
"args": ["weather_server.py"]
}
}
}
2-2. 데이터베이스 MCP 서버 (실전)
실무에서 가장 많이 쓰이는 DB 연동 MCP 서버를 만들어 봅시다.
from fastmcp import FastMCP
mcp = FastMCP("Database Server")
DB_PATH = "myapp.db"
def get_connection():
"""Get database connection."""
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
@mcp.resource("db://schema")
def get_schema() -> str:
"""Get the database schema information."""
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"SELECT sql FROM sqlite_master WHERE type='table' ORDER BY name"
)
tables = cursor.fetchall()
conn.close()
schema_lines = []
for table in tables:
if table[0]:
schema_lines.append(table[0])
return "\n\n".join(schema_lines)
@mcp.resource("db://tables")
def list_tables() -> str:
"""List all tables in the database."""
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
)
tables = [row[0] for row in cursor.fetchall()]
conn.close()
return json.dumps(tables, indent=2)
@mcp.tool()
def query_database(sql: str) -> str:
"""Execute a read-only SQL query and return results.
Args:
sql: SQL SELECT query to execute
"""
보안: SELECT만 허용
if not sql.strip().upper().startswith("SELECT"):
return "Error: Only SELECT queries are allowed for safety"
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(sql)
rows = cursor.fetchall()
if not rows:
return "No results found"
columns = [description[0] for description in cursor.description]
results = []
for row in rows:
results.append(dict(zip(columns, row)))
return json.dumps(results, indent=2, ensure_ascii=False)
except Exception as e:
return f"Query error: {str(e)}"
finally:
conn.close()
@mcp.tool()
def insert_record(table: str, data: str) -> str:
"""Insert a record into a table.
Args:
table: Table name
data: JSON string of column-value pairs
"""
try:
record = json.loads(data)
except json.JSONDecodeError:
return "Error: Invalid JSON data"
columns = ", ".join(record.keys())
placeholders = ", ".join(["?" for _ in record])
values = list(record.values())
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
f"INSERT INTO {table} ({columns}) VALUES ({placeholders})",
values,
)
conn.commit()
return f"Successfully inserted record with id {cursor.lastrowid}"
except Exception as e:
return f"Insert error: {str(e)}"
finally:
conn.close()
@mcp.tool()
def update_record(table: str, record_id: int, data: str) -> str:
"""Update a record in a table.
Args:
table: Table name
record_id: ID of the record to update
data: JSON string of column-value pairs to update
"""
try:
updates = json.loads(data)
except json.JSONDecodeError:
return "Error: Invalid JSON data"
set_clause = ", ".join([f"{k} = ?" for k in updates.keys()])
values = list(updates.values()) + [record_id]
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
f"UPDATE {table} SET {set_clause} WHERE id = ?", values
)
conn.commit()
return f"Successfully updated {cursor.rowcount} record(s)"
except Exception as e:
return f"Update error: {str(e)}"
finally:
conn.close()
@mcp.tool()
def delete_record(table: str, record_id: int) -> str:
"""Delete a record from a table.
Args:
table: Table name
record_id: ID of the record to delete
"""
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(f"DELETE FROM {table} WHERE id = ?", [record_id])
conn.commit()
return f"Successfully deleted {cursor.rowcount} record(s)"
except Exception as e:
return f"Delete error: {str(e)}"
finally:
conn.close()
if __name__ == "__main__":
mcp.run()
이 서버는 다음을 제공합니다:
- **Resources**: DB 스키마 정보와 테이블 목록
- **Tools**: SELECT 쿼리, INSERT, UPDATE, DELETE 작업
Claude에게 "users 테이블에서 최근 가입한 10명을 보여줘"라고 말하면 자동으로 `query_database` 도구를 호출합니다.
2-3. 파일 시스템 MCP 서버
from pathlib import Path
from fastmcp import FastMCP
mcp = FastMCP("Filesystem Server")
보안: 허용된 디렉토리만 접근 가능
ALLOWED_DIRS = [
Path("/Users/dev/projects"),
Path("/Users/dev/documents"),
]
def is_path_allowed(path: str) -> bool:
"""Check if the path is within allowed directories."""
resolved = Path(path).resolve()
return any(
resolved == allowed or allowed in resolved.parents
for allowed in ALLOWED_DIRS
)
@mcp.tool()
def read_file(path: str) -> str:
"""Read contents of a file.
Args:
path: Absolute file path
"""
if not is_path_allowed(path):
return "Error: Access denied. Path is outside allowed directories."
try:
with open(path, "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
return f"Error: File not found: {path}"
except UnicodeDecodeError:
return "Error: File is not a text file"
@mcp.tool()
def write_file(path: str, content: str) -> str:
"""Write content to a file.
Args:
path: Absolute file path
content: Content to write
"""
if not is_path_allowed(path):
return "Error: Access denied. Path is outside allowed directories."
try:
with open(path, "w", encoding="utf-8") as f:
f.write(content)
return f"Successfully wrote to {path}"
except Exception as e:
return f"Error writing file: {str(e)}"
@mcp.tool()
def list_directory(path: str) -> str:
"""List contents of a directory.
Args:
path: Absolute directory path
"""
if not is_path_allowed(path):
return "Error: Access denied. Path is outside allowed directories."
try:
entries = []
for entry in sorted(Path(path).iterdir()):
entry_type = "dir" if entry.is_dir() else "file"
size = entry.stat().st_size if entry.is_file() else 0
entries.append(f"[{entry_type}] {entry.name} ({size} bytes)")
return "\n".join(entries)
except FileNotFoundError:
return f"Error: Directory not found: {path}"
@mcp.tool()
def search_files(directory: str, pattern: str) -> str:
"""Search for files matching a glob pattern.
Args:
directory: Directory to search in
pattern: Glob pattern (e.g., '*.py', '**/*.md')
"""
if not is_path_allowed(directory):
return "Error: Access denied."
matches = list(Path(directory).glob(pattern))
if not matches:
return "No files found matching the pattern"
return "\n".join(str(m) for m in matches[:50])
@mcp.resource("fs://allowed-dirs")
def get_allowed_dirs() -> str:
"""List allowed directories."""
return "\n".join(str(d) for d in ALLOWED_DIRS)
if __name__ == "__main__":
mcp.run()
핵심 보안 포인트:
- `ALLOWED_DIRS`로 접근 가능한 디렉토리를 제한
- `Path.resolve()`로 심볼릭 링크 우회를 방지
- 상위 디렉토리 탐색(`../`)을 차단
3. TypeScript SDK로 MCP 서버 만들기 (실습 2)
TypeScript는 MCP 서버 개발에서 Python 다음으로 많이 사용됩니다. 공식 `@modelcontextprotocol/sdk` 패키지가 제공됩니다.
3-1. 기본 구조
**설치**:
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
**tsconfig.json**:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"declaration": true
},
"include": ["src/**/*"]
}
**src/index.ts** (기본 구조):
// 서버 인스턴스 생성
const server = new McpServer({
name: 'My MCP Server',
version: '1.0.0',
})
// Tool 등록
server.tool(
'hello',
'Say hello to someone',
{
name: z.string().describe('Name of the person'),
},
async ({ name }) => {
return {
content: [
{
type: 'text',
text: `Hello, ${name}! Welcome to MCP.`,
},
],
}
}
)
// Resource 등록
server.resource('info', 'server://info', async (uri) => {
return {
contents: [
{
uri: uri.href,
mimeType: 'text/plain',
text: 'This is My MCP Server v1.0.0',
},
],
}
})
// Transport 연결 및 실행
async function main() {
const transport = new StdioServerTransport()
await server.connect(transport)
console.error('MCP Server running on stdio')
}
main().catch(console.error)
**실행**:
npx tsc
node dist/index.js
Zod를 사용한 파라미터 검증이 TypeScript SDK의 핵심입니다. 타입 안전성과 자동 JSON Schema 생성을 모두 얻습니다.
3-2. GitHub Issue MCP 서버 (실전)
실전에서 유용한 GitHub 이슈 관리 MCP 서버를 만들어 봅시다.
const server = new McpServer({
name: 'GitHub Issue Server',
version: '1.0.0',
})
const GITHUB_TOKEN = process.env.GITHUB_TOKEN
const BASE_URL = 'https://api.github.com'
// 공통 헤더
function getHeaders() {
return {
Authorization: `Bearer ${GITHUB_TOKEN}`,
Accept: 'application/vnd.github.v3+json',
'Content-Type': 'application/json',
'User-Agent': 'MCP-GitHub-Server',
}
}
// Tool: 이슈 목록 조회
server.tool(
'list_issues',
'List issues in a GitHub repository',
{
owner: z.string().describe('Repository owner'),
repo: z.string().describe('Repository name'),
state: z.enum(['open', 'closed', 'all']).default('open').describe('Issue state filter'),
labels: z.string().optional().describe('Comma-separated label names'),
per_page: z.number().min(1).max(100).default(10).describe('Results per page'),
},
async ({ owner, repo, state, labels, per_page }) => {
const params = new URLSearchParams({
state,
per_page: String(per_page),
})
if (labels) params.set('labels', labels)
const response = await fetch(`${BASE_URL}/repos/${owner}/${repo}/issues?${params}`, {
headers: getHeaders(),
})
if (!response.ok) {
return {
content: [
{
type: 'text' as const,
text: `Error: ${response.status} ${response.statusText}`,
},
],
}
}
const issues = await response.json()
const formatted = issues.map(
(issue: Record<string, unknown>) =>
`#${issue.number} [${issue.state}] ${issue.title}\n Labels: ${
(issue.labels as Array<Record<string, string>>).map((l) => l.name).join(', ') || 'none'
}\n Created: ${issue.created_at}`
)
return {
content: [
{
type: 'text' as const,
text: formatted.join('\n\n') || 'No issues found',
},
],
}
}
)
// Tool: 이슈 생성
server.tool(
'create_issue',
'Create a new issue in a GitHub repository',
{
owner: z.string().describe('Repository owner'),
repo: z.string().describe('Repository name'),
title: z.string().describe('Issue title'),
body: z.string().optional().describe('Issue body (Markdown)'),
labels: z.array(z.string()).optional().describe('Labels to add'),
assignees: z.array(z.string()).optional().describe('Usernames to assign'),
},
async ({ owner, repo, title, body, labels, assignees }) => {
const response = await fetch(`${BASE_URL}/repos/${owner}/${repo}/issues`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({ title, body, labels, assignees }),
})
if (!response.ok) {
const error = await response.text()
return {
content: [
{
type: 'text' as const,
text: `Error creating issue: ${response.status} - ${error}`,
},
],
}
}
const issue = await response.json()
return {
content: [
{
type: 'text' as const,
text: `Created issue #${issue.number}: ${issue.title}\nURL: ${issue.html_url}`,
},
],
}
}
)
// Tool: 이슈 닫기
server.tool(
'close_issue',
'Close an issue in a GitHub repository',
{
owner: z.string().describe('Repository owner'),
repo: z.string().describe('Repository name'),
issue_number: z.number().describe('Issue number'),
comment: z.string().optional().describe('Optional closing comment'),
},
async ({ owner, repo, issue_number, comment }) => {
// 코멘트가 있으면 먼저 추가
if (comment) {
await fetch(`${BASE_URL}/repos/${owner}/${repo}/issues/${issue_number}/comments`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({ body: comment }),
})
}
const response = await fetch(`${BASE_URL}/repos/${owner}/${repo}/issues/${issue_number}`, {
method: 'PATCH',
headers: getHeaders(),
body: JSON.stringify({ state: 'closed' }),
})
if (!response.ok) {
return {
content: [
{
type: 'text' as const,
text: `Error closing issue: ${response.status}`,
},
],
}
}
return {
content: [
{
type: 'text' as const,
text: `Closed issue #${issue_number} successfully`,
},
],
}
}
)
// Resource: 리포지토리 정보
server.resource('repo-info', 'github://repo-info', async (uri) => {
return {
contents: [
{
uri: uri.href,
mimeType: 'text/plain',
text: 'GitHub Issue MCP Server - Manage issues via AI',
},
],
}
})
// Prompt: 이슈 트리아지
server.prompt(
'triage_issues',
'Generate a prompt for triaging open issues',
{ owner: z.string(), repo: z.string() },
({ owner, repo }) => ({
messages: [
{
role: 'user' as const,
content: {
type: 'text' as const,
text: `Please triage the open issues in ${owner}/${repo}. For each issue:
1. Assess priority (critical/high/medium/low)
2. Suggest appropriate labels
3. Recommend if it should be assigned to someone
4. Provide a brief action plan`,
},
},
],
})
)
async function main() {
const transport = new StdioServerTransport()
await server.connect(transport)
console.error('GitHub Issue MCP Server running')
}
main().catch(console.error)
**package.json**:
{
"name": "github-issue-mcp",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0",
"zod": "^3.24.0"
},
"devDependencies": {
"typescript": "^5.7.0",
"@types/node": "^22.0.0"
}
}
**Claude Desktop 연동**:
{
"mcpServers": {
"github-issues": {
"command": "node",
"args": ["/path/to/github-issue-mcp/dist/index.js"],
"env": {
"GITHUB_TOKEN": "ghp_your_token_here"
}
}
}
}
4. MCP 서버 테스트와 디버깅
4-1. MCP Inspector
MCP Inspector는 MCP 서버를 로컬에서 테스트할 수 있는 공식 도구입니다.
npx로 바로 실행
npx @modelcontextprotocol/inspector
특정 서버를 지정하여 실행
npx @modelcontextprotocol/inspector python weather_server.py
FastMCP의 dev 모드 (Inspector 자동 연결)
fastmcp dev weather_server.py
Inspector가 제공하는 기능:
- 서버의 모든 도구, 리소스, 프롬프트 확인
- 도구를 직접 호출하고 결과 확인
- JSON-RPC 메시지 로그 실시간 확인
- 연결 상태 모니터링
4-2. Claude Desktop에서 연동
macOS에서 Claude Desktop 설정 파일 위치:
설정 파일 열기
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
Windows:
%APPDATA%\Claude\claude_desktop_config.json
설정 예시:
{
"mcpServers": {
"weather": {
"command": "python",
"args": ["/absolute/path/to/weather_server.py"]
},
"database": {
"command": "python",
"args": ["/absolute/path/to/db_server.py"],
"env": {
"DB_PATH": "/data/myapp.db"
}
},
"github": {
"command": "node",
"args": ["/absolute/path/to/github-mcp/dist/index.js"],
"env": {
"GITHUB_TOKEN": "ghp_xxx"
}
}
}
}
4-3. Claude Code에서 연동
프로젝트 루트에 `.mcp.json` 파일을 생성합니다:
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["./tools/my_mcp_server.py"]
}
}
}
또는 글로벌 설정:
claude mcp add my-server python ./tools/my_mcp_server.py
4-4. 로깅 전략
MCP 서버에서 디버그 로그는 **stderr**로 출력합니다. stdout은 JSON-RPC 메시지 전용이기 때문입니다.
**Python**:
stderr로 로그 출력 설정
logging.basicConfig(
level=logging.DEBUG,
stream=sys.stderr,
format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger("my-mcp-server")
@mcp.tool()
def my_tool(param: str) -> str:
logger.debug(f"Tool called with param: {param}")
try:
result = do_something(param)
logger.info(f"Tool succeeded: {result}")
return result
except Exception as e:
logger.error(f"Tool failed: {e}")
return f"Error: {str(e)}"
**TypeScript**:
// stderr로 로그 출력
function log(level: string, message: string) {
console.error(`[${new Date().toISOString()}] [${level}] ${message}`)
}
server.tool('my_tool', 'Description', { param: z.string() }, async ({ param }) => {
log('DEBUG', `Tool called with: ${param}`)
try {
const result = await doSomething(param)
log('INFO', `Success: ${result}`)
return { content: [{ type: 'text', text: result }] }
} catch (error) {
log('ERROR', `Failed: ${error}`)
return { content: [{ type: 'text', text: `Error: ${error}` }] }
}
})
4-5. 에러 핸들링 패턴
from fastmcp import FastMCP
from fastmcp.exceptions import ToolError
mcp = FastMCP("Robust Server")
@mcp.tool()
def safe_operation(param: str) -> str:
"""A tool with proper error handling."""
입력 검증
if not param or len(param) > 1000:
raise ToolError("Parameter must be 1-1000 characters")
try:
result = external_api_call(param)
return result
except ConnectionError:
raise ToolError("Failed to connect to external service. Please try again.")
except TimeoutError:
raise ToolError("Request timed out. The service may be temporarily unavailable.")
except Exception as e:
예상치 못한 에러는 상세 정보를 숨기고 일반 메시지만 반환
logging.error(f"Unexpected error: {e}", exc_info=True)
raise ToolError("An unexpected error occurred. Check server logs for details.")
5. MCP 서버 배포
5-1. Cloudflare Workers 배포
Cloudflare Workers를 사용하면 서버리스로 MCP 서버를 전 세계에 배포할 수 있습니다.
프로젝트 생성
npm create cloudflare@latest -- my-mcp-server
cd my-mcp-server
npm install @modelcontextprotocol/sdk
**src/index.ts** (Workers용):
export default {
async fetch(request: Request): Promise<Response> {
const server = new McpServer({
name: 'Cloudflare MCP Server',
version: '1.0.0',
})
// Tools 등록
server.tool('hello', 'Say hello', { name: z.string() }, async ({ name }) => ({
content: [{ type: 'text', text: `Hello ${name} from the edge!` }],
}))
// Streamable HTTP transport 처리
// 실제로는 MCP SDK의 HTTP transport handler를 사용
return new Response('MCP Server Running', { status: 200 })
},
}
배포
npx wrangler deploy
5-2. Docker 컨테이너화
FROM node:22-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY dist/ ./dist/
MCP 서버는 stdio로 통신
CMD ["node", "dist/index.js"]
docker build -t my-mcp-server .
docker run -i my-mcp-server
Claude Desktop에서 Docker 서버 연결:
{
"mcpServers": {
"my-server": {
"command": "docker",
"args": ["run", "-i", "--rm", "my-mcp-server"]
}
}
}
5-3. npm/PyPI 패키지 배포
**npm 패키지로 배포**:
{
"name": "@myorg/mcp-weather",
"version": "1.0.0",
"bin": {
"mcp-weather": "./dist/index.js"
}
}
npm publish --access public
사용자는 다음과 같이 바로 사용합니다:
{
"mcpServers": {
"weather": {
"command": "npx",
"args": ["-y", "@myorg/mcp-weather"]
}
}
}
**PyPI 패키지로 배포**:
pip install build twine
python -m build
twine upload dist/*
{
"mcpServers": {
"weather": {
"command": "uvx",
"args": ["mcp-weather"]
}
}
}
5-4. Smithery 마켓플레이스
Smithery(smithery.ai)는 MCP 서버 전용 레지스트리입니다. 등록 과정:
1. GitHub 리포지토리에 MCP 서버 코드 푸시
2. `smithery.yaml` 설정 파일 추가
3. Smithery 웹사이트에서 리포 연결
4. 자동 빌드 및 배포
smithery.yaml
name: my-weather-mcp
description: Weather MCP server for AI assistants
icon: cloud-sun
startCommand:
type: stdio
configSchema:
type: object
properties:
API_KEY:
type: string
description: Weather API key
required:
- API_KEY
commandFunction:
- node
- dist/index.js
5-5. OAuth 2.0 인증 추가
원격 MCP 서버에는 인증이 필수입니다. 2025년 6월 스펙에서 OAuth 2.0 지원이 공식화되었습니다.
const server = new McpServer({
name: 'Authenticated Server',
version: '1.0.0',
})
// OAuth 2.0 미들웨어 (Streamable HTTP transport)
async function validateToken(token: string): Promise<boolean> {
// OAuth provider에 토큰 검증 요청
const response = await fetch('https://auth.example.com/introspect', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `token=${token}`,
})
const data = await response.json()
return data.active === true
}
// HTTP handler에서 인증 확인
async function handleRequest(req: Request): Promise<Response> {
const authHeader = req.headers.get('Authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response('Unauthorized', { status: 401 })
}
const token = authHeader.slice(7)
if (!(await validateToken(token))) {
return new Response('Invalid token', { status: 403 })
}
// MCP 요청 처리 계속...
return new Response('OK')
}
6. MCP 생태계 100+ 서버 총정리
2026년 3월 현재 MCP 생태계는 폭발적으로 성장하고 있습니다. 카테고리별로 주요 서버를 정리합니다.
6-1. 데이터와 분석
| 서버 | 기능 | 활용 |
| ----------------- | ------------------------------------- | --------------------- |
| Grafana MCP | 대시보드 조회, 메트릭 쿼리, 알람 관리 | AI로 모니터링 자동화 |
| PostgreSQL MCP | SQL 쿼리, 스키마 탐색, 데이터 분석 | 자연어를 SQL로 변환 |
| MongoDB MCP | 컬렉션 CRUD, 집계 파이프라인 | NoSQL 데이터 탐색 |
| ClickHouse MCP | 분석 쿼리, 로그 조회 | 로그 기반 문제 해결 |
| BigQuery MCP | 대규모 데이터 분석 | 데이터 팀 생산성 향상 |
| MySQL MCP | SQL 쿼리, 테이블 관리 | RDBMS 직접 조작 |
| Redis MCP | 캐시 조회, 키-값 관리 | 캐시 디버깅 |
| Elasticsearch MCP | 전문 검색, 인덱스 관리 | 로그 분석 자동화 |
| Snowflake MCP | 데이터 웨어하우스 쿼리 | 비즈니스 인텔리전스 |
| DuckDB MCP | 로컬 분석 쿼리 | 빠른 데이터 탐색 |
| Prometheus MCP | 메트릭 쿼리(PromQL) | 시스템 모니터링 |
| Datadog MCP | 메트릭, 대시보드, 알림 관리 | 통합 관측성 |
**Grafana MCP 활용 예시**:
사용자: "지난 24시간 동안 CPU 사용률이 80%를 넘은 서버를 찾아줘"
Claude: [Grafana MCP의 query_metrics 도구 호출]
→ PromQL: avg by (instance)(cpu_usage_percent > 80)
→ 결과: server-03 (평균 87%), server-07 (평균 92%)
6-2. 개발 도구
| 서버 | 기능 | 활용 |
| ------------- | ---------------------------- | --------------------- |
| GitHub MCP | 이슈, PR, Actions, 코드 검색 | 코드 리뷰 자동화 |
| GitLab MCP | 파이프라인, 머지 리퀘스트 | CI/CD 관리 |
| Jira MCP | 이슈 추적, 스프린트 관리 | PM 업무 자동화 |
| Linear MCP | 이슈/프로젝트 관리 | 모던 이슈 트래커 연동 |
| Sentry MCP | 에러 추적, 성능 모니터링 | 디버깅 자동화 |
| CircleCI MCP | CI/CD 파이프라인 관리 | 빌드 모니터링 |
| npm MCP | 패키지 검색, 버전 확인 | 의존성 관리 |
| Snyk MCP | 보안 취약점 스캔 | 보안 감사 자동화 |
| SonarQube MCP | 코드 품질 분석 | 코드 리뷰 보조 |
| Postman MCP | API 테스트, 컬렉션 관리 | API 개발 효율화 |
6-3. 문서와 시각화
| 서버 | 기능 | 활용 |
| ----------------- | ----------------------------------------- | ------------------- |
| Mermaid MCP | 다이어그램 생성 (flowchart, sequence, ER) | 문서 자동화 |
| PPT/Slides MCP | 프레젠테이션 생성/편집 | 보고서 자동화 |
| Notion MCP | 페이지, 데이터베이스 CRUD | 지식 관리 |
| Google Docs MCP | 문서 읽기/편집 | 협업 문서 자동화 |
| Confluence MCP | Wiki 페이지 관리 | 기술 문서화 |
| Excalidraw MCP | 화이트보드 다이어그램 | 아키텍처 다이어그램 |
| Google Sheets MCP | 스프레드시트 읽기/편집 | 데이터 보고서 |
| Obsidian MCP | 마크다운 노트 관리 | 개인 지식 베이스 |
**Mermaid MCP 활용 예시**:
사용자: "현재 프로젝트의 아키텍처를 시퀀스 다이어그램으로 그려줘"
Claude: [Mermaid MCP의 create_diagram 도구 호출]
→ sequenceDiagram 자동 생성
→ SVG/PNG 파일로 저장
6-4. 커뮤니케이션
| 서버 | 기능 | 활용 |
| ------------------- | --------------------- | ---------------------- |
| Slack MCP | 채널 메시지, 검색, DM | 팀 커뮤니케이션 자동화 |
| Discord MCP | 서버/채널 관리 | 커뮤니티 관리 |
| Email MCP | 이메일 읽기/전송/검색 | 이메일 자동화 |
| Microsoft Teams MCP | 채팅, 미팅 관리 | 기업 협업 |
| Telegram MCP | 봇 메시지, 채널 관리 | 알림 자동화 |
| Twilio MCP | SMS, 음성 통화 | 고객 커뮤니케이션 |
6-5. 클라우드와 인프라
| 서버 | 기능 | 활용 |
| -------------- | ------------------------ | --------------------- |
| AWS MCP | S3, Lambda, EC2 관리 | 인프라 자동화 |
| Kubernetes MCP | 클러스터 관리, Pod 조회 | K8s 운영 자동화 |
| Docker MCP | 컨테이너/이미지 관리 | 개발 환경 관리 |
| Terraform MCP | IaC 실행, 상태 조회 | 인프라 코드 생성 |
| Vercel MCP | 배포, 환경 변수, 로그 | 프론트엔드 배포 |
| GCP MCP | Google Cloud 서비스 관리 | GCP 인프라 자동화 |
| Azure MCP | Azure 서비스 관리 | 엔터프라이즈 클라우드 |
| Cloudflare MCP | Workers, DNS, CDN | 엣지 컴퓨팅 관리 |
| Pulumi MCP | IaC (프로그래밍 언어) | 코드 기반 인프라 |
| Ansible MCP | 구성 관리, 자동화 | 서버 프로비저닝 |
6-6. AI와 ML
| 서버 | 기능 | 활용 |
| ---------------------- | ------------------ | ---------------- |
| HuggingFace MCP | 모델 검색, 추론 | ML 워크플로우 |
| Weights and Biases MCP | 실험 추적, 메트릭 | ML 실험 관리 |
| LangChain MCP | 체인/에이전트 실행 | AI 파이프라인 |
| Ollama MCP | 로컬 LLM 관리 | 프라이빗 AI 실행 |
| Replicate MCP | 클라우드 모델 실행 | ML 모델 배포 |
| Pinecone MCP | 벡터 DB 관리 | RAG 구축 |
| Weaviate MCP | 벡터 검색 | 시맨틱 검색 |
| ChromaDB MCP | 로컬 벡터 DB | 임베딩 관리 |
6-7. 브라우저와 스크래핑
| 서버 | 기능 | 활용 |
| ---------------- | ------------------------------ | --------------------- |
| Playwright MCP | 브라우저 자동화, 스크린샷 | 웹 테스트 |
| Puppeteer MCP | Chrome 자동화 | 크롤링 |
| Brave Search MCP | 웹 검색 API | AI에 실시간 정보 제공 |
| Firecrawl MCP | 웹사이트 크롤링, 마크다운 변환 | RAG 데이터 수집 |
| Browserbase MCP | 클라우드 브라우저 | 대규모 웹 자동화 |
| Exa MCP | 시맨틱 웹 검색 | 고품질 검색 결과 |
| Tavily MCP | AI 최적화 검색 | 리서치 자동화 |
6-8. 파일과 저장소
| 서버 | 기능 | 활용 |
| ---------------- | ---------------------- | ------------------ |
| Filesystem MCP | 로컬 파일 읽기/쓰기 | 파일 관리 |
| Google Drive MCP | 파일 검색/다운로드 | 클라우드 파일 관리 |
| S3 MCP | 버킷/객체 관리 | 클라우드 스토리지 |
| Dropbox MCP | 파일 동기화, 공유 | 파일 협업 |
| OneDrive MCP | Microsoft 파일 관리 | 기업 파일 관리 |
| Box MCP | 엔터프라이즈 파일 관리 | 기업 콘텐츠 관리 |
6-9. 디자인과 미디어
| 서버 | 기능 | 활용 |
| ---------- | ------------------------------- | ---------------- |
| Figma MCP | 디자인 파일 조회, 컴포넌트 추출 | 디자인-코드 변환 |
| Canva MCP | 디자인 생성/편집 | 마케팅 자료 |
| FFmpeg MCP | 오디오/비디오 변환 | 미디어 처리 |
| Sharp MCP | 이미지 처리 | 이미지 최적화 |
6-10. 기타 특화 서버
| 서버 | 기능 | 활용 |
| --------------- | ------------------------ | ------------------ |
| Stripe MCP | 결제, 구독 관리 | 결제 시스템 |
| Shopify MCP | 스토어, 상품 관리 | 이커머스 |
| Twilio MCP | SMS, 음성 | 커뮤니케이션 |
| SendGrid MCP | 이메일 발송 | 마케팅 이메일 |
| Airtable MCP | 스프레드시트 DB | 노코드 데이터 관리 |
| Supabase MCP | BaaS (DB, Auth, Storage) | 풀스택 개발 |
| Firebase MCP | Google BaaS | 모바일/웹 백엔드 |
| Neon MCP | 서버리스 PostgreSQL | 데이터베이스 관리 |
| PlanetScale MCP | 서버리스 MySQL | 스케일러블 DB |
| Turso MCP | 엣지 SQLite | 분산 데이터베이스 |
7. 나만의 MCP 서버 아이디어 10선
MCP 서버를 직접 만들어보면 AI의 활용 범위가 크게 넓어집니다. 다음은 실용적인 MCP 서버 아이디어입니다.
7-1. 개인 블로그 MCP
MDX 파일 기반 블로그를 관리하는 MCP 서버입니다.
from fastmcp import FastMCP
mcp = FastMCP("Blog Manager")
BLOG_DIR = "/path/to/blog/data"
@mcp.tool()
def list_posts(tag: str = "") -> str:
"""List all blog posts, optionally filtered by tag."""
posts = glob.glob(f"{BLOG_DIR}/**/*.mdx", recursive=True)
results = []
for post_path in sorted(posts, reverse=True):
with open(post_path) as f:
content = f.read()
frontmatter 파싱
if content.startswith("---"):
fm_end = content.index("---", 3)
frontmatter = yaml.safe_load(content[3:fm_end])
if tag and tag not in frontmatter.get("tags", []):
continue
results.append(
f"- {frontmatter['title']} ({frontmatter['date']})"
)
return "\n".join(results[:20])
@mcp.tool()
def create_post(title: str, tags: str, content: str) -> str:
"""Create a new blog post."""
구현...
return "Post created successfully"
7-2. 주식/암호화폐 시세 MCP
실시간 시세를 조회하고 포트폴리오를 분석합니다.
7-3. 캘린더/일정 MCP
Google Calendar나 Apple Calendar와 연동하여 일정을 관리합니다.
7-4. CI/CD 파이프라인 모니터링 MCP
GitHub Actions, Jenkins, CircleCI 파이프라인 상태를 모니터링합니다.
7-5. 쿠버네티스 로그 분석 MCP
kubectl을 래핑하여 Pod 로그를 분석하고 이상 징후를 감지합니다.
7-6. Figma 디자인 추출 MCP
Figma API로 디자인 컴포넌트를 추출하고 코드로 변환합니다.
7-7. 번역 MCP
DeepL이나 Google Translate API로 다국어 번역을 제공합니다.
7-8. 개인 메모/위키 MCP
Obsidian이나 Logseq 볼트를 AI가 탐색하고 편집할 수 있게 합니다.
7-9. 헬스케어 데이터 MCP
Apple Health나 Fitbit 데이터를 분석합니다.
7-10. 스마트홈 IoT 제어 MCP
Home Assistant API로 조명, 온도, 보안 시스템을 제어합니다.
from fastmcp import FastMCP
mcp = FastMCP("Smart Home")
HA_URL = "http://homeassistant.local:8123"
HA_TOKEN = "your_long_lived_token"
@mcp.tool()
def control_light(entity_id: str, action: str) -> str:
"""Control a smart light.
Args:
entity_id: Home Assistant entity ID (e.g., light.living_room)
action: 'on' or 'off'
"""
service = "turn_on" if action == "on" else "turn_off"
response = httpx.post(
f"{HA_URL}/api/services/light/{service}",
headers={"Authorization": f"Bearer {HA_TOKEN}"},
json={"entity_id": entity_id},
)
return f"Light {entity_id}: {action} (status: {response.status_code})"
@mcp.tool()
def get_temperature(entity_id: str) -> str:
"""Get temperature from a sensor."""
response = httpx.get(
f"{HA_URL}/api/states/{entity_id}",
headers={"Authorization": f"Bearer {HA_TOKEN}"},
)
data = response.json()
return f"Temperature: {data['state']}{data['attributes'].get('unit_of_measurement', '')}"
8. MCP 보안 베스트 프랙티스
MCP 서버는 AI에게 시스템 접근 권한을 부여하므로 보안이 매우 중요합니다.
8-1. 입력 검증
**Python (Pydantic)**:
from pydantic import BaseModel, Field, validator
class QueryInput(BaseModel):
sql: str = Field(..., max_length=5000)
timeout: int = Field(default=30, ge=1, le=300)
@validator("sql")
def validate_sql(cls, v):
forbidden = ["DROP", "DELETE", "TRUNCATE", "ALTER"]
upper = v.upper()
for keyword in forbidden:
if keyword in upper:
raise ValueError(f"Forbidden SQL keyword: {keyword}")
return v
**TypeScript (Zod)**:
const querySchema = z.object({
sql: z
.string()
.max(5000)
.refine(
(sql) => {
const forbidden = ['DROP', 'DELETE', 'TRUNCATE', 'ALTER']
const upper = sql.toUpperCase()
return !forbidden.some((kw) => upper.includes(kw))
},
{ message: 'Forbidden SQL keyword detected' }
),
timeout: z.number().min(1).max(300).default(30),
})
8-2. 최소 권한 원칙
- DB 서버: 읽기 전용 계정 사용 (SELECT만 허용)
- 파일 서버: 허용 디렉토리 제한, 심볼릭 링크 해결
- API 서버: 필요한 스코프만 가진 토큰 사용
- 네트워크: 내부 네트워크 접근 차단
8-3. Rate Limiting
from collections import defaultdict
class RateLimiter:
def __init__(self, max_calls: int = 10, window: int = 60):
self.max_calls = max_calls
self.window = window
self.calls = defaultdict(list)
def check(self, key: str) -> bool:
now = time.time()
self.calls[key] = [
t for t in self.calls[key] if now - t < self.window
]
if len(self.calls[key]) >= self.max_calls:
return False
self.calls[key].append(now)
return True
limiter = RateLimiter(max_calls=20, window=60)
@mcp.tool()
def rate_limited_tool(param: str) -> str:
if not limiter.check("default"):
return "Rate limit exceeded. Please wait before trying again."
return do_work(param)
8-4. 시크릿 관리
환경 변수에서 시크릿 로드 (하드코딩 금지)
DB_PASSWORD = os.environ.get("DB_PASSWORD")
API_KEY = os.environ.get("API_KEY")
if not DB_PASSWORD or not API_KEY:
raise RuntimeError("Required environment variables not set")
Claude Desktop 설정에서 환경 변수 전달:
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["server.py"],
"env": {
"DB_PASSWORD": "secret_from_keychain",
"API_KEY": "sk-xxx"
}
}
}
}
8-5. 감사 로깅
from datetime import datetime
def audit_log(tool_name: str, params: dict, result: str, success: bool):
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"tool": tool_name,
"params": params,
"success": success,
"result_length": len(result),
}
print(json.dumps(log_entry), file=sys.stderr)
8-6. 샌드박싱
Docker나 가상환경을 사용하여 MCP 서버를 격리합니다:
docker-compose.yml
services:
mcp-server:
build: .
read_only: true
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp:size=100M
mem_limit: 512m
cpus: '0.5'
networks:
- mcp-net
volumes:
- ./allowed-data:/data:ro
networks:
mcp-net:
driver: bridge
internal: true # 외부 인터넷 접근 차단
9. MCP의 미래: 2026 전망
9-1. Agent-to-Agent MCP 통신
현재 MCP는 주로 사람(사용자)-AI 간의 도구 연결에 초점이 맞춰져 있습니다. 2026년에는 AI 에이전트끼리 MCP를 통해 직접 통신하는 시나리오가 확대될 것입니다.
예를 들어:
- 코드 리뷰 에이전트가 보안 분석 에이전트에게 MCP를 통해 취약점 검사 요청
- 데이터 파이프라인 에이전트가 모니터링 에이전트에게 이상 징후 확인 요청
- 고객 지원 에이전트가 주문 관리 에이전트에게 주문 상태 조회
9-2. 멀티모달 MCP
현재 MCP는 텍스트 중심이지만, 이미지, 오디오, 비디오를 주고받는 멀티모달 확장이 진행 중입니다.
- 이미지 리소스: 스크린샷, 차트, 디자인 파일을 AI에게 직접 전달
- 오디오 처리: 회의 녹음을 AI가 분석
- 비디오 분석: 보안 카메라 영상을 AI가 모니터링
9-3. MCP Registry (중앙 서버 레지스트리)
npm이나 PyPI처럼 MCP 서버를 중앙에서 검색하고 설치할 수 있는 공식 레지스트리가 만들어지고 있습니다. 현재는 Smithery가 이 역할을 하고 있지만, 더 표준화된 레지스트리가 등장할 예정입니다.
9-4. Enterprise MCP Gateway
기업 환경에서 MCP 서버를 중앙에서 관리하는 게이트웨이가 필요합니다:
- 접근 제어: 누가 어떤 MCP 서버를 사용할 수 있는지
- 감사 로그: 모든 도구 호출 기록
- 비용 관리: API 호출 횟수 제한
- 보안 정책: 민감한 데이터 접근 통제
9-5. Linux Foundation AAIF 거버넌스
2025년 말 MCP가 Linux Foundation의 AI and AI Foundation (AAIF)에 기부되었습니다. 이는 MCP의 거버넌스가 특정 회사(Anthropic)에서 오픈 커뮤니티로 이전됨을 의미합니다.
기대 효과:
- 더 투명한 스펙 발전 과정
- 다양한 기업의 참여 촉진
- 상호운용성 표준 강화
- 인증 프로그램 도입 가능성
10. 퀴즈
지금까지 배운 내용을 확인해 봅시다.
**정답**: MCP는 **JSON-RPC 2.0** 기반입니다. 메시지 유형은 다음 세 가지입니다:
1. **Request**: id가 있는 요청 (응답 기대)
2. **Response**: 요청에 대한 결과 반환
3. **Notification**: id가 없는 단방향 알림
**정답**:
1. **stdio**: 로컬 프로세스의 stdin/stdout으로 통신. Claude Desktop이나 Claude Code에서 로컬 서버를 실행할 때 사용.
2. **Streamable HTTP**: HTTP POST와 SSE로 통신. 원격 서버 배포, 멀티 테넌트 환경에서 사용.
**정답**:
- **Resources**: URI 기반의 읽기 전용 데이터. 애플리케이션이 컨텍스트로 활용. (사용자 제어)
- **Tools**: AI 모델이 호출하는 실행 가능한 함수. JSON Schema로 파라미터 정의. 부작용 가능. (모델 제어)
- **Prompts**: 미리 정의된 재사용 가능한 프롬프트 템플릿. (사용자 제어)
**정답**: `@mcp.tool()` 데코레이터를 사용합니다. 함수의 docstring이 도구 설명이 되고, 타입 힌트가 JSON Schema로 자동 변환됩니다.
from fastmcp import FastMCP
mcp = FastMCP("My Server")
@mcp.tool()
def greet(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
**정답**:
1. **입력 검증**: Zod/Pydantic으로 모든 파라미터 검증
2. **최소 권한**: 필요한 권한만 부여 (읽기 전용 DB 계정 등)
3. **Rate Limiting**: API 호출 횟수 제한
4. **시크릿 관리**: 환경 변수 사용, 하드코딩 금지
5. **감사 로깅**: 모든 도구 호출을 기록
추가로 샌드박싱(Docker 격리)과 OAuth 2.0 인증도 중요합니다.
11. 참고 자료
1. [MCP 공식 사양](https://spec.modelcontextprotocol.io/) - Model Context Protocol 전체 스펙
2. [MCP 공식 문서](https://modelcontextprotocol.io/docs) - 시작 가이드, 튜토리얼
3. [MCP GitHub 리포지토리](https://github.com/modelcontextprotocol) - SDK, 레퍼런스 서버
4. [FastMCP 문서](https://gofastmcp.com/) - Python FastMCP 프레임워크
5. [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) - TypeScript SDK
6. [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) - Python SDK
7. [Awesome MCP Servers](https://github.com/punkpeye/awesome-mcp-servers) - 커뮤니티 MCP 서버 목록
8. [Smithery](https://smithery.ai/) - MCP 서버 레지스트리
9. [MCP Inspector](https://github.com/modelcontextprotocol/inspector) - MCP 서버 테스트 도구
10. [Claude Desktop MCP 설정 가이드](https://modelcontextprotocol.io/quickstart/user) - Claude Desktop 연동
11. [Claude Code MCP 설정](https://docs.anthropic.com/en/docs/claude-code/mcp) - Claude Code 연동
12. [Grafana MCP Server](https://github.com/grafana/mcp-grafana) - Grafana 공식 MCP 서버
13. [GitHub MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/github) - GitHub 공식 MCP 서버
14. [PostgreSQL MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/postgres) - PostgreSQL MCP
15. [Playwright MCP Server](https://github.com/microsoft/playwright-mcp) - Microsoft Playwright MCP
16. [Slack MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/slack) - Slack MCP
17. [Filesystem MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) - 파일시스템 MCP
18. [Cloudflare MCP Workers](https://developers.cloudflare.com/agents/guides/remote-mcp-server/) - Cloudflare 배포 가이드
19. [MCP OAuth 2.0 사양](https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/authorization/) - 인증 스펙
20. [Linux Foundation AAIF](https://www.linuxfoundation.org/press/linux-foundation-launches-open-standard-for-ai-agents) - MCP 거버넌스
21. [Anthropic MCP 블로그](https://www.anthropic.com/news/model-context-protocol) - MCP 발표 블로그
22. [OpenAI MCP 지원 발표](https://openai.com/index/adding-mcp-support/) - OpenAI의 MCP 채택
23. [Google MCP 지원](https://developers.googleblog.com/en/gemini-api-and-google-ai-studio-now-support-model-context-protocol-mcp/) - Google의 MCP 지원
현재 단락 (1/1425)
MCP(Model Context Protocol)는 AI 모델과 외부 시스템을 연결하는 표준 프로토콜입니다. 이번 글에서는 이전 MCP 개요 가이드에서 다루지 못했던 **프로토콜 ...