본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 2장: 프로토콜 아키텍처 심층 분석
2026년 1월 23일·AI / ML·

2장: 프로토콜 아키텍처 심층 분석

MCP의 JSON-RPC 2.0 기반 메시지 형식, 연결 생명주기, 능력 협상 메커니즘을 상세히 분석합니다.

20분428자12개 섹션
mcptypescriptpython
공유
mcp-guide2 / 10
12345678910
이전1장: MCP 소개와 핵심 개념다음3장: 전송 계층 - stdio와 Streamable HTTP

JSON-RPC 2.0 기반 통신

MCP는 JSON-RPC 2.0을 메시지 형식으로 채택하고 있습니다. JSON-RPC는 JSON으로 인코딩된 원격 프로시저 호출(Remote Procedure Call) 프로토콜로, 단순하면서도 구조화된 요청-응답 패턴을 제공합니다.

MCP가 gRPC나 GraphQL 대신 JSON-RPC를 선택한 이유는 명확합니다. JSON-RPC는 프로토콜 자체가 매우 단순하여 다양한 전송 계층(stdio, HTTP, WebSocket) 위에서 동작할 수 있으며, 양방향 통신을 자연스럽게 지원합니다. MCP에서는 클라이언트가 서버에 요청을 보내는 것뿐 아니라, 서버가 클라이언트에 요청을 보내는 역방향 통신도 필요하기 때문입니다.

메시지의 세 가지 유형

MCP에서 교환되는 모든 메시지는 세 가지 유형 중 하나에 해당합니다.

요청(Request)

한쪽에서 다른 쪽에 특정 작업을 요청하는 메시지입니다. 반드시 응답이 돌아와야 합니다.

요청 메시지 구조
json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "city": "서울"
    }
  }
}
  • jsonrpc: 항상 "2.0"입니다.
  • id: 요청을 식별하는 고유 값입니다. 응답에서 동일한 id를 사용하여 어떤 요청에 대한 응답인지 매칭합니다.
  • method: 호출할 메서드의 이름입니다.
  • params: 메서드에 전달할 매개변수입니다.

응답(Response)

요청에 대한 결과를 반환하는 메시지입니다. 성공과 실패 두 가지 형태가 있습니다.

성공 응답
json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "서울의 현재 날씨: 맑음, 18도"
      }
    ]
  }
}
실패 응답
json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": "city 매개변수는 필수입니다."
  }
}

알림(Notification)

응답을 기대하지 않는 단방향 메시지입니다. id 필드가 없는 것이 특징입니다.

알림 메시지
json
{
  "jsonrpc": "2.0",
  "method": "notifications/tools/list_changed"
}

알림은 주로 상태 변경을 통보하는 데 사용됩니다. 예를 들어 서버의 도구 목록이 변경되었을 때, 진행 상황을 보고할 때, 로그 메시지를 전달할 때 알림을 보냅니다.

연결 생명주기

MCP 클라이언트와 서버 간의 연결은 명확한 생명주기를 따릅니다.

1단계: 초기화(Initialization)

연결이 수립된 후 가장 먼저 수행되는 것은 초기화 핸드셰이크입니다.

클라이언트가 initialize 요청을 보내며, 이 요청에는 클라이언트의 정보와 지원하는 프로토콜 버전, 클라이언트의 능력(Capabilities)이 포함됩니다.

initialize 요청
json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
      "roots": { "listChanged": true },
      "sampling": {},
      "elicitation": {}
    },
    "clientInfo": {
      "name": "my-mcp-client",
      "version": "1.0.0"
    }
  }
}

서버는 자신의 정보와 능력을 응답으로 반환합니다.

initialize 응답
json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
      "tools": { "listChanged": true },
      "resources": { "subscribe": true, "listChanged": true },
      "prompts": { "listChanged": true }
    },
    "serverInfo": {
      "name": "weather-server",
      "version": "2.1.0"
    }
  }
}

