Model Context Protocol(MCP)은 AI 모델을 외부 도구 및 데이터 소스와 연결하는 표준 방식으로 빠르게 자리 잡고 있습니다. MCP 채택이 확산되면서 개발자들은 견고하고 프로덕션 수준의 MCP 구현체를 만들기 위해서는 단순히 스펙을 따르는 것만으로는 부족하다는 사실을 깨닫고 있습니다. 올바른 인프라가 필요한 것이죠. 이 글에서는 Redis가 어떻게 세 가지 서로 다른 MCP 활용 사례를 지원하는지 살펴보겠습니다. 바로 Vercel의 SSE 구현에서 분산형 서버리스 함수 조율, 장시간 실행되는 스트림을 위한 이벤트 재개(resumability) 기능, 그리고 Clerk과 함께하는 안전한 OAuth 플로우 관리입니다.
MCP 트랜스포트(Transport) 이해하기
예제를 살펴보기 전에 MCP가 통신을 처리하는 방식을 간단히 짚어보겠습니다. MCP는 두 가지 트랜스포트를 지원합니다. 로컬 서버용 Stdio와 원격 서버용 Streamable HTTP입니다. 과거에는 원격 서버를 위한 SSE(Server-Sent Events) 트랜스포트도 있었지만, 현재는 더 이상 사용되지 않는(deprecated) 상태입니다.
SSE에서 Streamable HTTP로의 전환은 MCP가 실시간 통신을 처리하는 방식의 진화를 보여주지만, 여전히 많은 기존 구현체들이 SSE 패턴에 의존하고 있습니다. 그럼 Redis가 해당 모델의 핵심 과제 중 하나를 어떻게 해결했는지 살펴보겠습니다.
활용 사례 1: Redis Pub/Sub을 활용한 SSE
Vercel의 CTO Malte가 MCP 핸들러를 구축할 때 마주한 것은 서버리스 환경의 고전적인 문제였습니다. 각 요청이 서로 다른 서버리스 함수에 의해 처리될 수 있는데, 여러 엔드포인트 간에는 어떻게 협력을 조율할 수 있을까요?
SSE 트랜스포트 모델에는 두 가지 중요한 엔드포인트가 있습니다. 연결을 유지하는 /sse와 클라이언트 메시지를 수신하는 /message입니다. 문제는 다음과 같습니다. Vercel 같은 서버리스 환경에서는 이 엔드포인트들이 완전히 격리되어 있다는 점입니다. 메모리를 공유하지 않으며, /message 요청은 /sse를 처리하는 함수 인스턴스와 완전히 다른 인스턴스에서 처리될 수 있습니다.
해결책은 무엇일까요? 바로 Redis Pub/Sub입니다. Malte가 X(구 Twitter)에서 설명한 내용은 다음과 같습니다.

이 구현은 Redis 채널을 사용해 엔드포인트 간 조율을 수행합니다. 클라이언트가 /message로 메시지를 보내면 해당 엔드포인트는 /sse 엔드포인트가 구독 중인 Redis 채널에 메시지를 발행(publish)합니다. 그러면 /sse 엔드포인트가 요청을 처리하고, 응답을 /message가 수신 대기 중인 다른 채널을 통해 다시 발행합니다.

