1. MCP 전송 기술의 배경

MCP는 JSON-RPC 2.0을 통신 포맷으로 사용하며, 이 메시지를 효율적으로 주고받기 위한 전송 계층(transport mechanism)이 필요합니다. 단순한 HTTP 요청-응답 모델은 지연(latency)과 연결 비용이 크기 때문에, 지속적이고 실시간 성격의 AI 응답 처리에는 적합하지 않습니다.

그래서 MCP는 AI 시스템 간 통신을 위한 프로토콜로, 본래 HTTP+SSE(Server Sent Events) 기반으로 서버에서 클라이언트로의 스트리밍 통신을 구현했습니다. 그러나 2025년 3월 26일 버전부터는 HTTP+SSE를 폐지하고 Streamable HTTP로 전환하였습니다.

이 글에서는, 전송 계층(transport mechanism)에 대해 알아보기 전에 JSON-RPC 2.0 에 대해서 알아보도록 하겠습니다

2. JSON-RPC 2.0 란?

JSON-RPC 2.0은 JSON 기반의 원격 프로시저 호출(Remote Procedure Call, RPC) 프로토콜(규칙과 약속)로, 클라이언트와 서버가 JSON 형식의 메시지를 주고받아 메서드(함수)를 호출하는 방식입니다. 쉽게 말해, 한 시스템이 네트워크를 통해 다른 시스템의 함수를 마치 로컬 함수처럼 호출할 수 있게 해주는 규약입니다.

2009년에 표준화된 이 프로토콜은 필요 최소한의 규칙으로도 원하는 기능을 구현할 수 있고, stateless(상태를 유지하지 않음)·구조 설계에서 오는 부담을 최소화 해서 가볍고, HTTP나 WebSocket 같은 다양한 전송 계층 위에서 동작할 수 있습니다. JSON-RPC의 핵심 철학은 단순함입니다. REST API처럼 리소스 중심이 아닌, 액션(메서드) 중심으로 설계되어 있어 "이 작업을 수행해줘"라는 요청을 직관적으로 표현할 수 있습니다.

2.1. 단순성

  • JSON-RPC 2.0은 응답에서 성공은 result 필드로, 실패는 error 필드로만 표현합니다.

  • 메시지 구조가 최소화되어 있어 복잡한 규칙 없이 구현이 가능합니다.

    • 필수 필드인 "jsonrpc", "method", "id"에 선택적인 "params"만 있으면 호출 가능
  • REST처럼 "리소스 중심"이 아니라, 메서드 중심(액션 중심) 설계이므로, 호출 목적이 명확합니다.

// 성공 응답
{"jsonrpc":"2.0","result":42,"id":1}

// 실패 응답
{"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":1}

REST에서는 /users/1 GET, POST, PUT 등 HTTP 메서드로 행동을 구분하지만,
JSON-RPC에서는 "method": "user.create"처럼 호출 동작 자체를 명시합니다.

2.2. Transport 독립성(Transport Independence)

  • JSON-RPC 2.0은 데이터 구조만 규정하고, 전송 프로토콜에는 종속되지 않습니다. 즉, HTTP, WebSocket, TCP, IPC(프로세스 간 통신) 등 JSON 문자열을 전달할 수 있는 채널이면 모두 가능합니다.

  • 동일한 메시지 포맷을 여러 통신 방식에 재사용할 수 있습니다.

  • HTTP POST

POST /rpc HTTP/1.1
Host: example.com
Content-Type: application/json

{"jsonrpc":"2.0","method":"math.add","params":[5,7],"id":1}
  • WebSocket
// 연결 후 바로 전송
{"jsonrpc":"2.0","method":"math.add","params":[5,7],"id":1}

두 경우 모두 동일한 JSON 구조를 사용하므로, 서버 로직은 전송 계층에 의존하지 않습니다.

2.3. Stateless(상태 비저장)

  • 각 호출은 독립적이며, 서버는 이전 요청의 상태를 기억하지 않습니다.

  • 클라이언트가 매 요청마다 필요한 모든 정보를 전달해야 합니다.

  • 이 방식은 수평 확장, 장애 격리, 캐시 프락시 활용에 유리합니다.

    • 수평 확장에 유리 : 서버 간 상태 동기화가 필요 없으므로, 부하 분산(Load Balancing) 을 쉽게 적용 가능. 어떤 서버가 요청을 받아도 동일한 결과를 반환할 수 있음.

    • 장애 격리 : 특정 서버 인스턴스가 장애가 나더라도, 다른 인스턴스가 요청을 동일하게 처리할 수 있음. 세션 데이터를 메모리에 저장하는 구조(Session Sticky)보다 안정적.

    • 캐시 활용 가능성 높음 : 동일한 요청은 동일한 응답을 반환하므로, HTTP 캐시나 Reverse Proxy(Cache Layer) 를 쉽게 적용 가능.

  • 사용 예시

{"jsonrpc":"2.0","method":"order.get","params":{"orderId":"ORD-1001"},"id":1}

서버는 ORD-1001에 대한 정보만 조회해서 반환하고, 이전 호출의 컨텍스트나 세션 상태를 참조하지 않습니다. 만약 추가 정보가 필요하다면 다음 요청에 다시 포함해야 합니다.

