Redis
 Computer >> コンピューター >  >> プログラミング >> Redis

RedisでMCP開発を加速する:高速かつコスト効率に優れたインフラ構築ガイド

Model Context Protocol(MCP)は、AIモデルと外部ツールやデータソースを接続するための標準的な手法として急速に普及しています。MCPの採用が拡大するにつれ、堅牢で本番環境対応のMCP実装には、仕様に従うだけでなく適切なインフラストラクチャが必要であることが開発者の間で明らかになってきました。本記事では、Redisが以下の3つのMCPユースケースをどのように支えているのかを詳しく見ていきます。VercelのSSE実装における分散型サーバーレス関数の連携、長時間稼働するストリームのためのイベント再開(Resumability)機能、そしてClerkによるセキュアなOAuthフローの管理です。

MCPトランスポートの基礎知識

具体例に入る前に、MCPが通信をどのように処理しているのかを簡単におさらいしましょう。MCPは2つのトランスポートをサポートしています。ローカルサーバー向けの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という2つの重要なエンドポイントがあります。問題は、Vercelのようなサーバーレス環境ではこれらのエンドポイントが完全に分離されている点です。メモリを共有せず、/messageへのリクエストは、/sseを処理している関数インスタンスとはまったく別のインスタンスで処理される可能性があります。

その解決策となったのがRedis Pub/Subです。Malte氏はX(旧Twitter)で次のように説明しています。

RedisでMCP開発を加速する:高速かつコスト効率に優れたインフラ構築ガイド

この実装では、Redisチャネルを使ってエンドポイント間の連携を行います。クライアントが/messageにメッセージを送信すると、そのエンドポイントは/sseエンドポイントが購読しているRedisチャネルへパブリッシュします。/sseエンドポイントはリクエストを処理し、今度は/messageがリッスンしている別のチャネルを通じてレスポンスを返します。

RedisでMCP開発を加速する:高速かつコスト効率に優れたインフラ構築ガイド

このパターンにより、Redisは事実上メッセージバスとして機能し、分離されたサーバーレス関数同士をつなぐ橋渡し役となります。あたかも同一プロセスの一部であるかのように協調動作できるのです。SSEは現在Streamable HTTPに置き換えられ非推奨となっているため(新規のMCP実装では最新のトランスポートを使うべきです)、この例は「分離されたサーバーレス関数間の連携にRedisを活用する」優れた実践例として参考になります。

ユースケース2:再開性(Resumability)を実現するイベントストア

MCPのStreamable HTTPトランスポートの最も強力な機能の一つが、再開性(Resumability)のサポートです。この機能により、クライアントは切断が発生した場合でも、中断した箇所からストリームを続行できます。ネットワーク障害が避けられない本番アプリケーションにとって、これは極めて重要な特性です。

MCPストリームの仕組み

なぜ再開性が重要なのかを理解するには、MCPストリームの構成を把握する必要があります。シンプルなツール呼び出しなどの一部の操作は単一のレスポンスを返します。しかし、ツールはserver.sendLoggingMessageなどのメソッドを使って、実行中に複数のイベントを返すこともできます。こうしたマルチイベントのストリームこそ、再開性が不可欠となる場面です。クライアントが途中で切断された場合、最初からやり直すのではなく、中断した地点から処理を再開できるべきだからです。

RedisによるEventStoreの実装

MCP SDKは、イベント追加用のstoreEventと、特定のイベントID以降のイベントを取得するreplayEventsAfterという2つのメソッドを要求するEventStoreインターフェースを定義しています。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を使用しています。イベントストリームの保存と再生というこのユースケースに、Redis Streamsはまさにうってつけです。

既知の制限事項

注意すべき重要な点が一つあります。ツールが複数のイベントを送信する場合、streamIdは_GET_streamに設定され、この定数は異なるストリームやユーザー間で共通となります。つまり、複数のイベントを送信する同じツールを2人のユーザーが同時に使用した場合、それぞれのイベントが同じストリーム内で混在してしまう可能性があるのです。

筆者はこの問題について公式リポジトリにIssueを起票済みです。解決の方向性によっては実装の調整が必要になる可能性があり、必要であれば本記事も随時更新します。

ユースケース3:ClerkのMCP OAuth実装

MCP仕様には認証・認可のサポートが含まれており、MCPサーバーはクライアントを安全に認証し、リソースへのアクセスを制御できます。仕様はOAuth 2.0を含む複数の認証方式をサポートしており、特に既存のIdP(アイデンティティプロバイダー)との統合や、ツール・データへのユーザースコープのアクセス実現に有用です。

