ID001 Dev Blog
아티클 7 분 소요

MCP 완전 정복 2편 — JSON-RPC 2.0과 직렬화

mcp json-rpc protocol serialization

JSON-RPC 2.0이란

JSON-RPC는 “JSON으로 포장한 원격 함수 호출(Remote Procedure Call) 규약” 이다. 다른 프로세스나 서버에 있는 함수를 마치 로컬에서 호출하듯 쓸 수 있게 해주는 약속이다.

“2.0”은 버전을 뜻하며, 2010년에 확정된 스펙이다. 모든 메시지에 "jsonrpc": "2.0" 필드를 반드시 포함해야 한다.

메시지 세 가지 타입

JSON-RPC 2.0의 메시지는 단 세 종류뿐이다.

1. Request — 응답을 기대하는 요청

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "jira_search",
  "params": {
    "jql": "project = BIO AND status = 'In Progress'"
  }
}

id 필드가 핵심이다. 여러 요청을 동시에 날려도 응답이 뒤섞이지 않고 id로 짝을 찾을 수 있다.

2. Response — Request에 대한 응답

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "issues": [{ "key": "BIO-42", "summary": "OpenSearch 동의어 사전 최적화" }]
  }
}

id가 Request와 동일해야 한다. 오류 발생 시에는 result 대신 error 필드가 온다.

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32600,
    "message": "Invalid Request"
  }
}

3. Notification — 응답이 필요 없는 단방향 전송

{
  "jsonrpc": "2.0",
  "method": "progress",
  "params": {
    "value": 42,
    "message": "Jira API 호출 중..."
  }
}

id가 없다. 서버가 진행 상황을 클라이언트에 푸시할 때 사용한다.

JSON-RPC를 선택한 이유

다른 프로토콜도 있는데 JSON-RPC를 고른 이유가 있다.

프로토콜장점MCP에 부적합한 이유
gRPC성능, 타입 안전성.proto 스키마 + 빌드 파이프라인 필요 → 서버 구현 진입장벽 높음
REST범용성동사(GET/POST/PUT)가 리소스 중심 → “함수 호출” 개념에 부자연스러움
JSON-RPC단순성method + params만 정의하면 끝 → 언어/런타임 무관하게 구현 가능

JSON-RPC는 MCP Server를 Python, TypeScript, Kotlin 등 어떤 언어로도 낮은 진입장벽으로 구현할 수 있게 해준다. Anthropic이 생태계 확장을 우선순위에 두었기 때문에 선택한 트레이드오프다.

직렬화(Serialization)란

직렬화는 메모리 안에 있는 프로그램 객체를 전송 가능한 바이트 스트림(텍스트)으로 변환하는 과정이다. 반대는 역직렬화(Deserialization)다.

MCP에서 실제로 일어나는 과정을 따라가 보면 이렇다.

1단계 — LLM이 tool_use 블록 생성

LLM(Claude)이 응답 생성 시 Jira 검색이 필요하다고 판단하면, 메모리 내부에 구조화된 객체를 만든다.

ToolUseBlock {
  id: "toolu_abc123",
  name: "jira_search",
  input: { jql: "project = BIO AND status = 'In Progress'" }
}

이 객체는 아직 Python/TypeScript의 메모리 안에 있는 데이터 구조다. 다른 프로세스로 보낼 수 없다.

2단계 — JSON 직렬화 (Host가 수행)

Host가 이 객체를 JSON 텍스트로 변환한다. 이것이 직렬화다.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "jira_search",
  "params": {
    "jql": "project = BIO AND status = 'In Progress'"
  }
}

이제 네트워크(또는 stdio)로 전송할 수 있는 텍스트가 됐다.

3단계 — 전송

stdio라면 표준 입력으로, SSE/HTTP라면 HTTP body로 텍스트가 전달된다.

4단계 — 역직렬화 (MCP Server가 수행)

MCP Server(예: Atlassian 서버, Python으로 작성)가 받은 JSON 텍스트를 자기 언어의 객체로 복원한다.

# Python MCP Server 내부
request = {
    "method": "jira_search",
    "params": {"jql": "project = BIO AND status = 'In Progress'"}
}
# 실제 Jira API 호출
issues = jira_client.search_issues(request["params"]["jql"])

5단계 — 결과를 다시 직렬화해서 반환

서버가 Jira API에서 받은 결과를 다시 JSON으로 직렬화해 Host에 보내면, Host가 역직렬화해 LLM에 tool_result로 전달한다.

정리

JSON-RPC 2.0은 Request / Response / Notification 세 가지 메시지 타입으로 구성된 단순한 원격 함수 호출 규약이다. MCP가 gRPC나 REST 대신 JSON-RPC를 선택한 것은 낮은 진입장벽으로 생태계를 빠르게 확장하기 위한 전략적 선택이었다.

직렬화는 이 메시지들을 실제로 전송 가능한 형태로 변환하는 과정이며, MCP의 모든 통신은 이 변환을 통해 언어 무관하게 동작한다.

다음 편에서는 이 메시지들이 실제로 어떤 전송 계층을 타고 이동하는지, SSE에서 Streamable HTTP로 전환한 이유를 설명한다.

MCP 완전 정복 3편 — SSE에서 Streamable HTTP로

SSE의 한계와 Streamable HTTP가 해결하는 것들, MCP 스펙 변경 배경을 다룹니다.