MCP 기초 — Primitives, Lifecycle, JSON-RPC Base
MCP 이전의 모든 통합(integration)은 일회성이었습니다. 모델 컨텍스트 프로토콜(Model Context Protocol; MCP)은 2024년 11월에 Anthropic이 처음 공개했고, 지금은 리눅스 재단(Linux Foundation)의 Agentic AI Foundation이 관리하고 있습니다. MCP는 도구의 발견(discovery)과 호출(invocation)을 표준화해서 어떤 클라이언트(client)든 어떤 서버(server)와도 대화할 수 있게 만듭니다. 2025-11-25 사양(spec)은 여섯 가지 기본 요소(primitives, 서버 측 셋·클라이언트 측 셋), 세 단계 생애주기(lifecycle), 그리고 JSON-RPC 2.0 와이어 포맷(wire format)을 정의합니다. 이 세 가지만 익히면, 이 phase의 나머지 MCP 장은 대부분 "그저 읽기만 하면 되는" 내용으로 줄어듭니다.
유형: Learn
언어: Python (표준 라이브러리, JSON-RPC 파서)
선수 지식: Phase 13 · 01~05 (도구 인터페이스와 함수 호출(function calling))
예상 시간: 약 45분
학습 목표
- MCP의 여섯 기본 요소(primitive)를 모두 말할 수 있습니다. 서버 측의 도구(tools), 자원(resources), 프롬프트(prompts)와 클라이언트 측의 루트(roots), 샘플링(sampling), 일레시테이션(elicitation)을 각각 하나의 사용 사례(use case)와 함께 설명합니다.
- 세 단계 생애주기(initialize, operation, shutdown)를 따라가며 각 단계에서 누가 어떤 메시지를 보내는지 말합니다.
- JSON-RPC 2.0의 요청(request), 응답(response), 알림(notification) 봉투(envelope)를 파싱하고 직접 만들어 봅니다.
initialize 단계의 능력 협상(capability negotiation)이 무엇인지, 이것이 없으면 무엇이 깨지는지 설명합니다.
문제
MCP 이전에는 도구를 사용하는 에이전트(tool-using agent)마다 자체적인 프로토콜이 있었습니다. Cursor에는 MCP와 비슷해 보이지만 호환되지는 않는 도구 시스템이 있었습니다. Claude Desktop은 또 다른 방식을 제공했습니다. VS Code의 Copilot 확장(extension)에는 세 번째 방식이 있었습니다. "Postgres 쿼리(query)" 도구를 만드는 팀은 같은 도구를 세 번 작성해야 했고, 매번 각 호스트(host)의 API에 맞춰 다시 만들어야 했습니다. 재사용하려면 코드를 그대로 복사해 옮겨 붙이는 수밖에 없었습니다.
그 결과는 일회성 통합의 캄브리아기 대폭발이었고, 생태계 발전 속도(ecosystem velocity)에는 분명한 천장이 생겼습니다.
MCP는 이 문제를 와이어 포맷을 표준화해서 해결합니다. 하나의 MCP 서버는 모든 MCP 클라이언트에서 그대로 동작합니다. Claude Desktop, ChatGPT, Cursor, VS Code, Gemini, Goose, Zed, Windsurf 등 2026년 4월 기준 300개가 넘는 클라이언트가 있습니다. SDK 월간 다운로드는 1억 1천만 건, 공개 서버는 10,000개를 넘습니다. 리눅스 재단은 2025년 12월에 새로 만들어진 Agentic AI Foundation 아래에서 MCP의 관리(stewardship)를 맡았습니다.
이 phase에서 사용하는 사양 리비전(spec revision)은 2025-11-25입니다. 이 리비전은 비동기 작업(async Tasks; SEP-1686), URL 모드 일레시테이션(URL-mode elicitation; SEP-1036), 도구를 사용하는 샘플링(sampling with tools; SEP-1577), 점진적 범위 동의(incremental scope consent; SEP-835), OAuth 2.1의 자원 표시자 의미 체계(resource-indicator semantics)를 추가했습니다. Phase 13 · 09부터 16까지가 이러한 확장을 다루며, 이 lesson은 그 기반(base)에서 멈춥니다.
개념
서버 측 세 가지 기본 요소
- 도구(Tools). 호출 가능한 행동입니다. Phase 13 · 01에서 본 것과 같은 네 단계 루프(loop)를 그대로 사용합니다.
- 자원(Resources). 외부에 노출하는 데이터입니다. URI로 주소를 매길 수 있는 읽기 전용 콘텐츠이며,
file:///path, db://query/... 같은 표준 스킴이나 직접 정의한 커스텀 스킴(custom scheme)을 사용할 수 있습니다.
- 프롬프트(Prompts). 재사용 가능한 템플릿입니다. 호스트(host) UI에서는 슬래시 명령(slash command)으로 노출되며, 서버가 템플릿을 제공하고 클라이언트가 인자(argument)를 채워 넣습니다.
클라이언트 측 세 가지 기본 요소
- 루트(Roots). 서버가 접근해도 되는 URI의 집합입니다. 클라이언트가 선언하고, 서버는 이를 지켜야 합니다.
- 샘플링(Sampling). 서버가 클라이언트의 모델에게 텍스트 생성(completion)을 요청합니다. 이 덕분에 서버 쪽에 별도의 API 키 없이도 서버에서 호스팅되는 에이전트 루프(server-hosted agent loop)를 구성할 수 있습니다.
- 일레시테이션(Elicitation). 서버가 실행 도중에 클라이언트의 사용자에게 구조화된 입력을 요청합니다. 폼(form)이나 URL 형태(SEP-1036)가 될 수 있습니다.
MCP의 모든 능력(capability)은 이 여섯 가지 중 정확히 하나에 속합니다. Phase 13 · 10부터 14까지가 각각을 깊이 있게 다룹니다.
와이어 포맷: JSON-RPC 2.0
모든 메시지는 다음 필드를 가진 JSON 객체(JSON object)입니다.
- 요청(Requests):
{jsonrpc: "2.0", id, method, params}
- 응답(Responses):
{jsonrpc: "2.0", id, result | error}
- 알림(Notifications):
{jsonrpc: "2.0", method, params}. id가 없고 응답을 기대하지도 않습니다.
기반 사양(base spec)에는 기본 요소(primitive)별로 묶인 약 15개의 메서드가 정의되어 있습니다. 그중 중요한 것은 다음과 같습니다.
initialize / initialized (핸드셰이크(handshake))
tools/list, tools/call
resources/list, resources/read, resources/subscribe
prompts/list, prompts/get
sampling/createMessage (서버에서 클라이언트로 보내는 메시지)
notifications/tools/list_changed, notifications/resources/updated, notifications/progress
세 단계 생애주기
1단계: initialize.
클라이언트는 자신의 capabilities와 clientInfo를 담은 initialize 요청을 보냅니다. 서버는 자신의 capabilities, serverInfo, 그리고 자신이 따르는 사양 버전(spec version)을 응답으로 돌려줍니다. 클라이언트는 응답을 충분히 해석한 뒤 notifications/initialized 알림을 보냅니다. 이때부터는 협상된 능력(capability)의 범위 안에서 양쪽 모두 요청을 보낼 수 있습니다.
2단계: operation.
양방향으로 동작합니다. 클라이언트는 도구 발견을 위해 tools/list를 호출하고, 도구 호출을 위해 tools/call을 호출합니다. 서버는 자신이 선언한 능력에 따라 sampling/createMessage를 보낼 수 있고, 도구 목록이 바뀌면 notifications/tools/list_changed를 보낼 수 있습니다. 클라이언트는 사용자가 루트 범위(root scope)를 바꾸면 notifications/roots/list_changed를 보낼 수 있습니다.
3단계: shutdown.
어느 쪽이든 전송 계층(transport)을 닫습니다. MCP에는 별도로 정의된 구조화된 종료 메서드가 없습니다. 종료 신호는 전송 계층, 즉 stdio 또는 Streamable HTTP(Phase 13 · 09)가 직접 전달합니다.
능력 협상(Capability Negotiation)
initialize 핸드셰이크 안의 capabilities 객체가 양쪽 사이의 계약 역할을 합니다. 서버 예시는 다음과 같습니다.
{
"tools": {"listChanged": true},
"resources": {"subscribe": true, "listChanged": true},
"prompts": {"listChanged": true}
}
서버는 tools/list_changed 알림을 보낼 수 있고 resources/subscribe도 지원한다고 선언합니다. 클라이언트는 자신의 능력 선언으로 화답합니다.
{
"roots": {"listChanged": true},
"sampling": {},
"elicitation": {}
}
만약 클라이언트가 sampling을 선언하지 않았다면, 서버는 sampling/createMessage를 호출해서는 안 됩니다. 대칭적으로, 서버가 resources.subscribe를 선언하지 않았다면, 클라이언트는 구독을 시도해서는 안 됩니다.
바로 이 메커니즘이 생태계 분화(ecosystem drift)를 막아줍니다. 샘플링을 지원하지 않는 클라이언트도 여전히 유효한 MCP 클라이언트이고, sampling을 호출하지 않는 서버도 여전히 유효한 MCP 서버입니다. 단지 그 둘이 그 기능을 함께 쓰지 않을 뿐입니다.
구조화된 콘텐츠와 오류 형태
tools/call은 타입이 지정된 블록(typed block)의 content 배열을 반환합니다. 타입(type)은 text, image, resource입니다. Phase 13 · 14에서는 여기에 MCP 앱(MCP Apps), 즉 ui://로 식별되는 상호작용형 UI(interactive UI)가 추가됩니다.
오류(error)는 JSON-RPC 오류 코드를 사용합니다. 사양에서 추가로 정의된 코드로는 -32002 "Resource not found"와 -32603 "Internal error"가 있고, MCP에 특화된 오류 데이터는 error.data 필드에 담깁니다.
클라이언트 능력과 도구 호출 결정의 차이
자주 헷갈리는 부분이 있습니다. capabilities.tools는 클라이언트가 "도구 목록 변경 알림(tool-list-changed notification)을 지원하는가"를 나타냅니다. 클라이언트가 특정 도구를 실제로 호출할지 말지는 클라이언트가 사용하는 모델이 실행 시점에 내리는 결정이지, 능력 플래그(capability flag)가 아닙니다. 능력 플래그는 사양 수준의 계약(spec-level contract)이고, 모델의 도구 선택은 그것과 직교(orthogonal)하는 별개의 문제입니다.
왜 REST가 아니라 JSON-RPC인가?
JSON-RPC 2.0(2010)은 가벼운 양방향 프로토콜입니다. 반면 REST는 클라이언트 쪽에서 시작하는 방식(client-initiated)입니다. MCP는 서버에서 시작하는 메시지, 즉 샘플링이나 알림 같은 흐름이 필요했고, 그 때문에 요청과 응답의 형태가 대칭(symmetric)인 JSON-RPC가 자연스럽게 잘 맞았습니다. JSON-RPC는 stdio 위에서나 WebSocket/Streamable HTTP 위에서도 HTTP 요청 형태를 다시 만들어 내지 않고 깔끔하게 얹을 수 있다는 장점도 있습니다.
사용해보기
code/main.py는 최소한의 JSON-RPC 2.0 파서와 메시지 생성기를 먼저 제공한 뒤, initialize -> tools/list -> tools/call -> shutdown 흐름을 한 줄 한 줄 손으로 따라갑니다. 실제 전송 계층은 없고, 메시지 형태만 보여줍니다. 아래 "더 읽을거리"에 있는 사양 문서와 비교하면서 각 봉투의 형태를 직접 확인해 봅니다.
확인해 볼 지점은 다음과 같습니다.
initialize는 양쪽의 능력을 함께 선언합니다. 응답에는 serverInfo와 protocolVersion: "2025-11-25"가 들어 있습니다.
tools/list는 tools 배열을 반환합니다. 각 항목에는 name, description, inputSchema가 있습니다.
tools/call은 params.name과 params.arguments를 사용합니다.
- 응답의
content는 {type, text} 형태 블록의 배열입니다.
산출물 만들기
이 lesson에서는 outputs/skill-mcp-handshake-tracer.md를 만듭니다. MCP 클라이언트-서버 상호작용을 패킷 캡처처럼 기록한 트랜스크립트(pcap-style transcript)가 주어지면, 이 skill은 각 메시지에 어떤 기본 요소(primitive)에 속하는지, 어떤 생애주기 단계에 해당하는지, 어떤 능력에 의존하는지를 주석으로 달아 줍니다.
연습문제
-
(쉬움) code/main.py를 실행합니다. 능력 협상이 일어나는 줄을 찾고, 만약 서버가 tools.listChanged를 선언하지 않았다면 어떤 점이 달라질지 설명합니다.
-
(중간) 파서를 확장해서 notifications/progress를 처리하도록 만듭니다. 메시지 형태는 {method: "notifications/progress", params: {progressToken, progress, total}}입니다. 오래 걸리는 tools/call이 진행되는 동안 이 알림을 발생시키고, 클라이언트 핸들러(handler)가 진행 바(progress bar)를 표시할 수 있을지 확인합니다.
-
(어려움) MCP 2025-11-25 사양을 처음부터 끝까지 읽습니다. 전체 문서는 약 80쪽 분량입니다. 대부분의 서버에는 굳이 필요하지 않은 능력 플래그를 하나 찾아냅니다. 힌트: 자원 구독(resource subscription)과 관련이 있습니다.
-
(중간) 가상의 "크론 잡(cron job)" 기능이 어떤 기본 요소에 속할지 종이에 스케치해 봅니다. 힌트: 서버는 클라이언트가 예약된 시점(scheduled time)에 자신을 호출해 주기를 원합니다. 현재의 여섯 기본 요소로는 잘 맞지 않습니다. MCP의 2026 로드맵에는 이 주제에 대한 초안 SEP가 들어 있습니다.
-
(어려움) GitHub에 공개되어 있는 MCP 서버 세션 로그 하나를 골라 직접 파싱합니다. 요청, 응답, 알림 메시지의 개수를 세어 보고, 전체 트래픽에서 생애주기와 운영(operation) 단계의 비중이 어떻게 나뉘는지 계산합니다.
핵심 용어
| 용어 | 흔한 설명 | 실제 의미 |
|---|
| MCP(Model Context Protocol) | "모델 컨텍스트 프로토콜" | 모델과 도구 사이의 발견과 호출을 표준화하는 공개 프로토콜 |
| 서버 기본 요소(Server primitive) | "서버가 노출하는 것" | 도구(tools, 행동), 자원(resources, 데이터), 프롬프트(prompts, 템플릿) |
| 클라이언트 기본 요소(Client primitive) | "클라이언트가 서버에게 허용하는 것" | 루트(roots, 범위), 샘플링(sampling, LLM 콜백), 일레시테이션(elicitation, 사용자 입력) |
| JSON-RPC 2.0 | "와이어 포맷" | 대칭적인 요청/응답/알림 봉투 구조 |
initialize 핸드셰이크 | "능력 협상" | 첫 메시지 쌍. 서버와 클라이언트가 지원 기능을 선언한다 |
tools/list | "발견(Discovery)" | 클라이언트가 서버의 현재 도구 목록을 요청한다 |
tools/call | "호출(Invocation)" | 클라이언트가 서버에게 인자와 함께 도구 실행을 요청한다 |
notifications/*_changed | "변경 이벤트(Mutation event)" | 기본 요소 목록이 바뀌었음을 서버가 클라이언트에게 알리는 메시지 |
| 콘텐츠 블록(Content block) | "타입이 지정된 결과" | 도구 결과 안의 `{type: "text" |
| SEP(Spec Evolution Proposal) | "사양 진화 제안" | SEP-1686 async Tasks처럼 이름이 붙은 초안 제안 |
더 읽을거리