클라이언트는 응답을 확인한 후 initialized 알림을 보내 초기화 완료를 통보합니다. 이 과정이 능력 협상(Capability Negotiation)입니다.

2단계: 운영(Operation)

초기화가 완료되면 클라이언트와 서버는 자유롭게 메시지를 교환할 수 있습니다. 이 단계에서 실제 도구 호출, 리소스 읽기, 프롬프트 실행 등이 이루어집니다.

3단계: 종료(Shutdown)

연결을 종료할 때는 클라이언트가 서버에 종료를 요청하고, 서버가 정리 작업을 수행한 뒤 응답합니다.

능력 협상 메커니즘

능력 협상은 MCP의 핵심 설계 원칙 중 하나입니다. 클라이언트와 서버가 서로의 기능을 동적으로 파악하여, 양쪽이 모두 지원하는 기능만 사용하도록 합니다.

서버 능력

서버가 선언할 수 있는 능력은 다음과 같습니다.

서버 능력 타입
typescript
interface ServerCapabilities {
  tools?: {
    listChanged?: boolean;  // 도구 목록 변경 알림 지원
  };
  resources?: {
    subscribe?: boolean;    // 리소스 구독 지원
    listChanged?: boolean;  // 리소스 목록 변경 알림 지원
  };
  prompts?: {
    listChanged?: boolean;  // 프롬프트 목록 변경 알림 지원
  };
  logging?: {};             // 로깅 지원
}

서버가 tools 능력을 선언하지 않으면, 클라이언트는 해당 서버에 도구 관련 요청을 보내지 않습니다. 이를 통해 서버는 자신이 제공하는 기능의 범위를 명확히 정의할 수 있습니다.

클라이언트 능력

클라이언트가 선언할 수 있는 능력은 다음과 같습니다.

클라이언트 능력 타입
typescript
interface ClientCapabilities {
  roots?: {
    listChanged?: boolean;  // 루트 목록 변경 알림 지원
  };
  sampling?: {};            // 샘플링(LLM 호출) 지원
  elicitation?: {};         // 유도(사용자 입력 요청) 지원
}

샘플링(Sampling)은 서버가 클라이언트에 LLM 호출을 요청하는 기능입니다. 클라이언트가 이 능력을 선언하면, 서버는 자체적으로 LLM API를 호출하지 않고도 AI 추론을 활용할 수 있습니다.

유도(Elicitation)는 서버가 클라이언트를 통해 사용자에게 추가 정보를 요청하는 기능입니다. 예를 들어 데이터베이스 서버가 연결 정보를 사용자에게 물어볼 수 있습니다.

Info

능력 협상은 하위 호환성을 보장하는 핵심 메커니즘입니다. 새로운 프로토콜 버전에 기능이 추가되더라도, 해당 기능을 지원하지 않는 구현체는 단순히 해당 능력을 선언하지 않으면 됩니다. 이를 통해 서로 다른 버전의 클라이언트와 서버가 안전하게 통신할 수 있습니다.

핵심 메서드 목록

MCP 프로토콜에서 정의하는 주요 메서드를 역할별로 정리하면 다음과 같습니다.

클라이언트 -> 서버 메서드

메서드설명유형
initialize연결 초기화 및 능력 협상요청
initialized초기화 완료 통보알림
tools/list사용 가능한 도구 목록 조회요청
tools/call특정 도구 실행요청
resources/list사용 가능한 리소스 목록 조회요청
resources/read특정 리소스 읽기요청
resources/subscribe리소스 변경 구독요청
prompts/list사용 가능한 프롬프트 목록 조회요청
prompts/get특정 프롬프트 가져오기요청
completion/complete자동완성 요청요청
ping연결 상태 확인요청

서버 -> 클라이언트 메서드

메서드설명유형
sampling/createMessageLLM 호출 요청요청
elicitation/create사용자 입력 요청요청
roots/list루트 디렉토리 목록 요청요청
notifications/tools/list_changed도구 목록 변경 통보알림
notifications/resources/list_changed리소스 목록 변경 통보알림
notifications/resources/updated리소스 내용 변경 통보알림
notifications/progress진행 상황 보고알림

