MCP의 계층

 

MCP는 Transport Layer와 Protocol Layer의 두 계층으로 추상화되어 정의된다.

두 가지 계층

Transport Layer

Transport의 책임은 JSON-RPC 메시지를 어떻게 주고받을 것인가에 대한 책임을 가지고 있다.

  1. 데이터 추출: HTTP 요청이 들어오면 헤더에서 세션 정보와 인증 정보를 추출하고, 요청 본문에서 JSON 데이터를 추출
  2. 추출 데이터 변환: 추출한 데이터를 Protocol Layer가 이해할 수 있는 형태로 변환하여, HTTP의 복잡한 헤더와 쿠키, 세션 정보를 MCP 메시지 객체로 변환하여 상위 계층에 전달
  3. 클라이언트에게 전송: Protocol Layer에서 내려온 응답을 다시 전송 방식에 맞게 변환하여 클라이언트에게 전송

이 과정에서 Transport Layer는 전송 방식의 특성을 완전히 숨기고, Protocol Layer에게는 일관된 인터페이스만을 제공한다. HTTP든 WebSocket이든 gRPC든, Protocol Layer는 단순히 “메시지를 받았다”와 “응답을 보낸다”라는 두 가지 동작만 알면 된다.

Protocol Layer

Protocol Layer는 MCP의 본질로, 무엇을, 언제 보내며, 그 메시지가 무엇을 의미하는가에 대한 책임을 가지고 있다.

  1. 의미 해석: Transport Layer가 전달한 원시 데이터를 분석하여 이것이 도구 실행인지, 리소스 접근 요청인지, 프롬프트 요청인지 판단한다.
  2. 적절한 비즈니스 로직 실행: 도구 실행 요청이라면 해당 도구를 찾아서 실행하고, 리소스 요청이라면 해당 리소스를 조회하여 반환한다.
  3. 책임을 응답 형태로 구성: 실행 결과나 오류 정보를 MCP 표준에 맞는 응답 메시지로 구성하여 Transport Layer에 전달

Transport vs Protocol 책임 분리

세션 관리

세션 관리도 Transport Layer, Protocol Layer 두 계층으로 나누어 관리된다.

Transport Layer는 세션 연결, Protocol Layer는 세션 상태 및 비즈니스 로직 관리

Transport Layer

  • 연결 수준의 세션 식별 및 매핑을 담당 (e.g. HTTP 헤더 섹션 ID, 쿠키 기반 세션 정보 읽기)
  • 전송 방식별 차이를 감추고, Protocol Layer에는 일관된 메시지 인터페이스만 제공

Protocol Layer

  • 비즈니스 수준 세션 상태를 관리 (클라이언트 상태, 권한 정보, 할당된 리소스, 수행 중인 도구 등)
  • 세션의 생명주기(초기화, 활성화, 만료, 종료)와 함께 세션 내 비즈니스 상태의 저장/변경/정리를 담당

세션 상태 동기화

두 Layer는 세션과 관련된 서로 다른 상태를 유지하지만, 독립적으로만 움직일 수는 없으며 상호 동기화가 필요하다.

Transport → Protocol 동기화

Transport Layer는 세션과 관련된 물리적 변화(e.g. 연결 종료, 타임아웃, 세션 삭제 요청 등)를 감지했을 때 Protocol Layer에게 알려야 한다.

e.g.

클라이언트가 연결을 닫음 → Transport Layer detects it
→ Protocol Layer에게 세션 종료/cleanup 요청 전달

Protocol → Transport 동기화

Protocol Layer는 비즈니스 상태 변화(e.g. 세션 만료, 권한 업데이트 등)를 Transport Layer에게 알려야 한다.

e.g.

세션 expired → Protocol Layer notifies Transport
→ Transport Layer는 해당 세션에 대한 모든 연결 상태 정리 및 거부 처리

Streamable HTTP

Streamable HTTP Transport는 분리된 두 계층이 현실의 HTTP 환경에서 충돌하는 지점을 해결하기 위해 등장

HTTP + SSE 방식

MCP 는 초기에 HTTP + SSE 기반의 표준 전송 메커니즘(또는 stdio 표준 입출력 방식)을 채택하였다. 이는 HTTP 요청-응답 모델이 실시간 AI 통신에는 적합하지 않았기 때문이다. HTTP는 자주 연결을 설정해야 하므로 오버 헤드가 크고, 지연 시간(latency)이 높은 만면, MCP는 지속적이고 낮은 데이터 스트림이 필요하였다.

  • SSE Endpoint: Client가 연결을 설정하고, Server로부터 메시지를 수신하기 위한 용도
  • HTTP POST Endpoint: Client가 Server로 메시지를 전송하기 위한 용도

