Stateless MCP 서버: 세션·Mcp-Session-Id·핸들

Stateless MCP 서버는 요청 사이에 세션을 유지하지 않습니다. Mcp-Session-Id의 역할, 2026-07-28 개정판이 없앤 것, 상태를 대신 어디에 두는지 정리합니다.

읽는 시간 5분Sume
전체 글

Stateless MCP 서버는 HTTP 요청 사이에 세션을 유지하지 않습니다. 요청마다 서버가 응답하는 데 필요한 모든 것을 담고 있습니다. 2025-11-25 개정판에서는 Streamable HTTP 서버가 초기화 시 세션 ID인 Mcp-Session-Id를 할당할 수 있습니다. 현재 개정판인 2026-07-28은 프로토콜 세션과 그 헤더를 없앴고, 호출 간에 상태가 필요한 서버는 명시적인 핸들을 일반 도구 인수로 주고받습니다.

프로토콜 내용은 MCP 2025-11-25 트랜스포트 페이지, 2026-07-28 변경 로그와 도구 페이지, 버전 관리 페이지, MCP Inspector의 프로토콜 시대 페이지에서 가져왔으며, 2026-09-28에 확인했습니다. 예시로 든 Sume 호스팅 MCP 서버 내용은 서버의 현재 코드와 Job과 결과 (영문) 문서에서 가져왔습니다. Sume 기초 페이지는 호스팅 MCP가 여전히 동작하지만 현재 주 경로는 아니라고 설명합니다.

2025-11-25와 2026-07-28 개정판 사이에 무엇이 바뀌었나요?

핸드셰이크 시대의 개정판에서 MCP 세션은 초기화로 시작하는, 클라이언트와 서버 사이의 관련된 상호작용 묶음입니다. 2026-07-28 개정판은 MCP를 stateless로 만듭니다. 모든 요청이 _meta에 프로토콜 버전과 클라이언트 기능(capabilities)을 담으며, MCP Inspector는 최신(modern) 연결을 세션 없이 요청 단위로 동작한다고 설명합니다.

MCP 2025-11-25 트랜스포트 페이지와 2026-07-28 변경 로그 기준, 2026-09-28 확인.
항목2025-11-252026-07-28
세션서버가 초기화 시 MCP-Session-Id를 할당할 수 있음(MAY). 클라이언트는 이후 모든 요청에 이를 보냄프로토콜 수준 세션 없음. Mcp-Session-Id 제거
핸드셰이크initialize 다음 notifications/initialized제거. 버전과 기능(capabilities)은 요청마다 함께 전달
호출 간 상태서버가 만든 세션에 둘 수 있음서버가 발급한 명시적 핸들을 도구 인수로 전달
끊긴 스트림서버가 Last-Event-ID로 재개 가능하게 만들 수 있음(MAY)재개 불가. 클라이언트가 새 ID로 요청을 다시 보냄

Mcp-Session-Id는 어떻게 동작하고, 세션을 찾을 수 없다는 오류는 왜 나나요?

2025-11-25 Streamable HTTP 서버에서 세션 ID는 InitializeResult를 담은 응답의 MCP-Session-Id 헤더로 전달됩니다. 그 이후에는 다음과 같습니다.

  • 클라이언트는 이후의 모든 HTTP 요청에 그 헤더를 반드시 포함해야 합니다(MUST).
  • 세션을 요구하는 서버는 초기화 요청이 아닌데 헤더가 없는 요청에 400 Bad Request로 응답해야 합니다(SHOULD).
  • 서버는 언제든 세션을 끝낼 수 있습니다. 그 뒤에는 해당 세션 ID에 반드시 404 Not Found로 응답해야 하고(MUST), 클라이언트는 세션 ID가 없는 새 InitializeRequest로 새 세션을 반드시 시작해야 합니다(MUST). 그러니 세션을 찾을 수 없다는 오류에는 이전 ID로 재시도할 것이 아니라 새로 초기화해야 합니다.
  • 작업을 마친 클라이언트는 그 헤더를 담아 HTTP DELETE를 보내야 하며(SHOULD), 서버는 405 Method Not Allowed로 응답할 수 있습니다(MAY).