이 패턴은 Redis를 하나의 메시지 버스로 변환하여 격리된 서버리스 함수들 사이의 간극을 메우고, 마치 동일한 프로세스의 일부처럼 함께 작동하도록 만듭니다. SSE는 현재 Streamable HTTP로 대체되어 deprecated 상태이므로(즉, 새로운 MCP 구현체는 최신 트랜스포트를 사용해야 함), 이 예제는 여전히 격리된 서버리스 함수 간 조율에 Redis를 활용하는 훌륭한 사례로 남아 있습니다.
활용 사례 2: 재개성(Resumability)을 위한 이벤트 스토어
MCP의 Streamable HTTP 트랜스포트가 제공하는 가장 강력한 기능 중 하나는 재개성(resumability) 지원입니다. 이 기능을 사용하면 클라이언트가 연결이 끊긴 경우 스트림을 중단된 지점부터 이어서 계속할 수 있습니다. 네트워크 중단이 불가피한 프로덕션 애플리케이션에서 매우 중요한 기능입니다.
MCP 스트림의 이해
재개성이 왜 중요한지 이해하려면 먼저 MCP 스트림이 무엇으로 구성되어 있는지 알아야 합니다. 간단한 도구 호출 같은 일부 작업은 단일 응답을 반환하지만, 도구는 server.sendLoggingMessage 같은 메서드를 통해 실행 중 여러 이벤트를 반환할 수도 있습니다. 바로 이런 다중 이벤트 스트림에서 재개성이 결정적으로 중요해집니다. 클라이언트가 도중에 연결이 끊기더라도 처음부터 다시 시작하는 것이 아니라 중단된 지점부터 이어갈 수 있어야 하기 때문입니다.
Redis로 EventStore 구현하기
MCP SDK는 두 가지 메서드를 요구하는 EventStore 인터페이스를 정의합니다. 이벤트를 추가하는 storeEvent와 특정 이벤트 ID부터 시작하는 이벤트를 조회하는 replayEventsAfter입니다. SDK에는 인메모리 구현체가 포함되어 있지만, 프로덕션 환경에서는 영구 저장소가 필요합니다.
다음은 Redis 기반 EventStore 구현 예시입니다:
import { Redis } from '@upstash/redis';
import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
import { EventStore } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
export class RedisEventStore implements EventStore {
private redis: Redis;
constructor(params: ConstructorParameters<typeof Redis>[0]) {
this.redis = new Redis(params);
}
/**
* Stores an event in a Redis Stream
* Implements EventStore.storeEvent
*/
async storeEvent(streamId: string, message: JSONRPCMessage): Promise<string> {
const eventId = await this.redis.xadd(`stream:${streamId}`, '*', {
message: JSON.stringify(message),
});
return eventId;
}
/**
* Replays events that occurred after a specific event ID
* Implements EventStore.replayEventsAfter
*/
async replayEventsAfter(
lastEventId: string,
{ send }: { send: (eventId: string, message: JSONRPCMessage) => Promise<void> }
): Promise<string> {
if (!lastEventId) {
return '';
}
// Extract the stream ID from the lastEventId
const streamId = lastEventId.split('-')[0]; // Assuming the stream ID is part of the key
if (!streamId) {
return '';
}
let nextId = lastEventId;
while (true) {
// Fetch events from the stream starting AFTER the next ID (exclusive)
const events = await this.redis.xrange(`stream:${streamId}`, `(${nextId}`, '+', 10);
// Convert the returned object to an array of entries
const eventEntries = Object.entries(events);
if (eventEntries.length === 0) {
break; // No more events to replay
}
for (const [eventId, fields] of eventEntries) {
// Ensure fields.message exists and parse it
if (fields && typeof fields === 'object' && 'message' in fields && typeof fields.message === 'string') {
const message = JSON.parse(fields.message) as JSONRPCMessage;
await send(eventId, message);
nextId = eventId; // Update the next ID to the current event ID
}
}
}
return streamId;
}
}
이 구현은 Redis Streams를 사용하며, 이는 해당 사용 사례에 딱 맞는 기능입니다.
알아두어야 할 한계점
한 가지 주의해야 할 중요한 사항이 있습니다. 도구가 여러 이벤트를 전송할 때 streamId는 _GET_stream으로 설정되며, 이 상수 값은 서로 다른 스트림과 사용자 간에 동일하게 유지됩니다. 즉, 두 명의 사용자가 동시에 여러 이벤트를 전송하는 동일한 도구를 사용하면 그들의 이벤트가 같은 스트림에서 섞일 수 있습니다.
필자는 이 문제에 대해 공식 저장소에 이슈를 등록했습니다. 해결 방향에 따라 구현을 조정해야 할 수도 있으며, 필요한 경우 이 글도 그에 맞춰 업데이트할 예정입니다.
활용 사례 3: Clerk의 MCP OAuth 구현
MCP 스펙에는 인증 및 권한 부여에 대한 지원이 포함되어 있어, MCP 서버가 클라이언트를 안전하게 인증하고 리소스 접근을 제어할 수 있습니다. 이 스펙은 OAuth 2.0을 포함한 여러 인증 방식을 지원하며, 특히 OAuth 2.0은 기존 아이덴티티 프로바이더와의 통합과 사용자 범위(user-scoped)의 도구 및 데이터 접근 허용에 매우 유용합니다.
Clerk은 MCP SDK의 OAuthClientProvider 인터페이스를 구현하여 MCP를 위한 포괄적인 OAuth 클라이언트를 구축했습니다. 그들의 구현은 MCP 애플리케이션에서 Redis의 또 다른 핵심 활용 사례를 보여줍니다. Clerk의 MCP 도구를 시작하고 싶다면 훌륭한 출발점이 되는 데모 저장소를 확인해 보세요.
OAuth에 저장소가 중요한 이유
Clerk이 mcp-tools 저장소에서 설명하듯이, MCP OAuth 구현에는 두 가지 핵심 이유로 영구 저장소가 필수적입니다:
- OAuth 플로우가 여러 엔드포인트에 걸쳐 진행됨 - 초기화, OAuth 콜백, MCP 요청이 모두 공유 메모리 없이 서로 다른 서버리스 함수에 의해 처리될 수 있습니다.
- MCP 연결은 장시간 지속됨 - 인메모리 저장소에 의존하면 애플리케이션이 확장될수록 메모리 요구량이 급증하고, 서버 재시작 시 모든 세션이 무효화됩니다.
Redis에 저장되는 데이터
Clerk의 Redis 스토어는 OAuth 플로우에서 각각 고유한 목적을 가진 세 가지 유형의 데이터를 관리합니다:
PKCE Verifier (pkce_verifier_<...>)
OAuth 플로우를 시작할 때 저장되고, 사용자가 권한을 부여한 후 다시 읽혀집니다. PKCE(Proof Key for Code Exchange) 플로우의 일부로, 플로우를 시작한 애플리케이션과 완료하는 애플리케이션이 동일함을 보장함으로써(RFC 7636에 정의됨) 퍼블릭 클라이언트의 OAuth에 추가 보안 계층을 제공합니다.
{
"value": "XoYQ...",
"created_at": "2025-10-03T06:27:06.928Z",
"updated_at": "2025-10-03T06:27:06.928Z"
}
세션 데이터 (session_<...>)
MCP 세션의 모든 구성 정보와 상태를 저장합니다. 초기에는 MCP 엔드포인트, OAuth 구성, 클라이언트 자격 증명이 포함됩니다. 사용자가 권한을 부여하면 액세스 토큰과 리프레시 토큰으로 업데이트되며, 마지막으로 플로우가 완료되면 authComplete 플래그가 추가됩니다.
{
"value": {
"mcpEndpoint": "https://localhost:3001/mcp",
"oauthRedirectUrl": "https://localhost:3000/oauth_callback",
"oauthScopes": "openid profile email",
"mcpClientName": "Clerk MCP Demo",
"mcpClientVersion": "0.0.1",
"oauthClientUri": "https://example.com",
"oauthPublicClient": false,
"clientId": "8Yb2...",
"clientSecret": "64YG..."
},
"created_at": "2025-10-03T06:27:06.882Z",
"updated_at": "2025-10-03T06:27:06.882Z"
}
State 파라미터 (state_<...>)
OAuth state 파라미터를 세션 ID에 매핑하여, state ID만으로 세션 데이터를 조회할 수 있게 해줍니다:
{
"value": "dX61...",
"created_at": "2025-10-03T06:27:03.585Z",
"updated_at": "2025-10-03T06:27:03.585Z"
}
이러한 아키텍처 덕분에 OAuth 플로우가 보안과 세션 무결성을 유지하면서도 분산된 서버리스 함수 전반에서 원활하게 작동할 수 있습니다.
MCP에 Redis가 적합한 이유
세 가지 예제 모두에서 Redis를 MCP 인프라의 이상적인 선택으로 만드는 공통 패턴을 확인할 수 있습니다:
낮은 지연 시간: MCP 작업은 빠른 속도가 요구되는 경우가 많습니다. Redis의 인메모리 아키텍처는 실시간 AI 상호작용에 필요한 응답 속도를 제공합니다.
Pub/Sub 기능: Vercel의 구현에서 확인했듯이, Redis Pub/Sub는 전체 메시지 큐의 복잡성 없이 분산 컴포넌트 간 우아한 조율을 가능하게 합니다.
풍부한 데이터 구조: 이벤트 스트림에는 Streams, 세션 데이터에는 Hashes, 상태에는 단순 키-값 쌍까지. Redis는 각 사용 사례에 맞는 적절한 데이터 구조를 제공합니다.
내장형 만료 기능: TTL을 통한 자동 정리 기능입니다. OAuth 토큰, 이벤트 스트림, 세션 데이터 모두 수동 가비지 컬렉션 없이 자동으로 만료될 수 있습니다.
서버리스 친화성: Upstash Redis 같은 솔루션을 사용하면 사용하지 않을 때 스케일을 제로로 줄이는 완전 관리형 서버리스 Redis를 이용할 수 있습니다. 가변적인 워크로드를 가질 수 있는 MCP 구현에 완벽합니다.
MCP 스펙을 넘어선 활용: 더 넓은 AI 생태계 속의 Redis
지금까지 MCP 스펙에 명시적으로 정의된 기능들을 중심으로 살펴보았지만, AI를 지원하는 측면에서 Redis의 활용 범위는 우리가 탐구한 패턴들을 훨씬 뛰어넘습니다. 최근 블로그 게시물에서 몇 가지 패턴을 소개한 바 있습니다:
- AI SDK 통합 - Redis를 활용해 Vercel AI SDK를 강화
- 채팅 기록 - 메시징 히스토리 영구 저장
자체 예제 외에도 커뮤니티에서는 AI 애플리케이션을 위한 인상적인 Redis 기반 도구들을 만들어 왔습니다. Midday의 ai-sdk-tools 제품군에는 Redis를 사용해 AI SDK 도구 결과를 캐싱하는 도구 결과 캐싱 패키지가 포함되어 있으며, 아래 예시에서 볼 수 있듯이 성능을 크게 향상시키고 비용을 절감합니다:

MCP 서버, AI 에이전트, 또는 완전한 AI 애플리케이션을 구축하든, Redis는 시스템을 프로덕션 준비 상태로 만들고 확장 가능하며 고성능으로 이끄는 인프라 계층을 제공합니다.
마무리
Model Context Protocol은 아직 역사가 짧지만, AI 애플리케이션의 필수 인프라로 빠르게 자리 잡고 있습니다. 이번 세 가지 예제를 통해 확인했듯이, Redis는 다음과 같은 방식으로 MCP 구현을 견고하고 프로덕션 준비 상태로 만드는 데 핵심적인 역할을 합니다:
- 속도와 유연성: 빠른 속도, 유연성, 서버리스 친화적 아키텍처의 결합
- 비용 절감: 비싼 API 호출과 연산을 줄여주는 캐싱 기능
서버 측이든 클라이언트 측이든 MCP로 개발하고 계신다면, Redis는 MCP 기반 AI 애플리케이션의 완벽한 동반자로서 반드시 툴킷에 포함해야 할 도구입니다. Upstash Redis를 사용하면 사용하지 않을 때 스케일을 제로로 줄이는 글로벌 가용성을 갖춘 서버리스 Redis를 오늘 바로 몇 초 만에 시작할 수 있습니다.