티스토리 뷰

Server

[Server] MCP 서버 프로토콜, SSE에서 Streamable HTTP 방식으로의 대변경

망나니개발자 2025. 8. 12. 10:00
반응형




 

1. MCP 서버 프로토콜, SSE에서 Streamable HTTP 방식으로의 대변경


[ MCP 프로토콜과 JSON-RPC 2.0 데이터 포맷 ]

이전 포스팅에서 설명하였듯, MCP(Modal Context Protocol)는 Antropic(엔트로픽)이 2024년 11월 26일 처음 제안되어 오늘날 가장 널리 사용되고 인기있는 AI 프로토콜 중 하나이다. MCP와 같은 AI 프로토콜은 다양한 AI 구성 요소 간에 정보를 주고 받기 위해 탄생하게 되었다.

구체적으로, MCP는 클라이언트와 서버 간 메시지 교환 형식으로 JSON-RPC 2.0을 사용한다. JSON-RPC 2.0은 RPC(Remote Procedure Call) 방식의 프로토콜 중 하나로, JSON을 메시지 포맷으로 사용하는 경량의 비동기 통신 프로토콜로, HTTP, WebSocket, TCP 등 다양한 전송 레이어 위에서 사용할 수 있는 전송 계층 독립형 프로토콜이다.

해당 프로토콜로 통신하는 경우, 먼저 클라이언트는 서버로 다음과 같은 JSON 요청을 보낸다. 만약 반환값이 없는 객체라면 id를 null로 보내야 하며, 응답이 없는 호출(Notification)으로 간주한다.

  • jsonrpc: 버전 정보로, 항상 "2.0" 이어야 함
  • method: 호출하고자 하는 메서드 이름
  • params: 메서드에 넘길 매개변수로, 배열 또는 객체를 보낼 수 있고 혹은 없을 수도 있음
  • id: 요청 식별자로, 응답을 매칭하기 위해 사용되며 null이면 응답이 오지 않음
{
  "jsonrpc": "2.0",
  "method": "subtract",
  "params": [42, 23],
  "id": 1
}

 

 

서버는 요청 처리에 성공한 경우, 클라이언트에게 다음과 같은 JSON 응답을 보내게 된다.

  • jsonrpc: 버전 정보로, 항상 "2.0" 이어야 함
  • result: 처리 결과
  • id: 요청 식별자로 받은 값을 전달함
{
  "jsonrpc": "2.0",
  "result": 19,
  "id": 1
}

 

 

만약 요청 처리에 실패했다면, 다음과 같은 형태의 JSON 응답을 반환하게 된다.

  • jsonrpc: 버전 정보로, 항상 "2.0" 이어야 함
  • error: 에러 실패 관련 정보들
    • code: 정해진 오류 코드 (예: -32601은 메서드 없음)
    • message: 사람이 읽을 수 있는 설명
    • data: 선택적으로 제공되는 추가 디버깅 정보
  • id: 요청 식별자로 받은 값을 전달함
{
  "jsonrpc": "2.0",
  "error": {
    "code": -32601,
    "message": "Method not found"
  },
  "id": 1
}

 

 

MCP 프로토콜은 JSON-RPC 2.0 포맷을 채택함으로써, 다음과 같은 장점들을 누릴 수 있었다.

  • 단순한 JSON 기반 구조로 언어나 플랫폼에 구애받지 않음
  • 응답이 필요한 호출 / 필요 없는 알림을 구분할 수 있음
  • 비동기 배치 처리 지원
  • 전송 프로토콜에 의존하지 않음 (HTTP, WebSocket, Socket 등 가능)

 

 

 

[ HTTP + SSE 방식 ]

위와 같은 JSON-RPC 메시지를 전달하기 위해, MCP는 초기에 HTTP+SSE 기반의 표준 전송 메커니즘(또는 로컬 서버 간 통신 시 사용하는 stdio 표준 입출력 방식)을 채택하였다.

이와 같은 특수한 전송 계층이 필요한 이유는, 우리에게 익숙한 기존의 HTTP 요청-응답 모델이 실시간 AI 통신에는 적합하지 않기 때문이다. 일반 HTTP는 자주 연결을 설정해야 하므로 오버헤드가 크고 지연 시간(latency)이 높은 반면, MCP는 지속적이고 지연이 낮은 데이터 스트림이 필요하였다. 따라서 이러한 목적에 맞게 MCP는 HTTP+SSE 기반의 표준 전송 메커니즘을 공식적인 전송 메커니즘으로 채택하게 되었다.

