Skip to content

필사 모드: MCP 서버 직접 만들기: 원리부터 구축까지 — Grafana, Mermaid, PPT, DB 연동 MCP 생태계 완전 해부

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

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 개요 가이드에서 다루지 못했던 **프로토콜 ...

작성 글자: 0원문 글자: 35,234작성 단락: 0/1425