リモート開発メインのソフトウェア開発企業のエンジニアブログです

MCP サーバーの stateful と stateless

背景

現在、MCP ゲートウェイのようなものを作っています。

  • 複数の MCP サーバーを一元管理し、一つの実行環境で全て起動
    • MCP ゲートウェイは内部的には MCP クライアントとして動作
  • MCP クライアントに対して、MCP サーバーのツールをまとめて提供
    • MCP ゲートウェイは、外部的には MCP サーバーとして動作

という形です。通信の流れとしては以下のような感じです。

MCP クライアント <- n:1 -> MCP ゲートウェイ <- 1:n -> MCP サーバー

実装にあたって、MCP 関連のライブラリーや MCP 自体の仕様を色々調べる必要があり、せっかくなのでブログにまとめようと思いました。

MCP の仕様、特に通信周りについて

3つの方式

MCP の通信方式としては以下の3つがあります。

  • stdio
  • SSE
  • Streamable HTTP

stdio は、ローカル環境で MCP サーバーのプロセスを起動して、stdio (標準入出力)経由でデータをやり取りする方式です。

残りの2つは、ネットワーク上・リモートにある MCP サーバーに接続してデータをやり取りします。ただし、SSE はプロトコルバージョン 2024-11-05 で定義された古い仕様で現在は deprecated となっていて、代わりに Streamable HTTP が主流となっています。

Transports – Model Context Protocol

これ以降、基本的には Streamable HTTP について説明します。

Streamable HTTP の概要

詳しくは前述の公式仕様を見てもらうのが良いのですが、簡単にまとめます(細かい点は省略している部分もあります)。

  • クライアントは POST メソッドを使って JSON-RPC メッセージを送る
  • サーバーは以下のどちらかを返す
    • Content-Type: application/json で JSON object を返す
    • Content-Type: text/event-stream に SSE セッションを開始(※)
  • それとは別で、クライアントは GET メソッドで SSE チャンネルを開き、サーバーからの通知を受け取ることが出来る(MAY, ※)
  • サーバーは stateful セッションを提供する事もできて(MAY)、その場合は MCP-Session-Id ヘッダーを返す

最後の2つは MAY なので、サーバー側としてサポートしなくても仕様上は問題ありません。

※: この辺が分かりにくいのですが、プロトコルバージョン 2024-11-05 で定義された HTTP+SSE の仕様とは別物です。

セッションとは

MCP の仕様では、セッションの開始・終了等について定義されていますが、どのような情報を内部で扱うか等については関知していません。MCP サーバーとして stateful な処理が必要な場合にサポートするのだと思います。

MCP サーバーとして stateful な処理にどのようなものがあるかは詳しく調べていませんが、

  • 前回のツール呼び出しの結果を覚えておいて、以降のツール呼び出し等で使う
  • 重たい初期化処理を1度だけ行い、以降は初期化処理は行わない

などが考えられます。

HTTP は単体では stateless なプロトコルなので、セッションの仕組みを別途用意する必要があるという感じです。ウェブフレームワークにおけるセッションと似たようなものと考えてもらえれば、当たらずとも遠からずだと思います(セッションに何を保存するかは、アプリケーションによって異なる)。

stdio は性質としては stateful

なお、stdio の場合は、プロセスを起動して stdio 経由でデータをやり取りするので、プロセスが終了するまでは内部的に情報を保持しています。そのため、stateful 的な挙動をさせることは容易ですが、ツールの挙動を stateful, stateless のどちらにするかは MCP サーバーの設計方針によるかと思います。

Moba Pro

stateful と stateless のどちらが良いのか

stateless の方が実装は楽だし、ユースケースの大半はカバー出来る

stateful, stateless のどちらが良いのか、という曖昧な質問に対する答としては、「場合による」です。とはいえ、stateless の方がサーバー、クライアント共に実装という観点では statless の方が簡単です。

利用側の観点ではどうでしょうか。stateful でなければいけないユースケースというのは以下のような例がありますが、世間の MCP サーバーでこのような機能を提供しているものはそこまで多くないと思います。

  1. 複数のステップにまたがる処理をサーバー側で行う
    • 商品をカートに追加 -> 支払い -> 確定、など
  2. インタラクティブな処理
    • 人間による承認が途中で入る
    • ゲームみたいなもの

現実問題として、既存の REST API をラップしただけの MCP サーバーが大多数な事を考えると、stateless で大半のユースケースをカバー出来ます。(少なくとも現時点では。)

MCP の仕様としても stateless に寄せていく方向で議論しているっぽい

以上のような状況を踏まえてか、MCP の仕様自体も stateless がメインになっていくような方向で議論が進んでいるように見えます。ざっと眺めた程度で中身は詳しく読んでいませんが、以下のような情報・議論があります。

過去を遡ると、Streamable HTTP の導入前の議論でもこの辺の話が出てきています。

State, and long-lived vs. short-lived connections · modelcontextprotocol/modelcontextprotocol · Discussion #102

実装はどうなっているのか

全然網羅的ではありませんが、今開発しているものに関連していくつかのライブラリーを見たので、それらでどのような実装になっているのかを紹介します。

LangChain -> stateless がデフォルトだが、stateful もいける?

By default, MultiServerMCPClient is stateless—each tool invocation creates a fresh MCP session, executes the tool, and then cleans up.

If you need to control the lifecycle of an MCP session (for example, when working with a stateful server that maintains context across tool calls), you can create a persistent ClientSession using client.session().

Model Context Protocol (MCP) – Docs by LangChain

上の記載は Python のものです。

JavaScript 版だと以下のようになっているのですが、「the stateful sessions section」なるものが存在しません。

MultiServerMCPClient is stateless by default. Each tool invocation creates a fresh MCP ClientSession, executes the tool, and then cleans up. See the stateful sessions section for more details.

Model Context Protocol (MCP) – Docs by LangChain

FastMCP -> どちらも対応

どちらも問題無く対応出来そうな感じです。デフォルトでは stateful ですが、オプションを渡す事で stateless になるそうです。

MCP SDK -> 当然両方対応している

公式 SDK なので、当然どちらにも対応しています。ただ、実装例のコードを見ると stateless の方が圧倒的に簡単です。

typescript-sdk/examples/server/src/simpleStatelessStreamableHttp.ts at main · modelcontextprotocol/typescript-sdk

まとめ

MCP サーバーの通信方式としては、stdio (ローカル実行)、Streamable HTTP (リモート接続)の2つが主流です。Streamable HTTP でも MCP-Session-ID ヘッダーを使って stateful にするのと stateless にするという2つの選択肢があります。

サーバーの実装という観点では stateless の方が簡単です。世の中の大半のユースケースは stateless でも事足りるので、MCP サーバーを実装する場合は、まずは stateless で実装してみるのが良いと思います。

← 前の投稿

公式の Slack MCP サーバーを試してみた

次の投稿 →

コメントを残す