구독·알림 패턴
구독·알림 패턴 (Subscriptions)
subscriptions/listen은 서버에서 클라이언트로 가는 오래 지속되는 알림 스트림을 엽니다. 일회성 요청과 달리, 이 스트림은 클라이언트가 취소할 때까지 열린 채 알림을 전달해요. 기존의 resources/subscribe RPC와 HTTP GET 엔드포인트를 대체합니다.
스트림 열기 (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), _meta의 io.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)
스트림에서 전달되는 모든 알림은 _meta에 io.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
}
}
}
스트림의 다른 모든 메시지처럼 응답도 _meta에 io.modelcontextprotocol/subscriptionId를 실어, 어느 구독을 닫는지 식별합니다. 값은 발원 subscriptions/listen 요청의 JSON-RPC id와 일치해요.
이 응답을 받은 클라이언트는 구독이 깨끗하게 닫혔음을 압니다. 응답 없이 전송이 닫히면 예상치 못한 연결 해제를 뜻하며, 클라이언트는 이를 재연결 트리거로 취급할 수 있어요(MAY).
stdio에서는 연결이 종료된 뒤 다시 수립되면 클라이언트가 구독을 다시 수립하기 위해 subscriptions/listen을 다시 보내야 합니다(MUST) — 서버는 재연결을 가로질러 구독 상태를 보관하지 않아요.
전체 규칙은 취소 패턴을 참고하세요.
더 알아보기 (Learn more)
- 메시지 패턴 개요 — 구독·알림이 얹히는 프레임
- 취소 패턴 — 구독 스트림 종료
- 전송 계층 개요 — listen 스트림을 실어 나르는 전송
- Streamable HTTP 전송 — listen 응답 스트림의 SSE 동작