구독·알림 패턴

구독·알림 패턴 (Subscriptions)

subscriptions/listen은 서버에서 클라이언트로 가는 오래 지속되는 알림 스트림을 엽니다. 일회성 요청과 달리, 이 스트림은 클라이언트가 취소할 때까지 열린 채 알림을 전달해요. 기존의 resources/subscribe RPC와 HTTP GET 엔드포인트를 대체합니다.

출처: MCP 공식 문서 — Subscriptions

스트림 열기 (Opening a Stream)

클라이언트는 어떤 이벤트 유형을 받고 싶은지 지정하는 notifications 필터와 함께 subscriptions/listen 요청을 보냅니다. 서버는 클라이언트가 명시적으로 요청하지 않은 알림 유형을 보내면 안 됩니다(MUST NOT).

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "subscriptions/listen",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {
        "name": "ExampleClient",
        "version": "1.0.0"
      },
      "io.modelcontextprotocol/clientCapabilities": {}
    },
    "notifications": {
      "toolsListChanged": true,
      "resourceSubscriptions": ["file:///project/config.json"]
    }
  }
}

알림 필터 (Notification Filter)

필드 타입 설명
toolsListChanged boolean 도구가 바뀌면 notifications/tools/list_changed 수신
promptsListChanged boolean 프롬프트가 바뀌면 notifications/prompts/list_changed 수신
resourcesListChanged boolean 목록이 바뀌면 notifications/resources/list_changed 수신
resourceSubscriptions string[] 이 리소스 URI들에 대해 notifications/resources/updated 수신

모든 필드는 선택입니다. 필드를 생략하는 것은 그 알림 유형을 구독하지 않는 것과 같아요.

확인 (Acknowledgment)

서버는 스트림의 첫 메시지로 notifications/subscriptions/acknowledged보내야 하며(MUST), _metaio.modelcontextprotocol/subscriptionId 아래에 구독의 ID를 실어야 합니다. 그 전에는 구독에서 어떤 알림도 보내면 안 됩니다(MUST NOT). stdio처럼 모든 구독이 하나의 채널을 공유하는 곳에서는 이 순서가 채널 단위가 아니라 구독 ID 단위로 정의됩니다. 다른 구독에 속한 메시지가 그 전에 섞여 들어올 수 있어요(MAY).

확인 메시지의 notifications 필드는 서버가 이행하기로 동의한 부분집합을 반영합니다. 서버가 지원하지 않는 알림 유형은 생략돼요.

{
  "jsonrpc": "2.0",
  "method": "notifications/subscriptions/acknowledged",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/subscriptionId": 1
    },
    "notifications": {
      "toolsListChanged": true,
      "resourceSubscriptions": ["file:///project/config.json"]
    }
  }
}

클라이언트는 요청한 필터와 확인된 필터를 비교해, 지원되지 않는 유형을 우아하게 처리해야 합니다(SHOULD).

알림 받기 (Receiving Notifications)

스트림에서 전달되는 모든 알림은 _metaio.modelcontextprotocol/subscriptionId를 실어, 스트림을 연 subscriptions/listen 요청을 식별합니다. 그 값은 subscriptions/listen 요청의 JSON-RPC ID예요. 위 예에서 요청은 "id": 1을 썼으므로, 확인과 이후 모든 알림은 구독 ID 1을 실어 나릅니다. stdio처럼 모든 메시지가 단일 채널을 공유하는 곳에서 클라이언트는 이 필드로 알림을 발원 구독과 대응해야 합니다(MUST).

{
  "jsonrpc": "2.0",
  "method": "notifications/resources/updated",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/subscriptionId": 1
    },
    "uri": "file:///project/config.json"
  }
}

동시 다중 구독 (Multiple Concurrent Subscriptions)

클라이언트는 여러 활성 구독을 동시에 가질 수 있어요(MAY) — 예를 들어 하나는 도구 목록 변경을, 다른 하나는 리소스 갱신을 들을 수 있죠. 각 구독은 그 subscriptions/listen 요청의 JSON-RPC 요청 ID로 식별되고, 스트림의 모든 알림은 그 ID를 io.modelcontextprotocol/subscriptionId에 실어 나르므로 클라이언트가 역다중화(demultiplex)할 수 있어요.

취소 (Cancellation)

구독은 다음 경우에 끝납니다:

  • 클라이언트가 취소 — SSE 스트림을 닫거나(HTTP), subscriptions/listen 요청 ID를 참조하는 notifications/cancelled를 보냄(stdio).
  • 서버가 내림(예: 종료 동안) — 우아한 종료를 알리기 위해 성공적인 subscriptions/listen 응답을 보낸 뒤(SHOULD) 스트림을 닫습니다.
  • 밑의 전송이 닫힘(HTTP 타임아웃, TCP 연결 해제, stdio 프로세스 종료).

우아한 종료 (Graceful Closure)

서버가 자기 주도로 구독을 끝낼 때(예: 종료 동안) 원래 subscriptions/listen 요청에 완료 결과로 응답한 다음 스트림을 닫는 것이 좋습니다(SHOULD). 결과는 표준 결과 필드와 구독 메타데이터 외에 메서드별 데이터를 갖지 않아요. 이는 오래 지속되는 요청에 대한 JSON-RPC 응답으로, 그 id로 대응되며, 구독이 우아하게 끝났음을 알립니다 — 응답을 동반하지 않는 갑작스러운 전송 드롭과 대비되죠.

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "complete",
    "_meta": {
      "io.modelcontextprotocol/subscriptionId": 1
    }
  }
}

스트림의 다른 모든 메시지처럼 응답도 _metaio.modelcontextprotocol/subscriptionId를 실어, 어느 구독을 닫는지 식별합니다. 값은 발원 subscriptions/listen 요청의 JSON-RPC id와 일치해요.

이 응답을 받은 클라이언트는 구독이 깨끗하게 닫혔음을 압니다. 응답 없이 전송이 닫히면 예상치 못한 연결 해제를 뜻하며, 클라이언트는 이를 재연결 트리거로 취급할 수 있어요(MAY).

stdio에서는 연결이 종료된 뒤 다시 수립되면 클라이언트가 구독을 다시 수립하기 위해 subscriptions/listen다시 보내야 합니다(MUST) — 서버는 재연결을 가로질러 구독 상태를 보관하지 않아요.

전체 규칙은 취소 패턴을 참고하세요.

더 알아보기 (Learn more)