앞선 글에서 MCP가 무엇인지, 그리고 Tools·Resources·Prompts라는 세 가지 primitive를 살펴봤다. 그런데 클라이언트와 서버가 실제로 “어떤 통로로” 메시지를 주고받는지는 아직 다루지 않았다. 이 통로를 MCP에서는 transport라고 부른다. 표준으로 정해진 방식은 두 가지, stdio와 HTTP다.
메시지는 어차피 JSON-RPC
transport를 고르기 전에 알아둘 것이 하나 있다. 어떤 통로를 쓰든 오가는 메시지의 형식은 동일하다. MCP는 JSON-RPC 2.0을 쓴다. 도구를 호출하는 요청은 이렇게 생겼다.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "get_weather", "arguments": { "city": "Seoul" } }
}
transport는 이 JSON 덩어리를 어디로 흘려보낼지만 결정한다. 그래서 서버 로직을 짤 때 transport를 크게 신경 쓰지 않아도 되고, 나중에 바꾸기도 쉽다.
stdio — 표준 입출력으로 연결
stdio transport는 클라이언트가 서버를 자식 프로세스로 직접 실행한다. 그리고 그 프로세스의 표준 입력(stdin)으로 요청을 써 넣고, 표준 출력(stdout)으로 응답을 읽는다. 우리가 터미널에서 cat 같은 명령에 입력을 파이프로 넘기는 것과 원리가 같다.
클라이언트 ──(stdin)──▶ 서버 프로세스
클라이언트 ◀──(stdout)── 서버 프로세스
여기서 중요한 규칙 하나. 로그를 절대 stdout에 찍으면 안 된다. stdout은 JSON-RPC 메시지 전용 통로라, console.log로 아무거나 출력하면 프로토콜이 깨진다. 디버그 출력은 stderr로 보내야 한다.
// 안 됨 — 프로토콜 채널을 오염시킨다
console.log("서버 시작됨");
// 이렇게 — stderr는 안전하다
console.error("서버 시작됨");
stdio는 네트워크가 필요 없고, 클라이언트와 서버가 같은 기계에서 돈다. Claude Desktop 같은 로컬 앱이 내 컴퓨터의 파일이나 도구에 붙을 때 쓰는 방식이 바로 이것이다. 설정도 단순하다. “이 명령어를 실행해라” 한 줄이면 된다.
HTTP — 원격 서버로 연결
HTTP transport는 서버가 독립적으로 떠 있고, 클라이언트가 네트워크 너머로 요청을 보낸다. 서버를 한 번 배포해두면 여러 사용자가 각자의 클라이언트로 접속할 수 있다.
- 서버를 클라우드에 올려 팀 전체가 공유하고 싶을 때
- 서버가 무거운 리소스(DB, 사내 API)에 붙어 있어 한 곳에서 관리하고 싶을 때
- 인증·권한 제어를 서버 쪽에 두고 싶을 때
대신 stdio에는 없던 고민이 따라온다. 누가 접근할 수 있는지(인증), 어떻게 배포하고 살아있는지 확인할지(운영)를 직접 챙겨야 한다.
무엇을 고를까
| stdio | HTTP | |
|---|---|---|
| 실행 위치 | 같은 기계, 자식 프로세스 | 원격, 독립 서버 |
| 설정 | 명령어 한 줄 | 배포 + 인증 |
| 적합한 경우 | 로컬 도구, 개인용 | 공유 서버, 팀·서비스 |
처음 MCP 서버를 만든다면 stdio로 시작하는 것을 권한다. 배포나 인증 없이 로직에만 집중할 수 있고, 로컬에서 곧바로 붙여 테스트할 수 있다. 서버가 쓸 만해지고 여럿이 공유할 필요가 생기면 그때 HTTP로 옮기면 된다. 메시지 형식은 그대로이니 옮기는 비용도 크지 않다.
이 글에 대한 의견은 아래 댓글로 남겨주세요 (GitHub 계정 필요). 로그인 없이 남기고 싶다면