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 기준"이라고 말하면, 그날의 스펙 문서를 정확히 가리킬 수 있습니다.

📌 핵심
핵심: 이 안내서는 2025-11-25 리비전을 기준으로 합니다. 다음 리비전(2026-07-28)은 작성 시점에 RC(초안)이라, 확정되지 않은 내용은 단정하지 않습니다. 여러분이 코드를 쓸 때도 어느 리비전을 목표로 하는지 먼저 정하세요.

주요 리비전의 흐름 (맥락용)

세부 기능은 스펙 문서를 봐야 하지만, 큰 흐름을 알면 "왜 옛날 코드가 안 도는지" 이해됩니다.

리비전대표적 변화(요지)
2024-11-05최초 공개. 원격 전송은 HTTP+SSE(2개 엔드포인트)
2025-03-26Streamable HTTP 도입(HTTP+SSE deprecated), 인증 정비
2025-06-18인증 강화 — MCP 서버를 OAuth Resource Server로 분류, Resource Indicators(RFC 8707) 요구, 보안 가이드 보강
2025-11-251주년 안정판 — OpenID Connect Discovery, tool/resource/prompt 아이콘 메타데이터, 증분 스코프 동의, sampling의 도구 호출 지원 등
위 표의 약어를 잠깐 풀면: RFC(Request For Comments)는 인터넷 표준을 정의하는 공개 문서 번호로, "RFC 8707을 따른다"는 곧 "그 표준 문서의 규칙대로 한다"는 뜻입니다. OAuth·스코프·OpenID 같은 인증 용어는 7부에서 본격적으로 풀어 설명하니, 여기서는 "인증 관련 표준이 점점 정비됐다" 정도로만 읽어도 됩니다. deprecated는 "더 이상 권장되지 않고 향후 제거될 수 있음"을 뜻하는 소프트웨어 관용어입니다.
⚠️ 흔한 실수
흔한 실수: 반년 전 튜토리얼의 HTTP+SSE 코드를 새 프로젝트에 그대로 쓰는 것. SSE 2개 엔드포인트 방식은 2025-03-26부터 deprecated이며, 새 구현은 Streamable HTTP를 써야 합니다. (3부에서 상세히)

버전 협상

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

호환되지 않을 때의 에러는 이런 모양입니다:

JSON
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "Unsupported protocol version",
    "data": { "supported": ["2025-06-18"], "requested": "2025-11-25" }
  }
}

data.supported에 서버가 지원하는 버전 목록이 담겨, 클라이언트가 재시도할 단서를 줍니다. 초기화 에러는 해결될 때까지 이후 통신을 막습니다 — 호환되는 버전으로 클라이언트를 맞추거나, 요건을 만족하는 다른 서버를 찾아야 합니다.

💡 팁
실무 팁: 클라이언트는 보통 "내가 아는 가장 최신 버전"을 제시하고, 서버가 더 낮은 버전으로 응답하면 그에 맞춰 기능을 낮춰 동작합니다(graceful degradation). 서버를 만들 때는 한두 개 이전 리비전과의 호환을 고려하면 더 많은 클라이언트가 붙습니다.

전체 생명주기

핸드셰이크는 생명주기의 첫 국면일 뿐입니다. 연결 전체는 세 국면으로 나뉩니다.

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)을 쓸 수 있습니다.

💡 팁
타임아웃과 취소: 클라이언트는 요청별 타임아웃(예: 30초)을 두고, 시간 내 응답이 없으면 취소 알림을 보내 서버가 처리를 멈추게 합니다. 진행 알림이 오면 타임아웃을 연장하되, 최대 상한은 항상 둬야 합니다. 이렇게 해서 요청이 무한정 매달리지 않게 합니다.

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는 인증이 아니다 — 인증 서버는 매 요청을 독립적으로 검증해야 한다.

✍️ 확인 문제

  1. 왜 MCP는 시맨틱 버전 대신 날짜 버전을 쓸까? 한 가지 이점을 들어 보자.
  2. 클라이언트가 2025-11-25를 요청했는데 서버가 2025-06-18로 응답했다. 다음에 일어날 수 있는 두 갈래는?
  3. "세션이 유지되는 동안은 인증을 한 번만 하면 된다"는 주장의 문제점은?
2부 끝. 다음은 3부 · 전송 방식(07장 stdio)으로 이어집니다.