- Published on
MCP 서버 직접 만들기: 원리부터 구축까지 — Grafana, Mermaid, PPT, DB 연동 MCP 생태계 완전 해부
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 1. MCP 프로토콜 내부 구조 해부
- 2. Python FastMCP로 MCP 서버 만들기 (실습 1)
- 3. TypeScript SDK로 MCP 서버 만들기 (실습 2)
- 4. MCP 서버 테스트와 디버깅
- 5. MCP 서버 배포
- 6. MCP 생태계 100+ 서버 총정리
- 7. 나만의 MCP 서버 아이디어 10선
- 8. MCP 보안 베스트 프랙티스
- 9. MCP의 미래: 2026 전망
- 10. 퀴즈
- 11. 참고 자료
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 서버를 만들어 봅시다.
import sqlite3
import json
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 서버
import os
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 (기본 구조):
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
// 서버 인스턴스 생성
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 서버를 만들어 봅시다.
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
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:
import sys
import logging
# 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용):
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
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 서버 전용 레지스트리입니다. 등록 과정:
- GitHub 리포지토리에 MCP 서버 코드 푸시
smithery.yaml설정 파일 추가- Smithery 웹사이트에서 리포 연결
- 자동 빌드 및 배포
# 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 지원이 공식화되었습니다.
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
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
import glob
import yaml
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
import httpx
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
import time
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. 시크릿 관리
import os
# 환경 변수에서 시크릿 로드 (하드코딩 금지)
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. 감사 로깅
import json
import sys
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. 퀴즈
지금까지 배운 내용을 확인해 봅시다.
Q1. MCP의 기반 프로토콜은 무엇이며, 메시지의 세 가지 유형은?
정답: MCP는 JSON-RPC 2.0 기반입니다. 메시지 유형은 다음 세 가지입니다:
- Request: id가 있는 요청 (응답 기대)
- Response: 요청에 대한 결과 반환
- Notification: id가 없는 단방향 알림
Q2. MCP의 두 가지 Transport 방식과 각각의 주요 사용 사례는?
정답:
- stdio: 로컬 프로세스의 stdin/stdout으로 통신. Claude Desktop이나 Claude Code에서 로컬 서버를 실행할 때 사용.
- Streamable HTTP: HTTP POST와 SSE로 통신. 원격 서버 배포, 멀티 테넌트 환경에서 사용.
Q3. MCP의 세 가지 핵심 프리미티브(Resources, Tools, Prompts)의 차이점은?
정답:
- Resources: URI 기반의 읽기 전용 데이터. 애플리케이션이 컨텍스트로 활용. (사용자 제어)
- Tools: AI 모델이 호출하는 실행 가능한 함수. JSON Schema로 파라미터 정의. 부작용 가능. (모델 제어)
- Prompts: 미리 정의된 재사용 가능한 프롬프트 템플릿. (사용자 제어)
Q4. FastMCP에서 MCP 도구(Tool)를 정의하는 가장 간단한 방법은?
정답: @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}!"
Q5. MCP 서버 보안에서 가장 중요한 5가지 원칙은?
정답:
- 입력 검증: Zod/Pydantic으로 모든 파라미터 검증
- 최소 권한: 필요한 권한만 부여 (읽기 전용 DB 계정 등)
- Rate Limiting: API 호출 횟수 제한
- 시크릿 관리: 환경 변수 사용, 하드코딩 금지
- 감사 로깅: 모든 도구 호출을 기록
추가로 샌드박싱(Docker 격리)과 OAuth 2.0 인증도 중요합니다.
11. 참고 자료
- MCP 공식 사양 - Model Context Protocol 전체 스펙
- MCP 공식 문서 - 시작 가이드, 튜토리얼
- MCP GitHub 리포지토리 - SDK, 레퍼런스 서버
- FastMCP 문서 - Python FastMCP 프레임워크
- MCP TypeScript SDK - TypeScript SDK
- MCP Python SDK - Python SDK
- Awesome MCP Servers - 커뮤니티 MCP 서버 목록
- Smithery - MCP 서버 레지스트리
- MCP Inspector - MCP 서버 테스트 도구
- Claude Desktop MCP 설정 가이드 - Claude Desktop 연동
- Claude Code MCP 설정 - Claude Code 연동
- Grafana MCP Server - Grafana 공식 MCP 서버
- GitHub MCP Server - GitHub 공식 MCP 서버
- PostgreSQL MCP Server - PostgreSQL MCP
- Playwright MCP Server - Microsoft Playwright MCP
- Slack MCP Server - Slack MCP
- Filesystem MCP Server - 파일시스템 MCP
- Cloudflare MCP Workers - Cloudflare 배포 가이드
- MCP OAuth 2.0 사양 - 인증 스펙
- Linux Foundation AAIF - MCP 거버넌스
- Anthropic MCP 블로그 - MCP 발표 블로그
- OpenAI MCP 지원 발표 - OpenAI의 MCP 채택
- Google MCP 지원 - Google의 MCP 지원