🤖

MCP Server の Streamable HTTP に関する技術要素

に公開

MCPで「Streamable HTTP」という仕様が策定されました。「Streamable HTTP」を実現するために使われているWeb技術について私なりの理解をまとめた記事になります。参照したMCPのバージョンは 2025-06-18 です。

※この記事は100%人力で書かれています

MCPの基本プロトコル

Streamable HTTP の話に入る前に基本プロトコルについて再確認しておきます。
MCPの基本プロトコルではJSON-RPCをつかって「MCPクライアント」と「MCPサーバー」が次のようなやりとりをします。

リクエスト:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "location": "New York"
    }
  }
}

レスポンス:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
      }
    ],
    "isError": false
  }
}

このときのやりとりは、1リクエスト・1レスポンスという訳ではなく、お互いがお互いのタイミングで会話をしていくイメージです。

下記の矢印の向きに注目してください。どちらか片方が連続で送信しているケースがあるのが重要なポイントです。

stdio と HTTP(Streamable HTTP)

上述のJSON-RPCでのやりとりは「stdio」と「Streamable HTTP」と呼ばれる transport メカニズムのどちらか一方を使って「MCPクライアント」と「MCPサーバー」が通信することになります。

この2つの transport メカニズムのうち、どちらを使うかによって、一般的にそれぞれ次のように呼ばれることが多いと思います。

  • stdio → Local MCP Server
  • Streamable HTTP → Remote MCP Server

ちなみに「Remote MCP」という表現は MCP Specification の中には登場しないようなので注意が必要です。

では、それぞれについて順に見ていきます。まずは「stdio」から考えてみましょう。

stdio の場合はとてもシンプル

上記シーケンス図のようなやりとりを「MCPクライアント」と「MCPサーバー」が行うのですが、「stdio」を使った場合は特に難しいことはなく、普通に標準入出力を使って相互に会話をすればよいだけです。

またローカル上で実行されるので基本的には認証認可について考えることもありません。

しかし、この仕様をHTTPを使って実現する場合は工夫が必要になります。

HTTP上でどうやって実現するのか?

では上記の仕様をHTTPを使って実現することを考えてみます。大きく考えないといけないことが2つあると思います。

  1. JSON-RPCをどうやってやりとりするのか?
    • → Streamable HTTP を使う
  2. 認証認可はどうするか?
    • → OAuth 2.0/2.1 を使う

1. Streamable HTTP について:

「Streamable HTTP」という仕様を使いMCPクライアントとMCPサーバーが通信します。なお「Streamable HTTP」はHTTPの用語ではなく、MCPの仕様として定義された言葉のようです。

2. OAuth について:

誰もが等しくMCPサーバーを使っていい場合もあれば、リクエストしてきたユーザー毎にレスポンスする内容を変えたい場合もあると思います。これを実現するために OAuth 2.0/2.1 を使い認証認可を行います。

それでは1と2それぞれについて順番に説明していきます。

SSE (Server-sent events) について

「Streamable HTTP」の説明の前に「SSE (Server-sent events) 」について軽く説明します。

sse on curl

SSE はHTTPサーバー側(つまりMCPサーバー側)が HTTP の Transfer-Encoding: chunked を使ってレスポンスすることで、サーバー側からデータを分割して送信できる仕組みです。これによりサーバー側は任意のタイミングでクライアント側にデータを通知することができます。(例えば処理の進捗をクライアント側に随時通知したりできる)

下記の例では event:data: のセットが2つあり、この1セットを1回で送信します。つまりこの例ではサーバー側から別々のタイミングで2回データを送信しています。(この例では2回送信していますが、実際は何回でも送信できます)

このようにクライアントとサーバーがHTTP接続を継続した状態で待機することで、サーバー側から通知が必要になったタイミングで、サーバー側からPUSH通知を送信することができます。

sse

そして、SSEの重要な点としては、サーバー側からPUSHすることしかできない点です。

一番最初にクライアント側からリクエストを送信した以降、クライアントからの送信はできないので、クライアント側はデータを受け取るだけの状態になります。

Streamable HTTP について

SSEの概要が分かったところで「Streamable HTTP」についてざっくりと説明すると、

  • 普段は一般的なHTTPを使う
  • MCPサーバー側からPUSH通知をしたいときはSSEを使う