메시지 흐름 예제: 도구 호출

실제 도구 호출이 프로토콜 수준에서 어떻게 이루어지는지 단계별로 살펴보겠습니다.

  1. 사용자가 호스트(AI 애플리케이션)에 질문합니다.
  2. 호스트 내의 LLM이 질문을 분석하고, 날씨 도구를 호출해야 한다고 판단합니다.
  3. MCP 클라이언트가 서버에 tools/call 요청을 보냅니다.
  4. MCP 서버가 실제 날씨 API를 호출하여 데이터를 가져옵니다.
  5. 서버가 결과를 클라이언트에 반환합니다.
  6. 클라이언트가 결과를 LLM에 전달합니다.
  7. LLM이 결과를 자연어로 구성하여 사용자에게 응답합니다.

이 과정에서 LLM은 MCP 프로토콜의 존재를 알 필요가 없습니다. LLM은 도구의 이름, 설명, 매개변수 스키마만 알면 되고, 실제 통신은 MCP 클라이언트와 서버가 처리합니다.

프로토콜 버전 관리

MCP 사양은 날짜 기반 버전 관리를 사용합니다. 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25 등의 형식입니다.

초기화 단계에서 클라이언트가 자신이 지원하는 프로토콜 버전을 선언하면, 서버는 해당 버전 또는 호환 가능한 버전으로 응답합니다. 서버가 클라이언트의 버전을 지원하지 않으면 에러를 반환하여 연결을 거부합니다.

버전 호환성 검사
typescript
function checkVersionCompatibility(
  clientVersion: string,
  serverVersion: string
): boolean {
  const supported = ["2025-11-25", "2025-06-18", "2025-03-26"];
  return supported.includes(clientVersion) && supported.includes(serverVersion);
}
Warning

프로토콜 버전이 다른 클라이언트와 서버를 연결할 때는 주의가 필요합니다. 최신 버전의 기능(예: Elicitation, Tasks)은 해당 기능을 지원하지 않는 이전 버전의 구현체에서는 사용할 수 없습니다. 능력 협상을 통해 양쪽이 공통으로 지원하는 기능만 사용하도록 설계해야 합니다.

에러 처리

MCP는 JSON-RPC 2.0의 에러 코드 체계를 따르면서, MCP 전용 에러 코드를 추가로 정의합니다.

표준 JSON-RPC 에러 코드

코드이름설명
-32700Parse errorJSON 파싱 실패
-32600Invalid request유효하지 않은 요청
-32601Method not found존재하지 않는 메서드
-32602Invalid params유효하지 않은 매개변수
-32603Internal error서버 내부 오류

MCP 전용 에러 코드

코드이름설명
-32001Resource not found요청한 리소스 없음
-32002Tool not found요청한 도구 없음

에러 응답에는 선택적으로 data 필드를 포함하여 추가 정보를 제공할 수 있습니다. 이를 활용하면 클라이언트가 에러의 원인을 파악하고 적절한 복구 조치를 취하는 데 도움이 됩니다.

에러 처리 패턴
typescript
server.tool("query_database", "데이터베이스를 쿼리합니다", {
  query: { type: "string", description: "SQL 쿼리" },
}, async (params) => {
  try {
    const result = await db.query(params.query);
    return {
      content: [{ type: "text", text: JSON.stringify(result) }],
    };
  } catch (error) {
    return {
      content: [{ type: "text", text: "쿼리 실행에 실패했습니다: " + error.message }],
      isError: true,
    };
  }
});

도구 실행 결과에서 isError: true를 설정하면, LLM이 이 결과를 에러로 인식하고 사용자에게 적절히 안내하거나 다른 접근 방식을 시도할 수 있습니다.

진행 상황 보고

장시간 실행되는 도구의 경우, 서버는 진행 상황을 클라이언트에 실시간으로 보고할 수 있습니다. 이를 위해 요청 메시지에 _meta.progressToken을 포함시키고, 서버는 이 토큰을 사용하여 notifications/progress 알림을 보냅니다.