위의 전송 메커니즘을 MCP 서버는 반드시 2개의 엔드포인트를 제공해야 한다. 이를 통해 MCP는 양방향 메시지 통신을 위해 한 방향(S→C)은 SSE, 다른 방향(C→S)은 HTTP POST를 사용하는 것이다.

  • SSE 엔드포인트: 클라이언트가 연결을 설정하고, 서버로부터 메시지를 수신하기 위한 용도
  • 일반적인 HTTP POST 엔드포인트: 클라이언트가 서버로 메시지를 전송하기 위한 용도

 

 

1. Client가 Server로 SSE 연결하기

클라이언트는 SSE 스트림 연결을 수립하기 위해, MCP 서버의 SSE 엔드포인트로 HTTP GET 요청을 보낸다. 이때 요청 헤더에는 다음과 같은 항목이 포함되어 있어야 한다.

GET /sse
Accept: text/event-stream

 

 

이 요청이 성공하면 클라이언트는 서버로부터 지속적으로 메시지를 받는 통로를 열어둔 상태가 되며, 이 연결은 일반 HTTP 요청과 달리 끊기지 않는다. (long-lived connection) 즉, 서버는 연결을 유지한 채로, text/event-stream 포맷으로 지속적인 이벤트 스트림을 클라이언트에게 보내게 된다.

 

 

2. Server가 Client로 Endpoint 이벤트 전송하기

클라이언트가 연결을 설정하면, 서버는 반드시 endpoint 이벤트를 전송해야 하며, 이 이벤트에는 클라이언트가 메시지를 전송할 때 사용할 URI가 포함되어 있어야 한다. 이후 모든 클라이언트 메시지는 해당 엔드포인트로 HTTP POST 요청을 보내야 한다. 서버에서 보내는 메시지는 SSE의 endpoint 이벤트로 전송되며, data 필드에는 URI를 포함한 JSON 형식의 데이터를 포함해야 한다.

event: endpoint
data: { "uri": "/mcp/send/123456" }

 

 

클라이언트는 이후 이 URI로 메시지를 보낼 준비를 하게 되는데, 이 URI는 고정일 수도 있고, 동적으로 클라이언트별로 할당될 수도 있다.

 

 

3. Client가 Server로 메시지 전송하기(HTTP POST)

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

POST /mcp/send/123456 HTTP/1.1
Content-Type: application/json

{
  "type": "user_input",
  "content": "Hello!"
}

 

 

4. Server가 Client로 메시지 응답하기 (SSE message 이벤트)

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

event: message
data: {
  "type": "ai_response",
  "content": "안녕하세요! 무엇을 도와드릴까요?"
}

 

 

MCP 서버는 초기에 HTTP + SSE 방식을 채택하여, WebSocket보다 단순하고 HTTP 만으로 양방향 통신이 가능하도록 전송 메커니즘이 설계되었었다.

 

 

[ HTTP + SSE 방식의 한계들 ]

HTTP + SSE 기반의 통신이 전송 프로토콜로 채택됨에 따라, 많은 기업들이 해당 방식을 따라 원격의 MCP 서버들을 HTTP + SSE 통신 방식으로 지원하기 시작했다. 하지만 운영을 하면서 해당 통신 방식은 여러 가지 문제점들을 야기하게 되었다.

  1. 스트림 재개(resumable stream)를 지원하지 않음
  2. 서버가 장시간 유지되고 고가용성을 갖춘 연결을 유지해야 함
  3. 서버 메시지 전송이 SSE를 통해서만 가능함

 

 

스트림 재개(resumable stream)를 지원하지 않음

MCP 서버를 원격으로 제공하고 있다면, 연결이 맺어진 클라이언트들이 존재할 것이다. 문제는 네트워크의 연결 문제는 완전히 극복이 불가능한 부분이기에, 긴 AI 응답 중에 네트워크 문제로 연결이 끊기면 그 흐름을 이어갈 수 없다는 것이다. 이는 신규 기능 추가를 위해 MCP 서버를 배포하는 경우에도 동일하다.

따라서 HTTP + SSE 방식에서는 기존에 유지하던 연결이 끊어졌다면, 클라이언트는 처음부터 요청을 다시 보내야 하며, 서버는 전체 응답을 다시 생성하거나 재전송해야 한다. 해당 부분을 개선하려면 클라이언트가 중간에 연결이 끊겼을 때, 이전에 어디까지 수신했는지를 기억해서 이어받는 기능이 필요한데, 이는 기본적으로 제공되지 않는다.

 

 

서버가 장시간 유지되고 고가용성을 갖춘 연결을 유지해야 함