というイメージです。なお、MCPサーバー側からPUSH通知を送信する要件がなければ、MCPサーバー側はSSEを実装する必要はないようです。

Streamable HTTP のシーケンス図

文章だけだと分かりにくいのでシーケンス図を使って「SSEを使う場合」と「SSEを使わない場合」について説明します。

SSEバージョン:

下記の図はMCPサーバー側がSSEを使う場合の一例です。
この図では先ほどのstdio版のシーケンス図と同じやり取りを Streamable HTTP で実現しています。

注目したいのは initialized のプロセスが終わった後 GET メソッドで SSE を開始しているところです。これによりサーバー側からのPUSH通知を受け取ることができています。

SSEなしバージョン:

SSEを使わない場合はもっとシンプルです。
もちろんSSEを使わないので、サーバー側からのPUSH通知を送信できなくなります。

Streamable HTTP についてもう少し深掘り

(この章は読み飛ばしても良いです)

ここまで説明してきた内容よりさらに詳しい仕様が知りたい場合は MCP specification を読むと良いと思いますが、個人的に重要なだと感じたポイントを箇条書きでまとめてみました。

ポイント1: Streamable HTTP とは 「HTTP または SSE」

  • 「Streamable HTTP」ではHTTPとSSEを使い分けます。
  • クライアント側は Accept: application/json, text/event-stream または Accept: text/event-stream とリクエストします
  • サーバー側でSSEが不要と判断したら Content-Type: application/json でレスポンスします
  • サーバー側でSSEが必要と判断したら Content-Type: text/event-stream でレスポンスします

ポイント2: エンドポイントパスは1つ

  • エンドポイントのパスは https://example.com/mcp のような1つのみです。RESTのように複数のパスをついつい想像してしまいますが、そうではないです。

ポイント3: GETメソッドとPOSTメソッドを使う

  • 「Streamable HTTP」で使うのはGETメソッド、POSTメソッドの2つです。
  • GETメソッド:
    • 常に Server-Sent Events(SSE)を使います
    • MCPサーバーからの通知をMCPクライアントが受け取るため、MCPクライアント側からGETリクエストを送信してSSE接続を確立しておきます
  • POSTメソッド:
    • MCPクライアント側からMCPサーバーに要求を伝えたい場合はPOSTを使います
    • SSEをつかってもいいし、使わなくてもいいです(SSEを使わない場合 Content-Type: application/json で応答する)

認可について

認証認可にはOAuthを使い、現時点では次の仕様となっています。

  • OAuth 2.1 IETF DRAFT (draft-ietf-oauth-v2-1-13)
  • OAuth 2.0 Authorization Server Metadata (RFC8414)
  • OAuth 2.0 Dynamic Client Registration Protocol (RFC7591)
  • OAuth 2.0 Protected Resource Metadata (RFC9728)

実際の処理については次のように進みます。

  • MCPの初回起動じにDynamic Client RegistrationでClient情報を登録する
  • MCPサーバー(リソースサーバー)にMetadataを問い合わせ認可サーバーを発見する
  • ローカル上で認可サーバーを受け取るWebサーバーを起動する
  • ブラウザで認可サーバーを開き、ユーザーが認可をする
  • ローカルで立ち上げておいたWebサーバーで認可コードをうけとる
  • 受け取った認可コードと、事前に登録したクライアント情報を使ってアクセストークンを発行する

アトラシアンMCPはこれをすでに実装してあります。

認可はまだまだ仕様が変わりそう

現状の仕様ではまだ問題もいくつかあり、仕様追加・変更の提案がいくつか上がっているようです。

例えば次のPRは Token Exchange などを使った根本的な変更を加えることで、企業環境で使いやすくしようという提案です。

SEP-646: Enterprise-Managed Authorization Profile for MCP
https://github.com/modelcontextprotocol/modelcontextprotocol/pull/646

また下記issueは、認証認可フローをMCPサーバーで実行するという提案です。これが通れば Dynamic Client Registration の問題などを根本的に解決することになりそうなので、こちらの議論の行方も気になるところです。

SEP-1299: Server-Side Authorization Management with Client Session Binding
https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1299

おしまい

以上、私の理解をまとめてみましたが、認識が違っているところがあればご指摘いただけるとありがたいです。

Discussion