06. 버전(날짜 리비전)과 버전 협상, 생명주기
- MCP의 날짜 기반 버전 체계를 이해하고, "어느 리비전 기준인지"를 왜 명시해야 하는지 안다
- 버전 협상이 어떻게 일어나고, 불일치 시 무슨 일이 생기는지 설명할 수 있다
- 연결의 전체 생명주기(초기화 → 동작 → 종료)를 그릴 수 있다
날짜로 버전을 매기는 이유
MCP의 버전은 1.2.3 같은 시맨틱 버전이 아니라 날짜 문자열입니다. 시맨틱 버전(SemVer) 은 주.부.수(major.minor.patch) 세 자리로 변화의 성격을 표현하는 흔한 버전 규칙입니다(예: 1.2.3 → 큰 호환 깨짐은 첫 자리, 기능 추가는 둘째, 버그 수정은 셋째). MCP는 이 대신 날짜를 씁니다. 예: 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25. 스펙은 날짜 문자열로 버전이 매겨지고, initialize 때 협상되며, 스키마는 TypeScript 우선(schema.ts)으로 정의된 뒤 JSON으로도 내보내집니다.
날짜 버전의 장점은 "이 리비전이 언제 확정됐는가"가 이름에 그대로 담긴다는 점입니다. 빠르게 진화하는 스펙에서 "2025-11-25 기준"이라고 말하면, 그날의 스펙 문서를 정확히 가리킬 수 있습니다.
주요 리비전의 흐름 (맥락용)
세부 기능은 스펙 문서를 봐야 하지만, 큰 흐름을 알면 "왜 옛날 코드가 안 도는지" 이해됩니다.
| 리비전 | 대표적 변화(요지) |
|---|---|
| 2024-11-05 | 최초 공개. 원격 전송은 HTTP+SSE(2개 엔드포인트) |
| 2025-03-26 | Streamable HTTP 도입(HTTP+SSE deprecated), 인증 정비 |
| 2025-06-18 | 인증 강화 — MCP 서버를 OAuth Resource Server로 분류, Resource Indicators(RFC 8707) 요구, 보안 가이드 보강 |
| 2025-11-25 | 1주년 안정판 — OpenID Connect Discovery, tool/resource/prompt 아이콘 메타데이터, 증분 스코프 동의, sampling의 도구 호출 지원 등 |
위 표의 약어를 잠깐 풀면: RFC(Request For Comments)는 인터넷 표준을 정의하는 공개 문서 번호로, "RFC 8707을 따른다"는 곧 "그 표준 문서의 규칙대로 한다"는 뜻입니다. OAuth·스코프·OpenID 같은 인증 용어는 7부에서 본격적으로 풀어 설명하니, 여기서는 "인증 관련 표준이 점점 정비됐다" 정도로만 읽어도 됩니다. deprecated는 "더 이상 권장되지 않고 향후 제거될 수 있음"을 뜻하는 소프트웨어 관용어입니다.
버전 협상
initialize에서 클라이언트는 자신이 원하는 protocolVersion을 제시하고, 서버는 자신이 지원하는 버전으로 응답합니다. 양쪽이 호환되면 그 버전으로 진행하고, 안 되면 연결을 끊습니다.
sequenceDiagram
participant C as Client
participant S as Server
C->>S: initialize (protocolVersion: "2025-11-25")
alt 서버가 그 버전 지원
S-->>C: InitializeResult (protocolVersion: "2025-11-25")
Note over C,S: 합의 → 진행
else 서버가 다른 버전 지원
S-->>C: InitializeResult (protocolVersion: "2025-06-18")
Note over C,S: 클라이언트가 수용 가능하면 진행, 아니면 연결 종료
else 호환 불가
S-->>C: error -32602 Unsupported protocol version
Note over C,S: data.supported에 지원 목록 포함 → 연결 끊김
end
호환되지 않을 때의 에러는 이런 모양입니다:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Unsupported protocol version",
"data": { "supported": ["2025-06-18"], "requested": "2025-11-25" }
}
}
data.supported에 서버가 지원하는 버전 목록이 담겨, 클라이언트가 재시도할 단서를 줍니다. 초기화 에러는 해결될 때까지 이후 통신을 막습니다 — 호환되는 버전으로 클라이언트를 맞추거나, 요건을 만족하는 다른 서버를 찾아야 합니다.
전체 생명주기
핸드셰이크는 생명주기의 첫 국면일 뿐입니다. 연결 전체는 세 국면으로 나뉩니다.
stateDiagram-v2
[*] --> Initialization: 연결 시작
Initialization --> Operation: 핸드셰이크 완료\n(initialized 알림)
Operation --> Operation: tools/call, resources/read,\n알림, ping 등
Operation --> Shutdown: 작업 종료
Shutdown --> [*]: 전송 연결 닫힘
1) 초기화 (Initialization)
05장에서 다룬 핸드셰이크. 버전·capability 합의.
2) 정상 동작 (Operation)
합의된 버전과 capability에 따라 요청·알림·응답을 주고받습니다. 이 국면은 양방향이며, 클라이언트는 tools/list로 도구를 발견하고, 서버는 (협상됐다면) sampling이나 elicitation 요청을 먼저 보낼 수도 있습니다. 양쪽 모두 핸드셰이크에서 합의되지 않은 기능은 호출하지 않아야 합니다.
연결 유지를 위해 양쪽 다 ping을 보낼 수 있고, 긴 작업에는 진행 알림(notifications/progress)을 쓸 수 있습니다.
3) 종료 (Shutdown)
세션의 깔끔한 종료입니다. 특정 프로토콜 메시지는 필요 없습니다 — 클라이언트(또는 가끔 서버)가 그냥 하부 전송 연결을 닫습니다.
전송별로 닫는 방법이 다릅니다:
- stdio: 클라이언트가 서버의 입력 스트림(stdin)을 닫고 자식 프로세스를 종료시킵니다.
- Streamable HTTP: HTTP 연결을 닫습니다. (세션 ID를 쓰는 경우 별도 종료 흐름이 있을 수 있음)
세션(session)에 대한 짧은 노트
"세션"은 클라이언트-서버 간 논리적으로 연관된 상호작용의 묶음으로, 초기화 단계에서 시작됩니다. 전송 방식에 따라 세션이 생기는 방식이 다릅니다 — stdio에서는 프로세스 수명에 세션이 암묵적으로 묶이고, Streamable HTTP에서는 서버가 초기화 때 Mcp-Session-Id를 부여하면서 세션이 만들어집니다. (세션 ID의 보안 취급은 8장·21장에서.)
🔒 보안: 세션 ID는 대화 맥락 식별용이지 인증 수단이 아닙니다. 인증이 필요한 서버라면, 세션 ID와 무관하게 모든 요청이 독립적으로 유효한 인증 자격을 갖춰야 합니다. "세션이 살아있으니 인증은 건너뛴다"는 위험한 발상입니다.
이 장에서 배운 것
- MCP 버전은 날짜 문자열(예: 2025-11-25)이며,
initialize에서 협상된다. 문서를 쓸 땐 기준 리비전을 명시한다. - 버전 불일치는 -32602 에러로 나타나고
data.supported에 지원 목록이 담긴다. 초기화 에러는 이후 통신을 막는다. - 생명주기는 초기화 → 정상 동작 → 종료 3국면. 종료는 보통 별도 메시지 없이 전송 연결을 닫는 것.
- 세션 ID는 인증이 아니다 — 인증 서버는 매 요청을 독립적으로 검증해야 한다.
✍️ 확인 문제
- 왜 MCP는 시맨틱 버전 대신 날짜 버전을 쓸까? 한 가지 이점을 들어 보자.
- 클라이언트가
2025-11-25를 요청했는데 서버가2025-06-18로 응답했다. 다음에 일어날 수 있는 두 갈래는? - "세션이 유지되는 동안은 인증을 한 번만 하면 된다"는 주장의 문제점은?
2부 끝. 다음은 3부 · 전송 방식(07장 stdio)으로 이어집니다.