HTTP + SSE 방식의 한계

  1. 스트림 재개(resumable stream)미지원 : 기존 연결이 끊어졌다면 Client는 처음부터 요청을 다시 보내야하며, Server는 전체 응답을 다시 생성하거나 재전송 해야한다. 이때 해당 부분을 개선하려면 Client가 중간에 연결이 끊겼을 때, 이전에 어디까지 수신했는지를 기억해서 이어받는 기능이 필요한데, 이는 기본적으로 제공되지 않음

  2. 장시간 연결 미지원: SSE는 Server가 Client와 지속적인 연결을 유지한 채로 데이터를 푸시하는 방식인데, 이는 사실상 HTTP 연결을 장시간 유지하는 것이다. Server 입장에서는 모든 Client마다 연결을 유지해야 하기 때문에, 높은 리소스 소비와 부하가 요구됨

  3. 메시지 전송: SSE 방식의 통신은 Server에서 Client 방향으로만 가능하며, Client → Server는 별도의 HTTP 요청이 필요함. 따라서 양방향 통신이 필요한 시나리오 (e.g. AI 채팅에서 중단, 취소, 수정 요청 등)에 대응하기 어려우며 Client가 메시지를 보내는 순간마다 새로운 HTTP 요청을 생성해야 하므로, 이로 인해 통신 지연(latency)이 커질 수 있음

Streamable HTTP 방식

HTTP + SSE 방식의 단점과 한계들로 인해, MCP는 기존 방식의 장점은 유지하면서 단점은 개선할 수 있는 Streamable HTTP을 새로운 전송 메커니즘으로 변경하였다.

Streamable HTTP는 전통적인 HTTP 통신을 확장해서 스트리밍처럼 작동하도록 만든 방식으로, Server가 여러 Client 연결을 처리할 수 있는 독립 실행형 프로세스로 동작한다.

표준 HTTP POST 및 GET 요청을 사용하여 통신을 수행하며, 선택적으로 Server는 SSE를 사용해 Client로 여러 메시지를 스트리밍할 수 있다. 이를 통해 단순한 요청/응답 도구를 위한 기본적인 MCP 서버뿐만 아니라, 스트리밍 및 실시간 Server → Client 알림과 같은 고급 기능을 제공하는 MCP 서버 모두에 적합하다.

Server는 GET과 POST 모두를 지원하는 하나의 HTTP Endpoint를 제공해야 하며, Client가 Server에 무언가 요청할 때는 기존처럼 HTTP POST로 JSON 메시지를 보내게 되고, Server는 Event stream으로 응답을 제공하게 된다. 이때 Server는 응답을 한 번에 끝내지 않고 조금씩 여러 개의 메시지를 순차적으로 보내는데, HTTP 응답을 계속 열어두고 스트리밍 형태로 동작하며 Client는 이 열린 연결을 통해 메시지를 계속 받는다.

Client가 Server로 전송 (HTTP POST)

Client는 Server에서 받은 URI로 HTTP POST 요청을 보내며, 요청의 본문에는 JSON 형식의 데이터가 포함된다. 이 POST는 연결을 유지할 필요가 없기 때문에 단발성 요청으로 효율적인 방식으로 처리된다.

POST /mcp
Content-Type: application/json
Accept: text/event-stream

{
  "jsonrpc": "2.0",
  "method": "generateText",
  "params": {
    "prompt": "오늘 날씨 어때?"
  },
  "id": "1"
}

Server가 Client로 메시지 응답 (HTTP event-stream)

Server는 Client에게 응답하기 위해 SSE 스트림을 통해 message 이벤트를 보내며, data 필드에는 응답 메시지가 JSON 형식으로 포함된다.

HTTP/1.1 200 OK
Content-Type: text/event-stream
Transfer-Encoding: chunked

data: { "jsonrpc": "2.0", "id": "1", "messageId": "msg-101", "result": "오늘 날씨는..." }
data: { "jsonrpc": "2.0", "id": "1", "messageId": "msg-102", "result": "...맑고 따뜻합니다." }

event: end
data: [DONE]

이때 해당 데이터의 응답은 스트리밍으로 반환되는데, 아래와 같이 조각내어 응답이 순차적으로 전달되게 한다.

[T=0] data: { "jsonrpc": "2.0", "id": "1", "messageId": "msg-101", "result": "오늘 날씨는..." }
[T=0.3] data: { "jsonrpc": "2.0", "id": "1", "messageId": "msg-102", "result": "...맑고 따뜻합니다." }
[T=0.5] event: end
[T=0.5] data: [DONE]

스트리밍 방식에서는 Client가 여러 응답을 Chunk로 받는 것처럼 동작한다. 이때 Streamable HTTP가 연결이 끊겼을 때를 고려하여 messageId를 추가하였고, 이를 통해 이어받기도 고려한다.

2025년 3월 26일 버전부터 공식적인 전송 메커니즘을 HTTP + SSE에서 Streamable HTTP로 교체하였고, MCP Protocol 구현 방식에 적지 않은 수정을 불러왔고, 많은 서드파트 MCP Client 및 Server 라이브러리가 이에 맞춰 변경되어야 했지만 중요한 변화였음. https://github.com/modelcontextprotocol/modelcontextprotocol/pull/206

Reference

  • [1] https://mangkyu.tistory.com/442
  • [2] https://wikidocs.net/286339