Table of contents
- 두 트랜스포트의 분기점
- stdio — 로컬 자식 프로세스
- Streamable HTTP — POST + 선택적 SSE
- 세션과 재개
- SSE 트랜스포트의 폐기와 2026 stateless 방향
두 트랜스포트의 분기점
1편에서 서버는 위치가 아니라 역할이라고 했다. 로컬에서 자식 프로세스로 돌든 인터넷 너머에서 SaaS로 돌든 둘 다 서버다. 그 차이를 흡수하는 자리가 트랜스포트다.
| 측면 | stdio | Streamable HTTP |
|---|---|---|
| 위치 | 같은 머신 | 보통 원격 |
| 연결 모델 | 1 클라이언트 ↔ 1 서버 자식 프로세스 | 1 서버 ↔ 다수 클라이언트 |
| 메시지 프레이밍 | 개행 구분 JSON | HTTP POST body / SSE event |
| 인증 | 없음 (로컬 신뢰) | OAuth 2.1 권장 |
| 메시지 손실 대응 | 트랜스포트 신뢰 | 세션·재개로 보완 |
같은 데이터 층 메시지가 둘 다 그대로 흐른다. 트랜스포트만 갈아끼우면 같은 서버가 양쪽에서 동작한다.
stdio — 로컬 자식 프로세스
stdio는 가장 단순한 모양이다. 클라이언트가 서버를 자식 프로세스로 띄우고, 그 프로세스의 stdin·stdout으로 메시지를 주고받는다.
sequenceDiagram
participant Client
participant Server as Server (subprocess)
Client->>+Server: spawn (예: node build/index.js)
loop 메시지 교환
Client->>Server: stdin: JSON-RPC request
Server->>Client: stdout: JSON-RPC response
Server--)Client: stderr: optional logs
end
Client->>Server: stdin 닫기 + SIGTERM
deactivate Server
규칙은 빡빡하다.
- 메시지는 한 줄짜리 JSON. 메시지 사이는 개행으로 구분하고, 메시지 내부에 개행이 들어가면 안 된다.
- stdout은 MCP 메시지 전용. 서버가 stdout에 다른 텍스트를 흘리면 클라이언트 파서가 깨진다. 5편에서
console.log대신console.error를 쓰라고 한 게 이 때문이다. - stderr는 자유. 디버그 로그·진행 상황 같은 건 stderr로 나가도 된다. 클라이언트는 stderr를 잡아 호스트 로그에 흘리거나 무시할 수 있다.
- 종료는 트랜스포트 차원. 클라이언트가 stdin을 닫고, 일정 시간 안에 서버가 안 끝나면 SIGTERM, 그래도 안 끝나면 SIGKILL.
stdio 트랜스포트의 강점은 과감한 단순함이다. 인증·세션·재시도가 다 필요 없다. 같은 머신의 1:1 IPC라 신뢰 경계가 OS 프로세스 격리에 묻혀 있다. 단점도 같은 자리에 있다 — 원격에선 못 쓴다.
Streamable HTTP — POST + 선택적 SSE
원격에서 돌릴 때 쓰는 트랜스포트다. 2025년 11월 spec부터 정식이다. 단일 HTTP 엔드포인트(MCP endpoint, 보통 https://example.com/mcp 같은 한 경로) 하나에 POST와 GET을 받게 한다.
클라이언트 → 서버 (요청 보내기)
매 메시지가 POST다.
POST /mcp HTTP/1.1
Host: example.com
Content-Type: application/json
Accept: application/json, text/event-stream
{"jsonrpc": "2.0", "id": 1, "method": "tools/call", ...}
Accept 헤더에 application/json과 text/event-stream을 둘 다 적어야 한다. 서버가 둘 중 어느 쪽으로 응답할지 고를 수 있어서다.
응답이 한 번이면 끝나는 단순 호출이면 서버는 Content-Type: application/json으로 한 번에 돌려준다. 진행 알림이 나가야 하거나 도구 실행이 길거나 서버가 클라이언트에 추가 요청을 끼워 넣어야 하면(sampling, elicitation 같은 클라이언트 primitive), 서버는 Content-Type: text/event-stream으로 SSE 스트림을 연다. 그 스트림에 progress 알림·sampling 요청·최종 응답을 차례로 넣고, 응답이 다 나간 다음에 스트림을 닫는다.
서버 → 클라이언트 (서버가 먼저 말 걸기)
서버가 클라이언트의 POST를 기다리지 않고 알림(tools/list_changed 같은 것)을 보내고 싶을 수도 있다. 클라이언트가 GET을 한 번 날려두면 서버가 그 응답으로 SSE 스트림을 연다.
GET /mcp HTTP/1.1
Accept: text/event-stream
이 스트림으로 서버는 알림이나 클라이언트로 향하는 요청을 푸시한다. 응답을 보내면 안 된다 — 재개 케이스가 아닌 한.
왜 이렇게 갈라뒀나. stdio는 양방향 통신이 자연스럽다(stdin·stdout). HTTP는 본래 클라이언트가 요청을 시작하지 서버가 먼저 말 걸지 못한다. SSE 스트림이 그 한쪽 방향을 열어준다. POST 응답에도, 별도 GET 응답에도 SSE를 쓸 수 있게 해서 상황별로 가장 가벼운 모양을 고르게 했다.
세션과 재개
stdio는 프로세스가 살아 있는 동안이 곧 세션이다. HTTP는 본래 무상태라, 세션 개념을 트랜스포트가 직접 만들어준다.
세션 ID
서버가 initialize 응답에 MCP-Session-Id 헤더를 실어 보낸다. 안전하게 생성된 UUID나 JWT 같은 값이다.
HTTP/1.1 200 OK
MCP-Session-Id: 1868a90c...
Content-Type: application/json
{"jsonrpc": "2.0", "id": 1, "result": {...}}
클라이언트는 이후 모든 요청에 같은 헤더를 같이 보낸다. 세션 ID 없이 들어온 요청(initialize 제외)은 서버가 400을 돌려준다. 서버가 세션을 만료시키면 404를 돌려주고, 클라이언트는 다시 initialize부터 시작한다. 세션을 명시 종료하려면 클라이언트가 DELETE /mcp + MCP-Session-Id 헤더를 보낸다.
재개 (Resumability)
긴 SSE 스트림이 네트워크 사정으로 끊어질 수 있다. 서버가 SSE 이벤트마다 ID를 붙여두면, 클라이언트가 마지막 받은 ID를 Last-Event-ID 헤더에 실어 GET을 다시 날리는 것으로 끊긴 자리부터 재개할 수 있다.
sequenceDiagram
participant Client
participant Server
Client->>Server: GET /mcp (Last-Event-ID: evt-42)
Server->>Client: SSE 스트림 재개
Server-->>Client: id: evt-43, data: {...}
Server-->>Client: id: evt-44, data: {...}
이벤트 ID는 스트림별로 유일해야 한다. 서버는 끊긴 스트림의 그 위치 이후 메시지를 재전송할 수 있고, 다른 스트림의 메시지를 섞어 보내면 안 된다. 메시지 손실 위험을 줄이는 표준 메커니즘이다.
Protocol Version 헤더
HTTP 트랜스포트는 매 요청에 MCP-Protocol-Version: 2025-11-25도 같이 보내야 한다. 4편의 버전 협상에서 정해진 값이다. 서버는 이 값으로 어떤 spec 모양에 맞춰 응답할지 결정한다. 헤더가 빠지면 서버는 옛 버전(2025-03-26)으로 가정하거나 400을 돌려준다.
SSE 트랜스포트의 폐기와 2026 stateless 방향
Streamable HTTP가 나오기 전 spec 버전(2024-11-05)에는 HTTP+SSE 트랜스포트가 따로 있었다. POST는 한 엔드포인트, SSE 수신은 다른 엔드포인트로 분리된 모양이었다. 호환을 위해 두 엔드포인트를 동시에 운영해야 했고, 세션 관리도 비표준이었다.
2025년 11월 spec에서 Streamable HTTP가 그 자리를 통째로 대체한다. 단일 엔드포인트, 표준화된 세션 헤더, 명세화된 재개 흐름. 옛 spec(2024-11-05)을 따르는 클라이언트와도 backwards compatibility 절차로 붙을 수 있게 했다 — 새 서버가 옛 SSE 엔드포인트도 같이 호스팅하거나, 새 클라이언트가 새 트랜스포트를 먼저 시도하고 실패하면 옛 트랜스포트로 폴백하는 식이다.
여기까지가 spec의 현재 모습이다. 2026 공식 로드맵이 가장 크게 손대려는 게 이 자리다. 핵심은 stateful 세션을 줄이는 방향이다.
지금의 Streamable HTTP는 세션 ID를 매개로 서버에 상태가 묶여 있다. 로드 밸런서 뒤에서 같은 세션 ID를 항상 같은 서버 인스턴스로 보내야 한다. 수평 확장이 어렵고, 서버 재시작에 세션이 깨진다.
2026 로드맵은 두 가지 방향을 동시에 잡는다.
- 세션 생성·재개·이전을 표준화 — 서버가 재시작되거나 인스턴스가 늘어나도 클라이언트 입장에선 투명하게 만든다.
.well-known메타데이터 — 서버가 연결 없이도 자기 capability를 노출할 수 있게 한다. 레지스트리·크롤러가 서버에 접속하지 않고도 카탈로그를 만들 수 있다.
이 방향이 자리 잡으면 원격 MCP 서버를 진짜 무상태 웹 서비스처럼 굴리는 그림이 완성된다. SaaS급 운영, 다중 리전 배포, blue-green 배포 같은 표준 웹 운영 기법을 그대로 가져다 쓸 수 있다.
다음 편에서는 그 원격 서버에 어떻게 안전하게 붙느냐 — 인증과 권한을 본다. 2025년 6월 spec부터 표준이 된 OAuth 2.1, Resource Server 모델, .well-known discovery, PKCE까지 — Streamable HTTP가 깔리고 나서 자연스레 다음 자리에 온 주제다.




Loading comments...