진행 상황 포함 요청
json
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "analyze_repository",
    "arguments": { "url": "https://github.com/example/repo" },
    "_meta": { "progressToken": "progress-1" }
  }
}
진행 상황 알림
json
{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": {
    "progressToken": "progress-1",
    "progress": 45,
    "total": 100,
    "message": "파일 분석 중... (450/1000)"
  }
}

배치 요청

JSON-RPC 2.0은 여러 요청을 하나의 배열로 묶어 보내는 배치 요청을 지원합니다. MCP에서도 이를 활용하여 네트워크 왕복을 줄일 수 있습니다.

배치 요청
json
[
  { "jsonrpc": "2.0", "id": 1, "method": "tools/list" },
  { "jsonrpc": "2.0", "id": 2, "method": "resources/list" },
  { "jsonrpc": "2.0", "id": 3, "method": "prompts/list" }
]

서버는 각 요청에 대한 응답을 배열로 반환합니다. 응답의 순서는 요청의 순서와 일치하지 않을 수 있으므로, id 필드를 통해 매칭해야 합니다.

정리

이 장에서 다룬 핵심 내용을 요약하면 다음과 같습니다.

  • MCP는 JSON-RPC 2.0을 기반으로 요청, 응답, 알림 세 가지 유형의 메시지를 교환합니다.
  • 연결 생명주기는 초기화, 운영, 종료 세 단계로 구성됩니다.
  • 능력 협상을 통해 클라이언트와 서버가 서로 지원하는 기능을 동적으로 파악합니다.
  • 에러 처리, 진행 상황 보고, 배치 요청 등의 메커니즘으로 안정적인 통신을 보장합니다.

다음 장 미리보기

3장에서는 MCP의 전송 계층(Transport Layer)을 다룹니다. stdio 전송과 Streamable HTTP 전송의 동작 원리, 각 전송 방식의 장단점, 그리고 상황에 따른 적절한 전송 방식 선택 기준을 살펴보겠습니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#mcp#typescript#python

관련 글

AI / ML

3장: 전송 계층 - stdio와 Streamable HTTP

MCP의 두 가지 핵심 전송 방식인 stdio와 Streamable HTTP의 동작 원리, 장단점, 선택 기준을 상세히 다룹니다.

2026년 1월 25일·17분
AI / ML

1장: MCP 소개와 핵심 개념

Model Context Protocol이 무엇이고, 왜 AI 생태계의 표준으로 자리 잡았는지, 그리고 이 시리즈에서 다룰 내용의 전체 지도를 살펴봅니다.

2026년 1월 21일·17분
AI / ML

4장: 서버 프리미티브 - 도구, 리소스, 프롬프트

MCP 서버가 제공하는 세 가지 핵심 프리미티브의 스키마 정의, 구현 패턴, 실전 활용 사례를 상세히 다룹니다.

2026년 1월 27일·24분
이전 글1장: MCP 소개와 핵심 개념
다음 글3장: 전송 계층 - stdio와 Streamable HTTP

댓글

목차

약 20분 남음
  • JSON-RPC 2.0 기반 통신
  • 메시지의 세 가지 유형
    • 요청(Request)
    • 응답(Response)
    • 알림(Notification)
  • 연결 생명주기
    • 1단계: 초기화(Initialization)
    • 2단계: 운영(Operation)
    • 3단계: 종료(Shutdown)
  • 능력 협상 메커니즘
    • 서버 능력
    • 클라이언트 능력
  • 핵심 메서드 목록
    • 클라이언트 -> 서버 메서드
    • 서버 -> 클라이언트 메서드
  • 메시지 흐름 예제: 도구 호출
  • 프로토콜 버전 관리
  • 에러 처리
    • 표준 JSON-RPC 에러 코드
    • MCP 전용 에러 코드
  • 진행 상황 보고
  • 배치 요청
  • 정리
  • 다음 장 미리보기