세션 없이 상태는 어떻게 유지하나요?

핸들을 반환하세요. 2026-07-28 도구 페이지의 안내는 이렇습니다. 장바구니, 열려 있는 브라우저 컨텍스트, 데이터베이스 트랜잭션처럼 호출 간에 상태가 필요한 서버는 생성 도구에서 명시적인 핸들을 반환하고, 이후 호출에서 그 핸들을 인수로 받습니다. 모델은 핸들을 다음 호출로 넘기고, 서버는 호출마다 그 상태를 조회합니다. 명세의 핸들 설계 참고 사항이 먼저 나오고, 그다음 장바구니 예시가 이어집니다.

  • 인가: 핸들은 권한(capability)이 아니라 이름일 뿐이므로, 호출마다 그 핸들에 대한 호출자의 권한을 확인하세요.
  • 수명: 보존 정책은 모델이 볼 수 있는 생성 도구의 설명에 적으세요.
  • 만료: 만료됐거나 알 수 없는 핸들로 호출하면, 그렇다고 알려 주는 도구 실행 오류를 반환해야 합니다.
// → tools/call
{ "name": "create_basket", "arguments": {} }
// ← result
{
  "content": [{ "type": "text", "text": "Created basket bsk_a1b2c3" }],
  "structuredContent": { "basket_id": "bsk_a1b2c3" }
}
// → tools/call
{ "name": "add_item", "arguments": { "basket_id": "bsk_a1b2c3", "sku": "..." } }

Sume MCP 서버는 stateless인가요?

프로토콜 수준에서는 그렇습니다. 현재 코드에서 https://mcp.sume.com/mcp의 Sume 호스팅 서버는 각 POST를 60초 기한 안에 따로따로 처리하고, JSON 본문으로 응답하며, 세션 ID를 설정하지 않습니다. 다만 여전히 핸드셰이크 시대의 방식을 씁니다. initialize로 2025-03-26, 2025-06-18, 2025-11-25 중 하나를 협상합니다.

오래 걸리는 작업은 이미 핸들을 씁니다. 현재 코드에서 generate_video는 클립 URL이 아니라 Job id로만 응답하고, 에이전트는 그 id를 jobs_wait와 jobs_result에 넘깁니다. 문서에 따르면 jobs_wait는 id를 1개에서 20개까지 받고 호출당 최대 55초까지 대기하며, 알 수 없거나 다른 워크스페이스의 id가 있으면 호출 전체가 실패합니다. 대기 루프는 긴 영상 Job의 MCP 도구 호출 타임아웃에서 다룹니다.

2026-07-28 클라이언트는 핸드셰이크 시대 서버에 연결되나요?

폴백하는 경우에만 연결됩니다. 2026-07-28 트랜스포트 페이지에 따르면 이전 개정판과 상호 운용하는 클라이언트와 서버는 상대의 시대를 감지해 폴백합니다. Streamable HTTP에서는 버전이 MCP-Protocol-Version 헤더로도 전달됩니다.

Sume 서버를 보면 폴백이 왜 중요한지 알 수 있습니다. 현재 코드에서 이 서버는 값이 2026-07-28인 MCP-Protocol-Version 헤더에 HTTP 400으로 응답하므로, 그 개정판에만 고정된 클라이언트는 연결에 실패하고 initialize로 폴백하는 클라이언트는 연결할 수 있습니다. MCP Inspector의 legacy, auto, modern 시대 설정은 MCP 서버를 테스트하는 방법에서, Sume가 응답하는 코드는 MCP 오류 코드에서 다룹니다.

출처

관련 글

개발자 카테고리의 다른 글

개발자 글 전체 보기

작성자 Sume