2.4. 효율성(Efficiency: Batch & Notification)

1. 배치 요청(Batch Request)

  • 여러 요청을 배열로 묶어서 한 번에 전송하면 네트워크 왕복 횟수를 줄일 수 있습니다.

  • 응답은 배열 형태로 반환되며, 요청 순서와 상관없이 id로 매칭합니다.

[
  {"jsonrpc":"2.0","method":"math.add","params":[1,2],"id":"a"},
  {"jsonrpc":"2.0","method":"math.multiply","params":[3,4],"id":"b"}
]

[
  {"jsonrpc":"2.0","result":3,"id":"a"},
  {"jsonrpc":"2.0","result":12,"id":"b"}
]

2. Notification

  • id 필드를 생략하면 서버는 응답을 보내지 않습니다.

    • WebSocket, TCP 같은 전송 방식에서는 Notification에 대해 아무 메시지도 보내지 않는 것이 가능. 즉, 연결만 유지하고, 해당 호출에 대한 응답 패킷을 보내지 않음.

    • HTTP의 경우 “아예 생략”이란 게 불가능하니, 대신 204 같은 빈 응답을 보냄.

  • 결과가 필요 없는 작업(로그 적재, 이벤트 기록 등)에 적합합니다.

{"jsonrpc":"2.0","method":"logEvent","params":{"event":"user_login"}}

JSON-RPC 2.0에서 Notification은 id 필드를 생략해 "응답을 보내지 않는다"는 의미를 가집니다. 하지만 HTTP 위에서 동작할 때는, 서버가 아무 응답도 안 보내고 연결을 끊을 수는 없습니다. HTTP는 요청을 받으면 반드시 응답 상태줄과 헤더를 보내야 요청-응답 사이클이 완성됩니다. HTTP에서는 보통 204 No Content로 처리하거나 응답을 아예 생략합니다.

3. 메시지 형식의 세부 규칙

1. 요청(Request)

{
  "jsonrpc": "2.0",
  "method": "math.add",
  "params": [1, 2],
  "id": 1
}
  • jsonrpc: 항상 "2.0".
  • method: 문자열, 호출할 메서드 이름.
  • params: 선택적, 배열 또는 객체.
  • id: 응답을 매칭하기 위한 식별자. 없으면 Notification.

2. 응답(Response: 성공)

{
  "jsonrpc": "2.0",
  "result": 3,
  "id": 1
}

3. 응답(Response: 실패)

{
  "jsonrpc": "2.0",
  "error": { "code": -32601, "message": "Method not found" },
  "id": 1
}

4. 표준 에러 코드

1. -32700 Parse error

  • 의미: 클라이언트가 보낸 JSON 문자열이 유효하지 않아 파싱에 실패했을 때 발생합니다.

  • 상황 예시:

{"jsonrpc": "2.0", "method": "sum", "params": [1,2], "id": 1

(중괄호 닫힘 누락 등 구문 오류)

2. -32600 Invalid Request

  • 의미: 요청 자체의 형식이 JSON-RPC 2.0 스펙에 맞지 않을 때 발생합니다.

  • 주요 원인:

    • jsonrpc 필드가 "2.0"이 아님
    • method가 문자열이 아님
    • id 타입이 유효하지 않음(null, string, number만 허용)
  • 예시:

{"method": 123, "id": "abc"}  // method는 반드시 문자열이어야 함

3. -32601 Method not found

  • 의미: 호출하려는 메서드가 서버에 등록되지 않은 경우.
  • 예시: 클라이언트가 "subtract"를 요청했는데 서버는 "sum"만 지원할 때.

4. -32602 Invalid params

  • 의미: 전달된 파라미터(params)가 잘못되었을 때 발생합니다.

  • 주요 원인:

    • 필요한 매개변수가 빠짐
    • 타입 불일치 (예: age는 number여야 하는데 string 전달)
  • 예시:

{"jsonrpc": "2.0", "method": "sum", "params": {"a":1}, "id":1}

(서버가 a와 b 둘 다 필요로 하는 경우)

5. -32603 Internal error

  • 의미: 서버 내부에서 처리 중 알 수 없는 오류가 발생했을 때.
  • 용도: 디버깅이 필요하며, 보안상 클라이언트에게 내부 스택트레이스를 그대로 전달하지 않고 이 코드로 일반화해서 응답하는 것이 권장됩니다.

6. -32000 ~ -32099 Server error (Custom)

  • 의미: JSON-RPC에서 표준화하지 않은, 서버 애플리케이션 특화 에러를 정의할 수 있는 범위입니다.

  • 활용 예시:

-32000: 데이터베이스 연결 오류
-32001: 인증 실패
-32002: 권한 부족
-32010: 특정 비즈니스 로직 오류 (예: 배송 불가 지역)

7. 요약 정리

코드의미설명
-32700Parse errorJSON 파싱 실패
-32600Invalid Request형식이 올바르지 않음
-32601Method not found메서드 미등록
-32602Invalid params파라미터 오류
-32603Internal error서버 내부 오류
-32000~-32099Server error커스텀 서버 에러 범위

5. reference

https://www.jsonrpc.org/specification

profile
가장 아름다운 정답은 서로의 협업안에 있다.

0개의 댓글