SSE는 서버가 클라이언트와 지속적인 연결을 유지한 채로 데이터를 푸시하는 방식인데, 이는 사실상 HTTP 연결을 장시간 유지하는 것이다. 따라서 서버 입장에서는 모든 클라이언트마다 연결을 유지해야 하기 때문에, 많은 사용자가 접속하면 연결 수가 폭발적으로 증가하며 높은 리소스 소비와 부하가 요구된다. 또한, 로드 밸런서나 게이트웨이가 지속적인 연결을 잘 처리하지 못하거나 제한이 있는 경우 문제가 발생할 수도 있다.

 

 

서버 메시지 전송이 SSE를 통해서만 가능함

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

 

 

 

[ Streamable HTTP 방식 ]

위와 같은 HTTP + SSE 방식의 단점과 한계들로 인해, MCP는 기존 방식의 장점은 유지하면서, 단점은 개선할 수 있는 Streamable HTTP을 새로운 전송 메커니즘으로 변경하게 되었다. 이는 MCP 프로토콜의 구현 방식에 적지 않은 수정을 불러왔으며, 많은 서드파티 MCP 클라이언트 및 서버 라이브러리가 이에 맞춰 변경되어야 했다. 하지만 그럼에도 이러한 변경은 필요했기에 이는 중대한 변화로 평가받고 있다.

Streamable HTTP는 전통적인 HTTP 통신을 확장해서 스트리밍처럼 작동하도록 만든 방식으로, 서버가 여러 클라이언트 연결을 처리할 수 있는 독립 실행형 프로세스로 동작한다. 표준 HTTP POST 및 GET 요청을 사용하여 통신을 수행하며, 선택적으로 서버는 SSE를 사용해 클라이언트로 여러 메시지를 스트리밍할 수 있다. 이를 통해 단순한 요청/응답 도구를 위한 기본적인 MCP 서버뿐만 아니라, 스트리밍 및 실시간 서버→클라이언트 알림과 같은 고급 기능을 제공하는 MCP 서버 모두에 적합하다.

이를 위해 서버는 GET과 POST 모두를 지원하는 하나의 HTTP 엔드포인트를 제공해야 한다. 클라이언트가 서버에 무언가 요청할 때는 기존처럼 HTTP POST로 JSON 메시지를 보내게 되고, 서버는 이벤트 스트림으로 응답을 제공하게 된다. 이때 서버는 응답을 단순히 한 번에 끝내지 않고, 조금씩 천천히 여러 개의 메시지를 순차적으로 보내는데, HTTP 응답을 계속 열어두고 스트리밍 형태로 동작하며 클라이언트는 이 열린 연결을 통해 메시지를 계속 받는 것이다.

 

 

 

1. Client가 Server로 메시지 전송하기(HTTP POST)

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

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

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

 

 

2. Server가 Client로 메시지 응답하기 (HTTP event-stream)

서버는 클라이언트에게 응답하기 위해 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]

 

 

즉, 스트리밍 방식에서는 “응답을 여러 번에 걸쳐 쪼개어 보내는 것처럼 보이게” 만들어 클라이언트가 여러 응답을 조각(chunk)으로 받는 것처럼 동작하는 것이다. 해당 방식이 가능한 이유는 Transfer-Encoding: chunked를 사용해, 데이터를 조각(chunk) 으로 나눠 전송하기 때문이다.

이때 중요한 부분은 Streamable HTTP가 연결이 끊겼을 때를 고려하여 messageId를 추가하였고, 이를 통해 이어받기도 고려한다는 점이다. 예를 들어 클라이언트가 다시 연결할 때 마지막으로 받은 messageId를 전달해주면, 서버가 이어지는 messageId부터 응답할 수 있는 것이다. 이를 통해 연결 장애에도 손실 없이 이어받기(resume)가 가능하다. 정리하면 다음과 같은 부분이 개선되었다고 볼 수 있다.

  • Streamable HTTP는 byte offset 또는 message ID 기반의 resume 기능을 설계에 포함할 수 있어, 중간부터 스트림을 다시 이어받을 수 있습니다.
  • Streamable HTTP는 일반 HTTP처럼 짧은 연결로 구성 가능하며, 필요 시 동적으로 SSE로 업그레이드할 수 있으므로 리소스 소모를 줄이고 유연한 확장이 가능합니다.
  • Streamable HTTP는 양방향 메시지 전송을 자연스럽게 지원하며, 하나의 연결 위에서 상호 메시지 교환이 가능해 실시간 통신에 더 적합합니다.

 

 

따라서 MCP는 2025-03-26 버전부터 공식적인 전송 메커니즘을 HTTP+SSE에서 Streamable HTTP로 교체하였고, 이는 프로토콜 아키텍처에 있어 중대한 변화를 의미한다.

 

 

 

 

 

참고자료

 

 

 

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2025/11   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
글 보관함