ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • MCP는 왜 SSE를 골랐나 — HTTP 위에서 서버가 먼저 말하게 하는 법
    IT 2026. 7. 1. 21:00
    MCP는 왜 SSE를 골랐나 — HTTP 위에서 서버가 먼저 말하게 하는 법

    MCP(Model Context Protocol, AI 모델을 외부 도구·데이터와 잇는 표준 규약) 문서를 처음 읽으면 SSE라는 세 글자에서 한 번 멈칫하게 된다. "원격 트랜스포트는 HTTP + SSE를 쓴다"는 문장은 나오는데, 정작 왜 하필 SSE인지는 설명 없이 지나가기 때문이다. 이 글은 그 빈자리를 채운다 — MCP가 왜 SSE를 끌어들일 수밖에 없었고, SSE가 어떤 문제를 어떻게 풀었으며, 그래서 MCP에 무엇이 좋아졌는지를 순서대로 본다.

    SSE는 Server-Sent Events의 약자다. 이름 그대로 "서버가 먼저 보내는 이벤트", 즉 서버가 클라이언트에게 데이터를 밀어 넣는(push) 표준 방식이다. 이 한 줄에 MCP가 SSE를 고른 이유가 이미 절반쯤 담겨 있다 — MCP에서는 서버가 클라이언트에게 먼저 말을 걸어야 하는 순간이 끊임없이 생기기 때문이다.

    배경: HTTP는 "묻고 답하면 끝"이라는 한 방향 대화다

    SSE가 왜 필요했는지 알려면, 웹의 기본 통신인 HTTP가 어떻게 생겼는지부터 봐야 한다. HTTP는 태생이 요청-응답(request-response) 모델이다. 클라이언트가 질문을 하나 던지면 서버가 답을 하나 주고, 연결은 거기서 닫힌다.

    diagram

    다이어그램 설명. 전통적 HTTP 한 사이클이다. 핵심은 "한 요청 = 한 응답 = 연결 종료"이고, 대화를 먼저 거는 쪽은 언제나 클라이언트라는 점이다. 서버는 질문을 받아야만 답할 수 있고, 자기 쪽에서 먼저 "지금 이런 일이 생겼어"라고 말을 걸 통로가 없다. 웹페이지 한 장을 받아 오는 데는 완벽한 모델이다. 함정은 "서버가 시간이 흐르는 동안 클라이언트에게 뭔가를 계속 알려 주고 싶을 때" 드러난다 — 이 구조에는 그럴 길이 아예 없다.

    문제: MCP에서는 서버가 먼저 말해야 할 일이 계속 생긴다

    그런데 MCP가 다루는 일은 이 모델과 정면으로 부딪친다. MCP에서 클라이언트(호스트)는 서버에게 "이 도구를 실행해줘"라고 부탁하는데, 그 도구 실행이 다음과 같은 성질을 가진다.

    diagram

    다이어그램 설명. MCP의 도구 호출이 왜 요청-응답과 안 맞는지를 한자리에 모았다. 위에서 아래로 읽으면 된다. 도구 실행은 수 초~수십 초씩 걸리고, 그 사이 "검색 50% 진행", "파일 1,204개 중 800개 처리" 같은 알림이 계속 생긴다. 게다가 MCP에는 "어떤 리소스가 바뀌면 서버가 먼저 클라이언트에게 알린다(notification)" 같은 서버 발신 기능도 있다. 이 모든 걸 "완성될 때까지 침묵하다 한 번에 답하는" HTTP로 처리하면, 클라이언트는 그저 기다릴 수밖에 없다. 결론은 맨 아래 화살표다 — 서버가 클라이언트에게 먼저, 그리고 여러 번 말을 거는 통로가 필요하다. 놓치기 쉬운 점: 이건 "더 빠르게"의 문제가 아니라 대화의 방향 문제다. HTTP가 막아 둔 "서버→클라이언트" 방향을 어떻게 여느냐가 핵심이다.

    선택지: 서버 푸시를 여는 네 가지 길, 그리고 SSE

    "서버가 클라이언트에게 계속 데이터를 보낸다"는 요구는 웹에서 20년 넘은 오래된 숙제다. 해법은 크게 넷이고, MCP는 그중 SSE를 골랐다. 왜 그랬는지는 네 방법을 한 축에서 비교하면 드러난다.

    방법 방향 프로토콜 인프라 부담 MCP 적합도
    Polling(폴링) 클라이언트가 물을 때만 HTTP 낮음 나쁨 — 지연·낭비
    Long Polling(롱 폴링) 물을 때만 HTTP 낮음 보통 — 군더더기
    WebSocket(웹소켓) 양방향 별도(ws) 높음 과함 — 양방향 불필요
    SSE 서버→클라 단방향 HTTP 낮음 최적

    표 설명. 네 방법을 같은 잣대로 세웠다. Polling·Long Polling은 결국 "클라이언트가 물어봐야" 데이터가 오니 서버가 먼저 말하는 데는 군더더기가 많다. WebSocket은 양방향이라 강력하지만, HTTP가 아닌 별도 프로토콜(ws://)이라 방화벽·프록시·인증·로드밸런서를 새로 손봐야 해 인프라가 무거워진다. SSE는 양방향을 포기하는 대신 HTTP 그대로라는 가벼움을 얻는다. 그리고 MCP에서 서버가 보내는 것(진행률·결과·알림)은 거의 다 "서버→클라이언트 한 방향"이라, SSE의 제약이 이 자리에서는 제약이 아니다. 꼭 맞는 칼이 가장 좋은 칼이다.

    SSE는 무엇을 어떻게 보내는가 — 헤더 한 번 + 끝나지 않는 본문

    SSE가 "그냥 HTTP"라는 말이 어느 정도냐면, 연결 위를 흐르는 게 사람이 읽을 수 있는 평범한 텍스트다. 형식이 놀랄 만큼 단순하다.

    diagram

    다이어그램 설명. SSE 스트림의 실제 생김새다. HTTP 응답 헤더는 맨 위에서 딱 한 번 내려가고, 그 아래는 전부 끝나지 않은 하나의 응답 본문이다. 이벤트들은 그 본문을 빈 줄로 끊어 읽는 단위일 뿐, 각각이 별개의 HTTP 응답이 아니다. 보통 HTTP는 본문을 다 만들어 Content-Length로 "여기까지 끝"을 알리지만, SSE는 Transfer-Encoding: chunked로 "본문을 조각조각 보낼 거고 끝은 미정"이라 선언해 연결을 닫지 않고 본문을 계속 이어 붙인다. 쓰이는 필드는 네 개가 거의 전부다 — data(내용), event(이벤트 이름), id(일련번호), retry(재연결 대기 ms). 그래서 event:·id:·data:는 모양이 HTTP 헤더(키: 값)와 닮았어도 계층이 다르다 — 헤더가 봉투라면 이 필드들은 봉투 안 편지 내용이다. 이 단순함 덕에 별도 라이브러리 없이 텍스트 한 줄씩 읽으면 파싱이 끝난다.

    MCP가 SSE를 끼운 방식 — 엔드포인트 2개

    MCP는 메시지를 JSON-RPC(JSON으로 함수 호출·응답을 주고받는 규약)로 표현한다. 문제는 "이 JSON-RPC 메시지를 무슨 통로로 실어 나르냐"인데, 이 통로를 트랜스포트(transport)라 부른다. 초기 MCP의 원격 트랜스포트가 바로 HTTP + SSE 조합이었고, 통로를 둘로 갈랐다.

    diagram

    다이어그램 설명. 초기 MCP의 원격 통신 구조다. 통로가 둘로 갈라져 있는 게 핵심이다 — 클라이언트는 먼저 GET /sse서버→클라이언트 방향의 SSE 연결을 열어 두고(받는 귀), 자기가 보낼 메시지는 모두 별도의 POST /messages로 부친다(말하는 입). 엔드포인트를 가르는 기준은 "읽기 대 쓰기"가 아니라 데이터가 흐르는 방향이다 — SSE가 단방향이라 반대 방향을 실어 나를 통로를 따로 둬야 했다. 여기에 직관과 어긋나는 비대칭이 하나 있다. 도구 호출 요청은 POST /messages로 보내지만, 그 POST의 HTTP 응답은 "접수했음(202)"에 그치고, 진행률과 실제 결과는 열어 둔 GET /sse 통로로 따로 내려온다. "요청은 POST로, 답은 SSE로" 받는 구조인 것이다. 놓치기 쉬운 점: 그래서 클라이언트는 POST의 응답만 봐서는 결과를 알 수 없고, 반드시 SSE 쪽을 함께 듣고 있어야 한다.

    덤: 끊겨도 이어받는 자동 재연결

    SSE에는 MCP처럼 연결이 오래 열려 있는 작업에 특히 잘 맞는 기능이 명세에 박혀 있다. 끊김에 대한 자동 재연결과 이어받기다.

    diagram

    다이어그램 설명. SSE가 끊김에 어떻게 대응하는지를 보여준다. 서버가 각 이벤트에 id를 붙이면 클라이언트는 마지막으로 받은 id를 기억한다. 연결이 끊기면 SSE 클라이언트가 알아서 재연결하면서 Last-Event-ID: 42 헤더로 "나 42번까지 받았어"를 알리고, 서버는 43번부터 이어서 보낸다. 이 절차가 표준에 정의돼 있어 개발자가 재연결 로직을 직접 짤 필요가 거의 없다. 도구 실행이 길어 연결이 오래 열려 있는 MCP에서는 이 자동 복구가 큰 안전망이다. 다만 함정: "이어받기"가 실제로 되려면 서버가 이미 보낸 이벤트를 어느 정도 버퍼에 기억하고 있어야 한다 — 안 그러면 재연결은 되어도 빠진 조각은 못 채운다.

    효과: MCP가 SSE로 얻은 것 (그리고 남긴 숙제)

    SSE 덕에 MCP는 "표준 HTTP 위에서, 서버가 클라이언트에게 진행률·결과·알림을 실시간으로 흘려보내는" 능력을 가장 적은 비용으로 얻었다.

    diagram

    다이어그램 설명. SSE가 MCP에 가져온 효과를 모았다. 특히 가운데 "HTTP 그대로"가 결정적이다. WebSocket 같은 새 프로토콜을 도입했다면 방화벽 정책·인증 토큰 전달·로드밸런서 설정을 전부 새로 손봐야 했지만, SSE는 기존 HTTP 인프라에 그대로 얹힌다 — 적게 가져가는 설계가 더 멀리 퍼진다. 다만 이 구조에는 숙제도 남았다. 서버가 클라이언트마다 SSE 연결을 계속 붙잡고 있어야 해서 상태를 가진(stateful) 무거운 구조가 됐고, 모든 응답이 단 하나의 SSE 채널로 섞여 내려와 요청-응답 짝을 맞추기 번거로웠으며, 연결이 한 번 끊기면 세션이 통째로 날아갔다. 이 숙제가 다음 단계 — MCP가 트랜스포트를 Streamable HTTP로 개편한 동기가 된다.

    정리

    MCP가 SSE를 고른 건 유행이 아니라 문제의 모양과 도구의 모양이 같아서다. MCP에서 서버가 보내는 것 — 도구 진행률, 실행 결과, 리소스 변경 알림 — 은 모두 "서버가 시간을 두고 하나씩 내보내는 단방향 흐름"이고, 그 형태에 표준 HTTP 위에서 정확히 들어맞는 도구가 SSE다.

    대신 SSE는 "받는 통로"만 열어 줄 뿐, 보내는 통로와 상태 관리·확장성은 그대로 숙제로 남겼다. 그 숙제를 어떻게 풀었는지가 다음 이야기, Streamable HTTP다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.