MCP 아키텍처: 설계 철학 및 엔지니어링 원칙
MCP 아키텍처: 설계 철학 및 엔지니어링 원칙
MCP의 아키텍처를 이해하려면 단순한 클라이언트-서버 패턴을 넘어서 생각해야 합니다. 이것은 AI 우선 컴퓨팅을 위해 설계된 프로토콜로, 전통적인 요청-응답 모델이 대규모 언어 모델의 동적이고 컨텍스트가 풍부한 세계를 만나는 곳입니다.
🎯 설계 철학: 이러한 선택들이 중요한 이유
AI 통합의 도전 과제
전통적인 API는 예측 가능한 인간 설계 워크플로우를 위해 설계되었습니다. AI 시스템에는 다음이 필요합니다:
- 동적 리소스 발견 (AI는 필요할 때까지 무엇이 필요한지 모릅니다)
- 풍부한 컨텍스트 교환 (데이터뿐만 아니라 메타데이터, 관계, 기능)
- 안전한 샌드박싱 (AI는 직접 시스템 액세스를 신뢰할 수 없습니다)
- 양방향 통신 (AI는 데이터를 소비하는 것뿐만 아니라 질문도 해야 합니다)
MCP의 아키텍처 대응
flowchart TB
subgraph "🧠 AI 우선 설계 원칙"
A["🔍 동적 발견<br/>AI가 필요한 것을 찾습니다"]
B["🛡️ 안전한 샌드박싱<br/>제어된 리소스 액세스"]
C["💬 풍부한 컨텍스트<br/>메타데이터 + 관계"]
D["🔄 양방향 흐름<br/>AI가 질문할 수 있습니다"]
end
subgraph "🏗️ MCP 아키텍처"
E["📡 프로토콜 레이어<br/>메시지 라우팅 및 수명주기"]
F["🚚 전송 레이어<br/>통신 메커니즘"]
G["🎭 기능 시스템<br/>기능 협상"]
H["🔐 보안 모델<br/>액세스 제어 및 검증"]
end
A --> E
B --> H
C --> G
D --> F
🏛️ 핵심 아키텍처: 클라이언트-서버를 넘어서
MCP는 **“중개된 액세스 패턴”**을 구현합니다 - 호스트는 AI와 외부 리소스 간의 보안 중개자 역할을 합니다:
flowchart TB
subgraph "🧠 AI 시스템 (LLM)"
AI["대규모 언어 모델<br/>필요: 컨텍스트, 도구, 데이터"]
end
subgraph "🏠 호스트 애플리케이션 (보안 중개자)"
direction TB
H["호스트 프로세스<br/>(Claude Desktop, IDE 등)"]
C1["MCP 클라이언트 A<br/>🔗 데이터베이스 액세스"]
C2["MCP 클라이언트 B<br/>🔗 파일 시스템"]
C3["MCP 클라이언트 C<br/>🔗 웹 API"]
H --> C1
H --> C2
H --> C3
end
subgraph "🌐 외부 리소스"
S1["MCP 서버 A<br/>📊 PostgreSQL"]
S2["MCP 서버 B<br/>📁 파일 시스템"]
S3["MCP 서버 C<br/>🌍 REST API"]
end
AI -.->|"컨텍스트/도구 요청"| H
C1 <-->|"보안 프로토콜"| S1
C2 <-->|"보안 프로토콜"| S2
C3 <-->|"보안 프로토콜"| S3
🔑 핵심 아키텍처 통찰
- 보안 중개자로서의 호스트: 호스트는 모든 AI-리소스 상호작용을 중개합니다
- 1:1 클라이언트-서버 매핑: 각 리소스 타입은 전용 격리 통신을 갖습니다
- 기능 기반 보안: 서버는 할 수 있는 것을 선언하고, 호스트는 허용할 것을 결정합니다
- 전송 불가지성: 프로토콜은 stdio, HTTP, WebSockets 등에서 작동합니다
🏗️ 계층형 아키텍처: 관심사 분리
프로토콜 레이어
프로토콜 레이어는 메시지 프레이밍, 요청/응답 연결 및 고수준 통신 패턴을 처리합니다.
class Protocol<Request, Notification, Result> {
// Handle incoming requests
setRequestHandler<T>(schema: T, handler: (request: T, extra: RequestHandlerExtra) => Promise<Result>): void
// Handle incoming notifications
setNotificationHandler<T>(schema: T, handler: (notification: T) => Promise<void>): void
// Send requests and await responses
request<T>(request: Request, schema: T, options?: RequestOptions): Promise<T>
// Send one-way notifications
notification(notification: Notification): Promise<void>
} class Session(BaseSession[RequestT, NotificationT, ResultT]):
async def send_request(
self,
request: RequestT,
result_type: type[Result]
) -> Result:
"""
Send request and wait for response. Raises McpError if response contains error.
"""
# Request handling implementation
async def send_notification(
self,
notification: NotificationT
) -> None:
"""Send one-way notification that doesn't expect response."""
# Notification handling implementation
async def _received_request(
self,
responder: RequestResponder[ReceiveRequestT, ResultT]
) -> None:
"""Handle incoming request from other side."""
# Request handling implementation
async def _received_notification(
self,
notification: ReceiveNotificationT
) -> None:
"""Handle incoming notification from other side."""
# Notification handling implementation핵심 클래스는 다음을 포함합니다:
ProtocolClientServer
전송 레이어
전송 레이어는 클라이언트와 서버 간의 실제 통신을 처리합니다. MCP는 여러 전송 메커니즘을 지원합니다:
Stdio 전송
- 통신을 위해 표준 입출력을 사용합니다
- 로컬 프로세스에 이상적입니다
SSE와 함께하는 HTTP 전송
- 서버-클라이언트 메시지를 위해 Server-Sent Events를 사용합니다
- 클라이언트-서버 메시지를 위해 HTTP POST를 사용합니다
모든 전송은 메시지 교환을 위해 JSON-RPC 2.0을 사용합니다. Model Context Protocol 메시지 형식에 대한 자세한 정보는 명세를 참조하세요.
메시지 타입
MCP에는 다음과 같은 주요 메시지 타입이 있습니다:
**요청(Requests)**은 다른 쪽의 응답을 기대합니다:
interface Request { method: string; params?: { ... }; }**결과(Results)**는 요청에 대한 성공적인 응답입니다:
interface Result { [key: string]: unknown; }**오류(Errors)**는 요청이 실패했음을 나타냅니다:
interface Error { code: number; message: string; data?: unknown; }**알림(Notifications)**은 응답을 기대하지 않는 일방향 메시지입니다:
interface Notification { method: string; params?: { ... }; }
연결 수명주기
1. 초기화
sequenceDiagram
participant Client
participant Server
Client->>Server: initialize 요청
Server->>Client: initialize 응답
Client->>Server: initialized 알림
Note over Client,Server: 연결 사용 준비 완료
- 클라이언트는 프로토콜 버전과 기능으로
initialize요청을 보냅니다 - 서버는 프로토콜 버전과 기능으로 응답합니다
- 클라이언트는 확인으로
initialized알림을 보냅니다 - 정상적인 메시지 교환이 시작됩니다
2. 메시지 교환
초기화 후 다음 패턴이 지원됩니다:
- 요청-응답: 클라이언트나 서버가 요청을 보내면 다른 쪽이 응답합니다
- 알림: 양쪽 모두 일방향 메시지를 보낼 수 있습니다
3. 종료
양쪽 모두 연결을 종료할 수 있습니다:
close()를 통한 정상 종료- 전송 연결 끊김
- 오류 상황
오류 처리
MCP는 다음과 같은 표준 오류 코드를 정의합니다:
enum ErrorCode {
// Standard JSON-RPC error codes
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603
}SDK와 애플리케이션은 -32000 이상의 자체 오류 코드를 정의할 수 있습니다.
오류는 다음을 통해 전파됩니다:
- 요청에 대한 오류 응답
- 전송에서의 오류 이벤트
- 프로토콜 수준 오류 핸들러
구현 예제
MCP 서버를 구현하는 기본 예제입니다:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server({
name: "example-server",
version: "1.0.0"
}, {
capabilities: {
resources: {}
}
});
// Handle requests
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "example://resource",
name: "Example Resource"
}
]
};
});
// Connect transport
const transport = new StdioServerTransport();
await server.connect(transport); import asyncio
import mcp.types as types
from mcp.server import Server
from mcp.server.stdio import stdio_server
app = Server("example-server")
@app.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri="example://resource",
name="Example Resource"
)
]
async def main():
async with stdio_server() as streams:
await app.run(
streams[0],
streams[1],
app.create_initialization_options()
)
if __name__ == "__main__":
asyncio.run(main)모범 사례
전송 선택
로컬 통신
- 로컬 프로세스에는 stdio 전송을 사용하세요
- 동일 머신 통신에 효율적입니다
- 간단한 프로세스 관리
원격 통신
- HTTP 호환성이 필요한 시나리오에는 SSE를 사용하세요
- 인증 및 권한 부여를 포함한 보안 영향을 고려하세요
메시지 처리
요청 처리
- 입력을 철저히 검증하세요
- 타입 안전 스키마를 사용하세요
- 오류를 우아하게 처리하세요
- 타임아웃을 구현하세요
진행률 보고
- 긴 작업에는 진행률 토큰을 사용하세요
- 점진적으로 진행률을 보고하세요
- 총 진행률을 알 때 포함하세요
오류 관리
- 적절한 오류 코드를 사용하세요
- 유용한 오류 메시지를 포함하세요
- 오류 시 리소스를 정리하세요
보안 고려사항
전송 보안
- 원격 연결에는 TLS를 사용하세요
- 연결 출처를 검증하세요
- 필요시 인증을 구현하세요
메시지 검증
- 모든 수신 메시지를 검증하세요
- 입력을 정화하세요
- 메시지 크기 제한을 확인하세요
- JSON-RPC 형식을 검증하세요
리소스 보호
- 액세스 제어를 구현하세요
- 리소스 경로를 검증하세요
- 리소스 사용량을 모니터링하세요
- 요청 속도를 제한하세요
오류 처리
- 민감한 정보를 유출하지 마세요
- 보안 관련 오류를 기록하세요
- 적절한 정리를 구현하세요
- DoS 시나리오를 처리하세요
디버깅 및 모니터링
로깅
- 프로토콜 이벤트를 기록하세요
- 메시지 흐름을 추적하세요
- 성능을 모니터링하세요
- 오류를 기록하세요
진단
- 상태 확인을 구현하세요
- 연결 상태를 모니터링하세요
- 리소스 사용량을 추적하세요
- 성능을 프로파일링하세요
테스팅
- 다른 전송을 테스트하세요
- 오류 처리를 검증하세요
- 엣지 케이스를 확인하세요
- 서버 부하 테스트를 하세요