Clerkは、MCP SDKのOAuthClientProviderインターフェースを実装することで、MCP向けの包括的なOAuthクライアントを構築しました。彼らの実装は、MCPアプリケーションにおけるRedisのもう一つの重要なユースケースを示しています。ClerkのMCPツールを使ってみたい方は、優れた出発点となるデモリポジトリをぜひチェックしてみてください。

OAuthになぜストレージが必要なのか

Clerkがmcp-toolsリポジトリで説明しているように、MCPのOAuth実装には永続ストレージが不可欠です。主な理由は次の2点です。

  1. OAuthフローが複数のエンドポイントにまたがる — 初期化、OAuthコールバック、MCPリクエストが、共有メモリを持たない異なるサーバーレス関数で処理される可能性があります
  2. MCP接続は長時間稼働する — インメモリストレージに依存すると、アプリケーションのスケールに伴いメモリ要件が肥大化し、サーバーの再起動ですべてのセッションが無効化されてしまいます

Redisに保存されるデータ

ClerkのRedisストアは、OAuthフローにおいてそれぞれ特定の役割を持つ3種類のデータを管理しています。

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が最適な理由

3つの事例すべてに共通するパターンから、RedisがMCPインフラとして理想的な選択肢である理由が見えてきます。

低レイテンシ:MCP操作には高速性が求められます。Redisのインメモリアーキテクチャは、リアルタイムのAIインタラクションに必要な応答時間を提供します。

Pub/Sub機能:Vercelの実装で見たように、Redis Pub/Subはフル機能のメッセージキューの複雑さなしに、分散コンポーネント間のエレガントな連携を可能にします。

豊富なデータ構造:イベントストリームにはStreams、セッションデータにはHashes、stateにはシンプルなキーバリュー。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のツール結果をキャッシュするパッケージが含まれています。次の例のように、パフォーマンスを大幅に向上させながらコスト削減も実現します。

RedisでMCP開発を加速する:高速かつコスト効率に優れたインフラ構築ガイド

MCPサーバー、AIエージェント、あるいは本格的なAIアプリケーションの構築など、目的を問わず、Redisはシステムを本番対応・スケーラブル・高性能にするインフラ層を提供してくれます。

まとめ

Model Context Protocolはまだ歴史の浅いプロトコルですが、AIアプリケーションに不可欠なインフラへと急速になりつつあります。3つの事例を通じて見てきたように、Redisは以下の点でMCP実装の堅牢性と本番対応力を支える重要な役割を果たします。

  • 速度と柔軟性:高速性・柔軟性・サーバーレスフレンドリーなアーキテクチャの組み合わせ
  • コスト削減:高価なAPI呼び出しや計算を減らすキャッシュ機能

MCPを使った開発を計画しているなら、サーバー側でもクライアント側でも、Redisをツールキットに加えておくことをおすすめします。MCP搭載AIアプリケーションの理想的な相棒となるはずです。Upstash Redisなら、グローバルに利用可能で未使用時にはゼロまでスケールするサーバーレスRedisを、今日すぐに数秒で使い始められます。

  1. Redis ZRANGEBYSCOREコマンドの使い方 – スコア範囲を指定してソート済みセットの要素を昇順に取得する方法

    このチュートリアルでは、RedisのZRANGEBYSCOREコマンドを使用して、指定したスコア範囲内にあるソート済みセット(sorted set)のすべての要素を、スコアの昇順で取得する方法について解説します。 ZRANGEBYSCOREコマンドとは ZRANGEBYSCOREコマンドは、引数として渡されたmin(最小値)以上かつmax(最大値)以下のスコアを持つ、ソート済みセット内のすべての要素を返します。返される要素はスコアの昇順に並べられ、同じスコアを持つ要素同士は辞書順(レキシコグラフィカル順)で昇順に配置されます。 デフォルトではminとmaxは閉区間(境界値を含む)として扱われま

  2. RedisとLuaでスケーラブルな分散レートリミッターを構築する – ステップバイステップ完全ガイド

    この包括的なガイドでは、Redis と Lua スクリプトを組み合わせて、高トラフィック環境下におけるユーザーリクエストを制御する「分散レートリミッター」を構築する方法を解説します。レート制限(Rate Limiting)は、不正利用の防止、トラフィックの管理、そしてリソースの保護のために、あらゆるシステムにおいて欠かせない仕組みです。Redis と Lua を活用することで、大量のリクエストを効率的にさばきながら、バックエンドサービスを安全に守れるスケーラブルなレート制限システムを実現できます。さらに本記事では、トラフィックをシミュレートし、レート制限が実際に適用される様子を観察したり、ブロ