
MCP 완전 정리, LLM에 도구를 꽂는 개방 표준
요약
- MCP는 LLM 앱과 외부 도구, 데이터를 잇는 개방 표준 프로토콜입니다. JSON-RPC 2.0 메시지로 통신하고, Anthropic이 2024년 11월에 발표하며 오픈소스로 공개했습니다.
- 존재 이유가 한 줄로 정리됩니다. 앱 N개와 도구 M개를 매번 따로 붙이면 통합이 N 곱하기 M으로 폭증합니다. 공통 프로토콜이면 N 더하기 M으로 줄어듭니다.
- 구조는 호스트, 클라이언트, 서버 세 역할입니다. 호스트가 서버마다 클라이언트를 하나씩 띄워 1대1 전용 연결을 맺습니다.
- 서버가 노출하는 건 셋입니다. Tools(행동), Resources(읽기 컨텍스트), Prompts(템플릿). 서버가 스스로 자기 도구를 설명하기 때문에 호스트가 서버 코드를 미리 알 필요가 없습니다.
- 가장 큰 변화가 최근에 있었습니다. 현행
2026-07-28스펙은 무세션(stateless) 코어로 갔습니다.initialize핸드셰이크와Mcp-Session-Id가 사라지고, 매 요청이_meta에 프로토콜 버전과 capability를 스스로 싣습니다. - 자주 헷갈리는 지점 하나. MCP는 function calling을 대체하지 않습니다. function calling은 "모델이 무엇을 호출할지", MCP는 "그 도구를 어떻게 발견하고 연결할지"를 정합니다. 층이 다르고 서로 보완합니다.
1장. 왜 나왔나
1.1 커넥터를 매번 새로 짜던 문제
AI 앱이 데이터나 도구에 붙으려면
연결마다 전용 커넥터를 새로 짜야 했다
Claude Desktop 에 GitHub 를 붙이는 코드
Cursor 에 GitHub 를 붙이는 코드
Claude Desktop 에 Postgres 를 붙이는 코드
Cursor 에 Postgres 를 붙이는 코드
전부 따로 짜야 했다1.2 N 곱하기 M
앱이 N 개
도구가 M 개
조합이 N 곱하기 M 으로 터진다
앱 10 개에 도구 100 개면 커넥터 1000 개다Anthropic은 발표문에서 이걸 "지속 불가능한 N×M 통합 문제"라고 불렀고, MCP를 조각난 통합을 하나의 프로토콜로 대체하는 범용 표준으로 소개했습니다.
공통 프로토콜이 생기면
서버를 한 번 만들면 모든 호스트에서 재사용된다 +M
호스트가 프로토콜만 구현하면 모든 서버가 붙는다 +N
N 곱하기 M 이 N 더하기 M 이 된다1.3 USB-C 비유
공식 문서의 비유가 가장 직관적입니다.
"MCP를 AI 애플리케이션을 위한 USB-C 포트라고 생각하라. USB-C가 전자기기를 연결하는 표준을 제공하듯, MCP는 AI 앱을 외부 시스템에 연결하는 표준을 제공한다."
USB-C 이전
기기마다 전용 케이블
USB-C 이후
케이블 하나로 다 꽂힌다
MCP 가 도구 연결에 하려는 일이 정확히 이것이다1.4 LSP라는 선례
이 발상은 하늘에서 떨어진 게 아닙니다.
LSP (Language Server Protocol)
"에디터 N 개 곱하기 언어 M 개" 통합 문제를
표준 프로토콜 하나로 푼 선례
VSCode 도 Neovim 도 같은 language server 를 쓴다
MCP 는 이 아이디어를 "AI 앱 곱하기 도구" 로 옮겨온 것이다MCP 스펙 첫 문단이 직접 LSP에서 영감을 받았다고 명시합니다.
1.5 function calling과는 층이 다르다
가장 자주 헷갈리는 지점입니다.
function calling
모델이 "무엇을" 호출할지 정하는 형식
모델의 출력 형식에 관한 얘기다
MCP
그 도구를 "어떻게" 발견하고 연결할지 정하는 표준
앱과 도구 사이 배선에 관한 얘기다
층이 다르므로 대체 관계가 아니다
MCP 서버가 노출한 도구를, 모델이 function calling 으로 부른다둘은 짝입니다. MCP가 도구 목록을 가져다 주면, 모델이 그중 하나를 function calling으로 고릅니다.
2장. 세 역할과 두 계층
2.1 호스트, 클라이언트, 서버
호스트 (Host)
사용자가 실제로 쓰는 LLM 앱
여러 클라이언트를 관리한다
예: Claude Desktop, VS Code, Cursor, ChatGPT
클라이언트 (Client)
호스트가 서버마다 하나씩 만드는 커넥터
서버 1 개와 1 대 1 전용 연결을 맡는다
서버 (Server)
도구와 컨텍스트를 노출하는 프로그램
예: 파일시스템, 데이터베이스, GitHub, Sentry2.2 "서버"는 위치가 아니라 역할
여기서 한 번 걸립니다.
MCP 에서 "서버"는 컨텍스트를 제공하는 프로그램을 가리킬 뿐
어디서 실행되는지와는 무관하다
Claude Desktop 이 파일시스템 서버를 띄우면
그건 같은 기계에서 도는 로컬 서버다
Sentry 가 자기 플랫폼에서 돌리는 건 원격 서버다
둘 다 똑같이 "MCP 서버"다2.3 데이터 계층과 전송 계층
MCP는 개념적으로 두 겹입니다.
데이터 계층 (안쪽)
JSON-RPC 2.0 로 메시지 구조와 의미를 정의한다
프리미티브, 버전, capability
전송 계층 (바깥쪽)
그 메시지를 실제로 나르는 채널과 인증을 담당한다
로컬 파이프냐 HTTP 냐
덕분에 같은 JSON-RPC 메시지가
전송 방식과 무관하게 그대로 통한다2.4 전체 그림
MCP 호스트 (LLM 앱)
│
├─ 클라이언트 1
│ 연결: stdio (로컬 하위 프로세스)
│ 상대: 서버 A, 파일시스템
│
├─ 클라이언트 2
│ 연결: stdio (로컬 하위 프로세스)
│ 상대: 서버 B, 데이터베이스
│
└─ 클라이언트 3
연결: Streamable HTTP (원격)
상대: 서버 C, GitHub 나 Sentry3장. 서버가 내놓는 것, 3대 프리미티브
3.1 프리미티브라는 개념
프리미티브 (primitive)
서버가 호스트에 무엇을 제공할 수 있는지를 정의하는 기본 단위
서버측 프리미티브는 셋이다3.2 Tools, 행동
AI 가 호출해 실제 행동을 하는 실행 함수
파일 조작, API 호출, DB 쿼리
부작용이 있을 수 있다
모델이 주도해서 부른다3.3 Resources, 읽기 컨텍스트
AI 에 문맥을 주는 읽기 전용 데이터
파일 내용, DB 레코드, API 응답
앱이나 사용자가 골라 주입한다3.4 Prompts, 템플릿
상호작용을 구조화하는 재사용 템플릿
시스템 프롬프트, few-shot 예시
슬래시 커맨드처럼 사용자가 주도해서 쓴다3.5 누가 주도하는가
셋을 가르는 축이 여기입니다.
| 프리미티브 | 무엇 | 누가 주도 | 부작용 |
|---|---|---|---|
| Tools | 실행 함수 | 모델 | 있을 수 있음 |
| Resources | 읽기 데이터 | 앱이나 사용자 | 없음 |
| Prompts | 템플릿 | 사용자 | 없음 |
각 프리미티브는 발견(*/list), 조회(*/get), 실행(tools/call) 메서드를 갖습니다. 목록이 동적이라 서버가 도구를 추가하거나 바꾸면 알림으로 클라이언트에 전파할 수 있습니다.
3.6 한 서버가 셋을 동시에
공식 문서의 예시가 깔끔합니다.
데이터베이스 컨텍스트를 주는 서버 하나가
Tool DB 를 조회하는 실행 함수
Resource 스키마를 담은 읽기 데이터
Prompt 그 도구 사용법 few-shot
셋을 한꺼번에 노출할 수 있다3.7 클라이언트도 프리미티브를 준다
Elicitation
서버가 세션 도중 사용자에게
추가 입력이나 확인을 되묻는 기능
Roots
클라이언트가 서버에 "이 디렉터리들이 작업 범위다" 라고 알려주는 것
Sampling
서버가 호스트의 LLM 에 완성을 역요청하는 것현행 스펙은 이 셋을 서버가 별도 요청을 보내는 방식이 아니라, 결과 안에 "입력이 더 필요하다"를 담아 돌려주는 방식으로 통일했습니다. 자세한 건 5.5에서 봅니다.
4장. 메시지를 직접 따라가기
4.1 흐름
① 클라이언트 ──► 서버 tools/list
"무슨 도구가 있나"
│
▼
② 서버 ──► 클라이언트 도구 목록과 inputSchema
"weather_current 가 있고 인자는 이렇다"
│
▼
③ 호스트가 그 목록을 모델의 도구 목록으로 등록
│
▼
④ 모델이 그중 하나를 고른다
여기가 function calling
│
▼
⑤ 클라이언트 ──► 서버 tools/call
name 과 arguments 를 실어 보낸다
│
▼
⑥ 서버 ──► 클라이언트 content 배열로 결과4.2 실제 메시지
// ① 클라이언트에서 서버로: 무슨 도구가 있나
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
// ② 서버에서 클라이언트로: weather_current 가 있다 (입력 스키마 포함)
{ "jsonrpc": "2.0", "id": 2, "result": { "tools": [{
"name": "weather_current",
"description": "전 세계 현재 날씨 조회",
"inputSchema": {
"type": "object",
"properties": { "location": { "type": "string" } },
"required": ["location"]
}
}]}}
// ③ 클라이언트에서 서버로: 그 도구를 이 인자로 실행해줘
{ "jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": { "name": "weather_current",
"arguments": { "location": "Seoul" } } }
// ④ 서버에서 클라이언트로: 결과 (여러 형식을 담는 content 배열)
{ "jsonrpc": "2.0", "id": 3, "result": { "content": [
{ "type": "text", "text": "서울: 20도, 구름 조금, 습도 65%" }
]}}
가독성을 위해 _meta를 뺀 형태입니다. 현행 스펙은 매 요청 _meta에 프로토콜 버전과 capability를 함께 싣습니다. 6장에서 봅니다.
4.3 inputSchema, 자기서술의 핵심
②의 inputSchema가 MCP 전체를 굴러가게 만드는 부품입니다.
서버가 도구의 이름, 설명, 인자 스키마를 스스로 광고한다
호스트는 그걸 그대로 모델의 도구 목록에 등록하고
모델이 tools/call 로 부른다
호스트가 서버 코드를 미리 알 필요가 없다
이 자기서술(self-describing) 덕분에
"아무 서버나 꽂으면 도구가 붙는" 일이 가능해진다스키마 방언에도 규칙이 있습니다.
방언을 명시하지 않으면 JSON Schema 2020-12 으로 본다
명시하려면 스키마에 $schema 필드를 넣는다
구현체는 최소 2020-12 를 지원해야 하고
지원하지 않는 방언은 적절한 에러로 돌려줘야 한다4.4 결과와 resultType
현행 스펙은 결과에 resultType 필드를 요구합니다.
resultType: "complete"
요청이 끝났고 최종 내용이 담겨 있다
resultType: "input_required"
요청이 아직 안 끝났고 추가 입력이 필요하다
InputRequiredResult 가 담겨 있다
확장이 추가 값을 정의할 수 있다
클라이언트가 모르는 값은 무효로 처리해야 한다하위호환 규칙이 하나 붙어 있습니다. 이전 버전 서버는 resultType을 보내지 않으므로, 필드가 없으면 "complete"로 취급해야 합니다.
4.5 에러 코드 체계
JSON-RPC 2.0이 구현 정의 서버 에러용으로 -32000에서 -32099를 예약해 뒀는데, MCP는 이 구간을 다시 쪼갰습니다.
-32000 에서 -32019 레거시
정책이 생기기 전에 구현체들이 임의로 쓰던 구간
새 코드를 여기 할당하면 안 된다
받는 쪽은 특정 의미를 가정하면 안 된다
-32020 에서 -32099 스펙 전용
MCP 스펙만 정의한다
스펙에 없는 코드를 이 구간에서 내보내면 안 된다| 코드 | 이름 | 언제 |
|---|---|---|
-32020 |
HeaderMismatch |
HTTP 헤더와 바디 값이 안 맞거나 필수 헤더가 없음 |
-32021 |
MissingRequiredClientCapability |
처리에 필요한 capability를 클라이언트가 선언하지 않음 |
-32022 |
UnsupportedProtocolVersion |
서버가 요청된 프로토콜 버전을 지원하지 않음 |
5장. 전송, stdio와 Streamable HTTP
5.1 stdio, 로컬용
클라이언트가 서버를 하위 프로세스로 띄우고
표준입출력 스트림으로 JSON-RPC 를 교환한다
네트워크 오버헤드가 없어 간단하고 빠르다
인증은 환경변수 같은 환경에서 가져온다5.2 Streamable HTTP, 원격용
서버가 POST 를 받는 HTTP 엔드포인트 하나를 연다 예: https://example.com/mcp
클라이언트는 모든 JSON-RPC 메시지를
각각 별도의 HTTP POST 로 보낸다
서버는 요청마다 둘 중 하나로 답한다
application/json 단일 JSON 객체
text/event-stream 그 요청에만 딸린 SSE 스트림
클라이언트는 둘 다 받을 수 있어야 한다5.3 요청 하나에 스트림 하나
현행 스펙에서 이 대응이 엄격해졌습니다.
POST tools/call
│
├─ 단순 응답
│ 200 OK, application/json 으로 JSON-RPC 응답
│
└─ 스트리밍 응답
이 요청 하나에만 묶인 SSE 스트림을 연다
notifications/progress 를 몇 번 흘리고
마지막에 JSON-RPC 응답을 보내며 스트림을 닫는다부수 효과가 하나 생깁니다.
SSE 응답 스트림을 닫는 것 자체가 그 요청의 취소 신호다
요청마다 스트림이 따로 있으므로
전송 계층의 끊김이 어느 요청을 가리키는지 모호하지 않다5.4 알림 스트림은 따로 연다
서버가 먼저 보내는 변경 알림
notifications/tools/list_changed
notifications/resources/updated
이건 subscriptions/listen 요청을 보내면
그 응답 자체가 열린 채로 유지되는 SSE 스트림이 된다
요청에 딸린 알림(progress, message)은
이 listen 스트림으로 오지 않는다
자기 요청의 응답 스트림으로만 흐른다두 종류 알림이 흐르는 길이 완전히 갈립니다. 요청에 딸린 것은 그 요청의 스트림으로, 서버가 먼저 보내는 것은 listen 스트림으로 갑니다.
5.5 서버가 되물을 때
여기가 현행 스펙에서 크게 바뀐 부분입니다.
예전
서버가 SSE 스트림에 자기 JSON-RPC 요청을 실어 보냈다
"sampling 해줘" "사용자에게 물어봐줘"
지금
서버는 독립 요청을 보내지 않는다
결과에 InputRequiredResult 를 담아 돌려준다클라이언트 ──► 서버 tools/call (id: 1)
│
▼
서버가 사용자 입력이나 LLM 완성이 필요하다고 판단
│
▼
서버 ──► 클라이언트 InputRequiredResult
inputRequests: elicitation/create
│
▼
클라이언트가 요청받은 입력을 모은다
│
▼
클라이언트 ──► 서버 tools/call (id: 2)
원래 params 에 inputResponses 를 덧붙여 재전송
│
▼
서버 ──► 클라이언트 최종 결과이 패턴을 MRTR(Multi Round-Trip Requests)라고 부릅니다. 서버가 스트림 위에서 대화를 이어가는 대신, 클라이언트가 더 채운 요청을 다시 보내는 형태로 바꾼 것입니다. 6장의 무세션 전환과 같은 방향입니다.
5.6 붙이는 건 설정 한 줄
실제로 서버를 붙이는 일은 대개 설정 한 줄입니다.
// 호스트 설정에 로컬 파일시스템 서버 등록 (개념 예시)
{ "mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]
}
}}
공식 레퍼런스 서버 Filesystem, Git, Fetch, Memory 등
TypeScript 서버는 npx
Python 서버는 uvx
호스트 설정에 실행 명령을 등록하면 도구가 바로 붙는다6장. 무세션 코어
6.1 원래는 세션 프로토콜이었다
연결할 때 initialize 핸드셰이크로
프로토콜 버전과 capability 를 협상하고
Mcp-Session-Id 헤더로 세션을 유지했다
HTTP GET 으로 별도 SSE 스트림을 열어
서버가 먼저 보내는 메시지를 받았고
Last-Event-ID 로 스트림을 재개할 수 있었다6.2 무엇이 문제였나
표현력은 좋았지만 인프라와 궁합이 나빴습니다.
같은 세션의 요청이 같은 서버 인스턴스로 가야 했다
로드밸런서에 sticky 라우팅을 강제한다
서버리스와 안 맞는다
인스턴스가 죽으면 세션이 통째로 날아간다6.3 무세션이라는 결정
2026-07-28 스펙은 이걸 정면으로 뒤집었습니다. 스펙 문장이 명확합니다.
"MCP는 무상태 프로토콜이다. 요청을 처리하는 데 필요한 모든 정보가 요청 자체에 담긴다. 서버는 각 요청을 독립적으로 처리하며, 같은 연결이나 스트림 위의 이전 요청에서도 어떤 상태도 추론해서는 안 된다."
서버는 같은 연결의 이전 요청에 기대어
capability, 프로토콜 버전, 클라이언트 신원을 알아내면 안 된다
매 요청이 _meta 로 다 실어 보낸다
클라이언트도 관련 작업에 같은 연결을 재사용하도록 요구받지 않는다
여러 요청에 걸쳐야 하는 상태는
클라이언트가 매 요청에 실어 보내는 명시적 식별자로 참조해야 한다얻는 것이 이것입니다.
어떤 요청이 어떤 서버 인스턴스에 떨어져도 된다
평범한 라운드로빈 로드밸런서 뒤에서 운영할 수 있다
sticky 고정이 프로토콜 층에선 불필요해졌다스펙이 덧붙인 주의 하나가 인상적입니다. 열린 연결은 대화나 세션이 아닙니다. stdio 프로세스 하나에 서로 무관한 요청들이 섞여 들어올 수 있으므로, 서버는 프로세스 정체성을 대화 연속성의 대용으로 삼으면 안 됩니다.
6.4 _meta에 무엇이 실리나
| 키 | 필수 | 무엇 |
|---|---|---|
io.modelcontextprotocol/protocolVersion |
예 | 이 요청의 프로토콜 버전 (예: "2026-07-28") |
io.modelcontextprotocol/clientCapabilities |
예 | 이 요청에 관련된 클라이언트 capability |
io.modelcontextprotocol/clientInfo |
아니오 | 클라이언트 이름과 버전 |
io.modelcontextprotocol/logLevel |
아니오 | 이 요청에 대해 서버가 낼 최소 로그 레벨 |
필수 필드가 빠진 요청은 malformed 다
서버는 -32602 (Invalid params) 로 거부해야 하고
HTTP 라면 400 Bad Request 여야 한다
응답 쪽에는
io.modelcontextprotocol/serverInfo 를 실어
서버가 자기를 밝히도록 권고한다주의점 하나를 스펙이 명시합니다. clientInfo와 serverInfo는 보내는 쪽이 스스로 신고하는 값이고 프로토콜이 검증하지 않습니다. 표시, 로깅, 디버깅용이며 보안 판단에 쓰면 안 됩니다.
_meta 키 이름에는 규칙이 있습니다.
prefix 는 역 DNS 표기를 권고한다 com.example/
두 번째 라벨이 modelcontextprotocol 이나 mcp 인 prefix 는
MCP 전용으로 예약돼 있다
io.modelcontextprotocol/ 예약
dev.mcp/ 예약
com.example.mcp/ 예약 아님 (두 번째 라벨이 example)예외가 셋 있습니다. traceparent, tracestate, baggage는 prefix 없이 쓰이며 OpenTelemetry 트레이스 컨텍스트 전파용으로 예약돼 있습니다. 에이전트 관측 도구와의 호환을 위해 남긴 자리입니다.
6.5 버전 협상이 요청 단위로
협상 핸드셰이크가 없다
매 요청이 자기 프로토콜 버전을 들고 온다
서버는 요청마다 독립적으로 받거나 거절한다// 서버가 그 버전을 지원하지 않을 때
{ "jsonrpc": "2.0", "id": 1,
"error": {
"code": -32022,
"message": "Unsupported protocol version",
"data": {
"supported": ["2026-07-28", "2025-11-25"],
"requested": "1900-01-01"
}
}}
클라이언트는 supported 목록에서 공통 버전을 골라 재시도합니다.
6.6 server/discover
서버는 server/discover 를 반드시 구현해야 한다
클라이언트는
다른 요청을 보내기 전에 이걸 호출해
서버가 지원하는 버전을 미리 알아볼 수 있다
하지만 의무는 아니다
그냥 아무 RPC 나 바로 쏘고
UnsupportedProtocolVersionError 를 받아 처리해도 된다핸드셰이크가 아니라 선택적 조회라는 게 차이입니다.
6.7 legacy와 modern
스펙이 두 시대를 부르는 이름을 정해 뒀습니다.
modern 버전, 신원, capability 를 요청별 메타데이터로 나른다
2026-07-28 이후
legacy initialize 핸드셰이크로 세션을 맺는다
2025-11-25 이전
dual-era 둘 다 지원하는 구현| 클라이언트 | 서버 | 결과 |
|---|---|---|
| modern | modern | 동작. 버전 불일치는 에러로 드러나고 재시도 |
| modern | legacy | 실패. stdio에서는 server/discover를 먼저 보내 확실히 실패시키길 권고 |
| dual-era | modern | 동작. modern으로 유지 |
| dual-era | legacy | 동작. initialize로 폴백 |
| legacy | modern | 실패. legacy 클라이언트에는 앞으로 나아갈 수단이 없음 |
| legacy | dual-era | 동작. 서버가 legacy 방식으로 응대 |
시대 판정은 요청이 아니라 서버의 성질이다
클라이언트는 판정 결과를
서버 프로세스(stdio)나 origin(HTTP) 수명 동안 캐시해야 한다
같은 설정의 재시작 사이에 유지해도 된다6.8 프로토콜 무세션이 앱 무상태는 아니다
여기를 혼동하면 안 됩니다.
프로토콜이 무세션이라는 것은
전송 층이 상태를 안 들고 있다는 뜻이지
앱이 상태를 못 갖는다는 뜻이 아니다
앱이 상태를 원하면
도구가 명시적 handle 을 발급하고
클라이언트가 매 요청에 그 handle 을 실어 보내면 된다
상태의 위치가 "연결"에서 "요청 인자"로 옮겨간 것이다7장. 헤더 미러링
7.1 왜 바디를 헤더에 복사하나
Streamable HTTP 는 JSON-RPC 바디의 일부 필드를
HTTP 헤더로 그대로 복사한다
중간 장비(로드밸런서, 게이트웨이, 관측 도구)가
바디를 파싱하지 않고도
라우팅하고 들여다볼 수 있게 하려는 것이다7.2 표준 헤더
| 헤더 | 원본 필드 | 언제 필수 |
|---|---|---|
MCP-Protocol-Version |
_meta의 protocolVersion |
모든 POST |
Mcp-Method |
method |
모든 요청 |
Mcp-Name |
params.name 또는 params.uri |
tools/call, resources/read, prompts/get |
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "Seattle, WA" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
7.3 x-mcp-header, 인자까지 헤더로
서버는 특정 도구 인자를 헤더로 올리도록 inputSchema에 표시할 수 있습니다.
{
"name": "execute_sql",
"description": "Execute SQL on Google Cloud Spanner",
"inputSchema": {
"type": "object",
"properties": {
"region": {
"type": "string",
"description": "The region to execute the query in",
"x-mcp-header": "Region"
},
"query": { "type": "string", "description": "The SQL query to execute" }
},
"required": ["region", "query"]
}
}
그러면 요청에 Mcp-Param-Region: us-west1 헤더가 붙습니다.
용도가 분명하다
테넌트나 리전 같은 라우팅 키를
게이트웨이가 바디 파싱 없이 읽어
그 리전 백엔드로 보낼 수 있다제약이 빡빡하게 걸려 있습니다.
원시 타입(정수, 문자열, 불리언)에만 붙일 수 있다
number 는 안 된다
스키마 루트에서 properties 체인만 따라 도달 가능해야 한다
items, oneOf, anyOf, allOf, if/then/else, $ref 를 지나면 무효다
클라이언트는 이 제약을 어긴 도구 정의를 거부해야 한다
거부는 그 도구를 tools/list 결과에서 빼는 것을 뜻한다
나머지 도구는 계속 쓸 수 있어야 한다ASCII로 안전하게 담을 수 없는 값은 Base64 센티널 형식으로 인코딩합니다.
Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?=7.4 헤더와 바디가 다르면 거부
이 규칙에 보안 논리가 깔려 있습니다.
로드밸런서는 헤더 값으로 라우팅하고
MCP 서버는 바디 값으로 실행한다
둘이 다르면
"A 리전으로 보낸다고 해놓고 B 리전 쿼리를 실행"이 가능해진다그래서 바디를 처리하는 서버는
헤더 값과 바디 값이 일치하는지 검증해야 하고
불일치면 400 Bad Request 와
-32020 HeaderMismatch 로 거부해야 한다중간 장비에 대한 권고도 붙어 있습니다. 미러링된 헤더로 정책을 강제하는 게이트웨이는 MCP-Protocol-Version이 헤더와 바디 검증을 요구하는 버전인지 먼저 확인해야 하고, 아니면 요청을 거부해야 합니다. 검증되지 않은 헤더를 믿으면 안 되기 때문입니다.
8장. 확장
8.1 확장 협상 방식
확장(extension)은 코어 밖의 선택 기능이다
항상 opt-in 이고
클라이언트와 서버 양쪽의 명시적 지원이 필요하다
capabilities 의 extensions 필드에
확장 식별자를 키로 하는 맵으로 광고한다{ "capabilities": {
"tools": {},
"extensions": {
"io.modelcontextprotocol/tasks": {}
}
}}
한쪽만 지원하면
지원하는 쪽이 코어 동작으로 되돌아가거나
적절한 에러로 거부해야 한다8.2 Tasks
장기 실행 작업을 위한 비동기 확장
요청을 시작하고 나중에 결과를 회수한다
폴링, 중간 입력, 지속되는 handle 을 지원한다
무세션 코어와 짝이 맞는다
연결에 기대지 않고 handle 로 상태를 참조하기 때문이다8.3 MCP Apps
대화 안에 인라인으로 렌더링되는 상호작용 UI 요소
차트, 폼, 비디오 플레이어
식별자는 io.modelcontextprotocol/ui8.4 Skills over MCP
에이전트 워크플로를 위한 구조화된 지시를
MCP 를 통해 발견하고 소비하는 확장
"도구"보다 큰 단위의 절차를 서버가 제공한다9장. 보안
9.1 스펙이 못 박은 원칙
스펙에 Security and Trust & Safety 절이 따로 있습니다.
사용자 동의와 통제
사용자는 모든 데이터 접근과 작업에 명시적으로 동의해야 한다
무엇이 공유되고 무슨 행동이 일어나는지 통제권을 유지해야 한다
데이터 프라이버시
호스트는 사용자 데이터를 서버에 노출하기 전에 명시적 동의를 받아야 한다
동의 없이 리소스 데이터를 다른 곳으로 보내면 안 된다
도구 안전성
도구는 임의 코드 실행을 뜻하므로 그에 맞게 다뤄야 한다
호스트는 도구를 호출하기 전에 명시적 사용자 동의를 받아야 한다9.2 도구 설명은 신뢰하지 않는다
스펙 문장 중 가장 날카로운 대목입니다.
"annotation 같은 도구 동작 설명은, 신뢰할 수 있는 서버에서 온 것이 아닌 한 신뢰할 수 없는 것으로 간주해야 한다."
도구 설명은 모델의 컨텍스트에 그대로 들어간다
악의적 서버가 설명란에 지시문을 심으면
그게 프롬프트 인젝션이 된다 tool poisoning
"아무 서버나 꽂기"가 쉬워진 만큼
신뢰 경계 밖 서버를 그대로 붙이면 위험하다관련해서 confused deputy(호스트의 권한을 빌려 원래 못 하던 일을 시키는 것)와 토큰 패스스루도 같은 계열의 위협입니다.
9.3 원격 전송의 기본기
Origin 헤더 검증
DNS rebinding 공격 방지
Origin 이 있는데 유효하지 않으면 403 Forbidden
로컬 실행 시 바인딩
0.0.0.0 이 아니라 127.0.0.1 에만 바인딩할 것
인증
모든 연결에 제대로 된 인증을 붙일 것
HTTP 전송은 MCP 인가 프레임워크를 따를 것
stdio 는 환경에서 자격증명을 가져올 것DNS rebinding을 막지 않으면, 원격 웹사이트가 브라우저를 통해 로컬에서 도는 MCP 서버와 대화할 수 있게 됩니다.
9.4 스키마라는 틈
JSON Schema를 그대로 받아들이는 데서 오는 위험도 스펙이 짚어 뒀습니다.
$ref 의 네트워크 URI
구현체는 네트워크 URI 로 해석되는 $ref 를
자동으로 따라가면 안 된다
옵트인으로 켤 수는 있으나 기본은 꺼져 있어야 하고
호스트 허용목록, 루프백과 사설망 거부,
타임아웃과 크기 제한, 참조 URI 로깅을 걸어야 한다
조합 키워드의 자원 소모
anyOf, oneOf, allOf, if/then/else, $defs 는 표현력이 좋지만
검증 비용이 크다
최대 깊이, 서브스키마 개수 상한, 검증 시간 예산 같은
합리적 한계를 걸어야 한다
악의적 스키마가 검증기를 상대로 한 서비스 거부 벡터가 될 수 있다아이콘도 같은 결입니다. SVG에 스크립트를 심을 수 있으므로, MIME 타입은 참고용으로만 보고 매직 바이트로 실제 타입을 판별하고, 자격증명 없이 가져오고, 같은 origin인지 확인하라고 권고합니다.
9.5 표준과 락인의 역설
개방 표준이라도 거버넌스가 한 회사에 묶여 있으면
사실상의 통제 위험이 남는다
이 때문에 Linux Foundation 산하
Agentic AI Foundation 으로 기증해 중립화했다10장. 생태계
10.1 레퍼런스 서버
modelcontextprotocol/servers 저장소
Filesystem 파일 읽기 쓰기
Git 저장소 조회와 조작
Fetch 웹 콘텐츠 가져오기
Memory 지식 그래프 기반 지속 메모리10.2 프레임워크 통합
LangChain, LangGraph, LlamaIndex, OpenAI Agents SDK 등이
MCP 서버를 "도구 소스"로 흡수한다
MCP 서버 1 개 = 그 프레임워크의 도구 세트
프레임워크를 바꿔도 서버는 그대로 재사용된다Tiny Agents라는 관점이 이 구조를 잘 드러냅니다. "MCP 클라이언트만 있으면 에이전트는 그 위의 while 루프일 뿐"이라며 50줄짜리 에이전트를 구현해 보인 예제입니다.
10.3 진입점
Hugging Face MCP Server
huggingface.co/settings/mcp 에서
Hub 모델과 데이터셋 검색, 문서 시맨틱 검색, Jobs 실행 같은 도구를
MCP 클라이언트에 붙여준다
Gradio Space
자기 함수를 MCP 도구로 노출할 수 있다
커뮤니티 앱이 그대로 도구가 된다10.4 레지스트리와 그 다음
2025-11 공식 커뮤니티 레지스트리로
서버 "발견"이 표준화됐다
남은 과제는 서버 신원, 서명, 검증이다
연결 전에 capability 를 광고하는 Server Cards (.well-known 방식)
검증되고 감사된 서버 디렉터리
이것들이 로드맵에 올라 있다로드맵 방향은 공식 문서 기준이고, 세부 시점은 진행 중입니다.
10.5 연표
| 시기 | 무슨 일 |
|---|---|
| 2024-11-25 | MCP 발표와 오픈소스화. N×M 통합 문제를 JSON-RPC 2.0 개방 표준으로 해소. Python, TypeScript, C#, Java SDK와 예제 서버 동시 공개 |
| 2025-03-26 | Streamable HTTP 전송 도입, HTTP+SSE 전송 deprecated |
| 2025-03 | OpenAI 채택. Agents SDK와 ChatGPT 데스크톱에 통합 |
| 2025-04 | Google DeepMind 지원 표명, 클라이언트 확산 (VS Code, Cursor, Zed) |
| 2025-06-18 | 스펙 개정. Elicitation, OAuth 2.1 인가, 구조화 출력, MCP-Protocol-Version 헤더 도입 |
| 2025-09 | OpenAI가 ChatGPT apps에 MCP 지원 확대 |
| 2025-11 | 공식 레지스트리 출범과 스펙 개정 |
| 2025-12-09 | Anthropic이 MCP를 Agentic AI Foundation(Linux Foundation)에 기증. Anthropic, Block, OpenAI 공동창립 |
| 2026-07-28 | 무세션 스펙(현행). initialize와 Mcp-Session-Id 제거, 요청별 메타데이터로 전환, GET 스트림 엔드포인트 제거, MRTR 도입, Tasks 확장 표준화 |
11장. 정리
11.1 핵심 세 줄
① MCP 는 LLM 앱과 도구, 데이터를 잇는 개방 표준이다
호스트, 클라이언트, 서버 3 역할이 JSON-RPC 2.0 로 통신한다
② 존재 이유는 N 곱하기 M 통합 폭증을 표준으로 눌러
"한 번 구현하면 어디에나" 붙게 하는 것이다
③ 서버는 Tools, Resources, Prompts 를 자기서술 방식으로 노출하고
전송은 로컬 stdio 와 원격 Streamable HTTP 둘이다11.2 무세션 전환이 뜻하는 것
2026-07-28 스펙은 세션을 버리고 요청별 메타데이터로 갔다
프로토콜이 인프라에 맞춰 내려앉은 것이다
평범한 로드밸런서 뒤에서 굴러가고
서버리스에 올릴 수 있고
인스턴스가 죽어도 요청이 안 날아간다
대신 매 요청이 조금 더 무거워졌다
버전, capability, 신원을 매번 실어 보내야 하기 때문이다11.3 무엇을 조심할지
도구 설명과 annotation 은 신뢰할 수 없는 입력이다
신뢰 경계 밖 서버는 붙이지 않는다
원격이면 Origin 검증과 인증이 기본이다
로컬 서버는 127.0.0.1 에만 바인딩한다
clientInfo 와 serverInfo 는 자기 신고값이다
보안 판단에 쓰지 않는다
스키마의 $ref 를 자동으로 따라가지 않는다
조합 키워드에 검증 한계를 건다11.4 함께 읽을 것
에이전트 루프 (ReAct) 와 function calling 이 MCP 와 어떻게 맞물리는지
LangGraph 같은 프레임워크가 MCP 서버를 도구로 흡수하는 방식
서버와 도구가 늘어날 때의 도구 라우팅과 발견 비용용어 정리
| 용어 | 한 줄 뜻 |
|---|---|
| MCP (Model Context Protocol) | LLM 앱과 외부 도구, 데이터를 잇는 개방 표준 프로토콜 |
| N×M 통합 문제 | 앱 N개와 도구 M개를 매번 따로 붙여야 하는 조합 폭증. MCP가 N+M으로 줄임 |
| JSON-RPC 2.0 | JSON으로 요청, 응답, 알림을 주고받는 경량 RPC 규약. MCP의 메시지 형식 |
| 호스트 (Host) | 사용자가 쓰는 LLM 앱. 여러 클라이언트를 관리 |
| 클라이언트 (Client) | 호스트가 서버마다 하나씩 만드는 커넥터 |
| 서버 (Server) | 도구와 컨텍스트를 노출하는 프로그램. 로컬일 수도 원격일 수도 있음 |
| 프리미티브 (Primitive) | MCP가 정의하는 기본 단위. 서버측 Tools, Resources, Prompts |
| Tools | 모델이 호출하는, 부작용이 있을 수 있는 실행 함수 |
| Resources | 읽기 전용 컨텍스트(파일, DB 레코드, API 응답)를 주입하는 프리미티브 |
| Prompts | 재사용 프롬프트 템플릿. 사용자가 주도해 쓰는 슬래시 커맨드 형태 |
| Elicitation | 서버가 도중에 사용자에게 추가 입력이나 확인을 되묻는 클라이언트 프리미티브 |
| Roots | 클라이언트가 서버에 알려주는 작업 범위 디렉터리 목록 |
| Sampling | 서버가 호스트의 LLM에 완성을 역요청하는 것 |
inputSchema |
도구의 인자를 기술하는 JSON Schema. 자기서술의 핵심 |
resultType |
결과의 종류. complete는 완료, input_required는 추가 입력 필요 |
| MRTR (Multi Round-Trip Requests) | 서버가 추가 입력을 결과로 요구하고 클라이언트가 채워 재전송하는 패턴 |
| 전송 (Transport) | MCP 메시지 운반 방식. stdio(로컬)와 Streamable HTTP(원격) |
| Streamable HTTP | POST 하나에 JSON 또는 요청 전용 SSE 스트림으로 답하는 원격 전송 |
| SSE (Server-Sent Events) | 서버에서 클라이언트로 가는 단방향 스트림 |
subscriptions/listen |
서버발 변경 알림을 받기 위해 여는 장기 SSE 스트림 요청 |
| 무세션 코어 (Stateless core) | initialize와 Mcp-Session-Id를 없애고 매 요청이 _meta에 버전과 capability를 싣는 구조 |
_meta |
요청과 결과에 붙는 메타데이터 필드. 프로토콜 버전, capability, 신원, 트레이스 컨텍스트를 담음 |
server/discover |
서버의 지원 버전과 capability를 미리 받아오는, 선택적으로 호출하는 요청 |
| modern / legacy | 요청별 메타데이터를 쓰는 시대(2026-07-28 이후)와 initialize 핸드셰이크를 쓰는 시대(2025-11-25 이전) |
MCP-Protocol-Version |
매 POST에 필수인 프로토콜 버전 헤더. 바디의 _meta 값과 일치해야 함 |
x-mcp-header |
도구 인자를 HTTP 헤더 Mcp-Param-{Name}으로 미러링하도록 스키마에 붙이는 표시 |
HeaderMismatch (-32020) |
헤더 값과 바디 값이 다르거나 필수 헤더가 없을 때의 에러 |
| Tasks | 장기 실행 작업을 시작하고 나중에 회수하는 비동기 확장 |
| MCP Apps | 대화 안에 차트나 폼 같은 UI를 렌더링하는 확장 |
| tool poisoning | 도구 설명이나 annotation에 지시문을 심어 모델을 조종하는 공격 |
| DNS rebinding | DNS 응답을 바꿔치기해 원격 페이지가 로컬 서버와 통신하게 만드는 공격. Origin 검증으로 막음 |
| function calling | 모델이 구조화된 도구 호출을 직접 출력하는 기능. MCP와 층이 다르고 보완적 |
| LSP (Language Server Protocol) | 에디터 N개 곱하기 언어 M개 통합 문제를 푼 프로토콜. MCP의 설계 영감 |
| AAIF (Agentic AI Foundation) | MCP를 호스팅하는 Linux Foundation 산하 중립 거버넌스 |
참고자료
- Anthropic, Introducing the Model Context Protocol (2024-11-25, 발표와 N×M 통합 문제)
- MCP 공식 문서, What is MCP (개방 표준 정의와 USB-C 비유)
- MCP 공식 문서, Architecture (3역할, 데이터 계층과 전송 계층)
- MCP Specification 2026-07-28 (현행, 무세션 코어와 확장 목록)
- MCP Spec, Base Protocol (statelessness,
_meta예약 키, 에러 코드 구간) - MCP Spec, Versioning and Compatibility (요청별 버전 협상,
server/discover, 호환 매트릭스) - MCP Spec, Streamable HTTP (헤더 미러링,
x-mcp-header, MRTR, 취소) - MCP 공식 로드맵 (Server Cards와 검증 디렉터리)
- modelcontextprotocol/servers, 공식 레퍼런스 서버 (Filesystem, Git, Fetch, Memory)
- Anthropic, Donating MCP and establishing the Agentic AI Foundation (2025-12-09)
- Hugging Face Docs, Hugging Face MCP Server와 Gradio Spaces as MCP
- Hugging Face Blog, Tiny Agents: an MCP-powered agent in 50 lines of code
- [Wikipedia, Model Context Protocol (채택 연표와 LSP 영감)](https://en.wikipedia.org/wiki/Model_Context_Protocol
'AI Agent > Tool-Use (Tool Calling)' 카테고리의 다른 글
| MCP 보안 (0) | 2026.02.13 |
|---|---|
| Native Tool Calling (0) | 2025.05.28 |
댓글