Streamable HTTPは、MCP(Model Context Protocol)のクライアントとサーバーがHTTPでメッセージをやり取りするための通信方式(トランスポート)です。2025-03-26版の仕様で、それまでのHTTP+SSE方式の後継として導入されました。2026年7月28日に公開された最新仕様(2026-07-28版)では、セッションやGETストリームが廃止され、中身が大きく変わっています。本記事では、仕様書の原文と公式TypeScript SDKを2026年9月26日時点で確認し、SSEとの違い、版ごとの変遷、SDK v2で実際にサーバーを立てて確かめた挙動をまとめます。
まとめ:Streamable HTTPの要点(2026年9月時点)
- MCPの標準トランスポートはstdioとStreamable HTTPの2つ。リモートのMCPサーバーはStreamable HTTPで公開する。
- クライアントはメッセージを1件ずつ、1本のMCPエンドポイント(例:
https://example.com/mcp)へPOSTする。サーバーは1件のJSONで返すか、そのリクエスト専用のSSEストリームで返すかを選べる。 - 旧HTTP+SSE方式(2024-11-05版)は2エンドポイントで常時接続が必要だった。2025-03-26版で非推奨になり、2026-07-28版では廃止候補(Deprecated)に分類された。
- 2026-07-28版で
Mcp-Session-Id・GETストリーム・Last-Event-IDによる再開が廃止され、Mcp-Method・Mcp-Nameヘッダーが必須になった。 - 2026-07-28版を実装しているのはTypeScript SDKでは分割パッケージのv2(
@modelcontextprotocol/serverほか)。従来の@modelcontextprotocol/sdkは最新の1.30.1でも2025-11-25版まで。 - v2のサーバーは既定で2025年仕様のクライアントも同じURLで受けられる。一方でv2のクライアントは既定で旧方式で接続するため、当面は新旧の両対応が前提になる。
Streamable HTTPの役割:MCPで使う2つのトランスポート
MCPはJSON-RPC 2.0でメッセージを表現し、それを運ぶ層を「トランスポート」と呼びます。2026-07-28版の仕様が標準として定めるのは次の2つだけです。
| 項目 | stdio | Streamable HTTP |
|---|---|---|
| サーバーの動き方 | クライアントが子プロセスとして起動 | 独立したプロセスとして常駐 |
| 接続できるクライアント | 起動したクライアント1つ | 複数 |
| メッセージの区切り | 改行区切りのJSON | HTTP POST 1回に1件 |
| 主な用途 | ローカルのツール連携 | リモート公開・社内共有 |
| キャンセル | notifications/cancelled を送る |
応答ストリームを閉じる |
WebSocketやgRPCは標準トランスポートではありません。仕様は独自トランスポートを認めていますが、JSON-RPCの形式とメッセージの型を守ることが条件です。MCPそのものの仕組み(ツール・リソース・プロンプト)はMCP(Model Context Protocol)とは?AIと外部ツールをつなぐ標準規格の仕組みをわかりやすく解説で解説しています。
リクエストへの応答:JSONとSSEの選択
クライアントはPOSTのたびに Accept ヘッダーへ application/json と text/event-stream の両方を書く決まりです。サーバーはリクエストごとにJSONかSSEを選べます。たとえば途中経過を通知する処理ではSSEを選び、進捗通知(notifications/progress)を流したあと、最後に結果を送ってストリームを閉じます。途中の通知が不要なら、単一のJSONで返す構成にできます。処理時間だけで応答形式が決まるわけではなく、クライアントはJSONとSSEの両方を受けられなければなりません。
通知(応答を求めないメッセージ)をPOSTした場合、サーバーは受理すれば本文なしの 202 Accepted を返します。
HTTP+SSE方式とStreamable HTTPの違い
「SSEとStreamable HTTPの違い」と言うとき、比べる相手はSSEという技術そのものではなく、2024-11-05版のMCP仕様にあった「HTTP+SSEトランスポート」です。
| 項目 | HTTP+SSE(2024-11-05版) | Streamable HTTP(2026-07-28版) |
|---|---|---|
| エンドポイント | SSE用とPOST用の2本 | MCPエンドポイント1本 |
| 接続の始め方 | GETでSSEを開き、endpointイベントでPOST先を受け取る | いきなりPOSTしてよい |
| 応答の返り道 | 常時接続のSSEストリーム | そのPOSTへのJSONか専用SSE |
| 常時接続 | 必須 | 不要(変更通知を受けるときだけ) |
| 接続・セッション管理 | 常時SSE接続を管理 | プロトコルのセッションなし |
| 仕様上の扱い | Deprecated(廃止候補) | 現行の標準 |
旧方式では、応答が必ず長時間つなぎっぱなしのSSEを通って返ってくるため、サーバーは接続とクライアントを結び付けて覚えておく必要がありました。接続情報を各サーバーのメモリだけに保持する構成では、スティッキーセッションなどでPOSTを対応するSSE接続へ届ける必要があります。Redisなどのメッセージブローカーを介して応答を配送する構成もあります。サーバーレス環境のように接続を長く保てない基盤とも相性が悪い方式です。
2026-07-28版の仕様は、HTTP+SSEを新しく定めた機能ライフサイクル方針の「Deprecated」に分類し、新規実装は採用すべきでない(SHOULD NOT)と明記しました。原則の廃止猶予は最低12か月ですが、以前から非推奨だったHTTP+SSEには移行規定が適用され、廃止可能になるのはSEP-2596がFinalに達してから3か月後です。これは実際の廃止日を確定するものではありません。
「SSEが廃止された」は誤解
Streamable HTTPもSSEを使います。なくなったのは「接続全体で1本の常時SSE」であって、「1リクエストに1本のSSE」はむしろ標準の応答形式です。SSEの仕組み自体はSSE(Server-Sent Events)とは|仕組み・WebSocketとの違い・実装を解説、WebSocketとの比較はSSEとWebSocketの違いと使い分け|WebRTC・WebTransportまで性能比較が詳しいです。
仕様バージョン別に見るStreamable HTTPの変遷
同じ「Streamable HTTP」でも、版によって中身が違います。2025年に書かれた解説記事の多くは2025-03-26版の形を説明しているため、現行仕様と食い違う箇所があります。
| 仕様の版 | Streamable HTTPに関わる主な変更 |
|---|---|
| 2025-03-26 | HTTP+SSEに代えて導入。セッションID・GETストリーム・再開・バッチ送信を定義 |
| 2025-06-18 | JSON-RPCのバッチ送信を削除。MCP-Protocol-Version ヘッダーを必須化 |
| 2025-11-25 | 不正なOriginへの403応答を明記。サーバーが任意に切断するSSEポーリングを追加 |
| 2026-07-28 | セッション・GETストリーム・再開を廃止。Mcp-Method と Mcp-Name を必須化 |
2026-07-28版はGitHubで2026年5月29日にリリース候補(RC)、7月28日に正式版が公開されました。仕様の版は日付で表し、クライアントとサーバーは対応する版をすり合わせて使います。公開日は公式リポジトリのリリース一覧、通信の要件は2026-07-28版のStreamable HTTP仕様で確認できます。ハンドシェイクの廃止・MRTR・非推奨機能など版全体の変更点はMCP仕様2026-07-28版とはにまとめています。
2026-07-28版で廃止された機能と追加された機能
2026-07-28版の変更は、MCPを「ステートレス(サーバーが接続ごとの状態を持たない)」なプロトコルへ作り替えるものです。初回の initialize ハンドシェイクもなくなり、各リクエストが本文の _meta に自分のプロトコルバージョンとクライアントの対応機能を載せて送られます。
Mcp-Session-IdとDELETEの廃止:ツール引数による状態の受け渡し
2025年版では、セッション管理を採用するサーバーが初期化時に Mcp-Session-Id ヘッダーでセッションIDを払い出すことができ、発行された場合はクライアントが以後のリクエストにそれを付けます。終了時のDELETE送信は推奨でしたが、サーバーは405を返してクライアントからの終了を認めないこともできました。2026-07-28版ではこの仕組みが丸ごと削除されています。
複数回の呼び出しにまたがる状態が必要なサーバーは、サーバー側で発行したハンドル(識別子)を普通のツール引数として受け渡します。たとえば「カートを作成するツール」がカートIDを返し、「商品を追加するツール」がそのIDを引数に取る形です。状態がプロトコルでなくアプリケーションの設計に移ったと考えると分かりやすいでしょう。
GETストリームの廃止とsubscriptions/listen
2025年版では、クライアントがGETで常時SSEを開き、ツール一覧の変更などサーバー発の通知を受け取れました。2026-07-28版ではGETエンドポイントが廃止され、代わりに subscriptions/listen というリクエストをPOSTし、その応答ストリームを開いたままにします。受け取る通知の種類(toolsListChanged・promptsListChanged・resourcesListChanged・resourceSubscriptions)はクライアントが明示し、サーバーは要求されていない種類を送ってはいけません。
サーバーからクライアントへの問い合わせ(サンプリングや入力要求)も、SSEで別リクエストとして送る方式から、結果の中に「追加入力が必要」という形で埋め込み、クライアントが入力を添えて再送する方式(Multi Round-Trip Requests)に変わりました。
Last-Event-IDによる再開の廃止
2025年版では、イベントIDの付与と再送に対応するサーバーなら、切断後に Last-Event-ID ヘッダーを使ってストリームの再開を要求できました。2026-07-28版ではこの再開機能と再送がなくなり、応答ストリームが切れたら処理中のリクエストは失われます。クライアントは新しいリクエストIDで同じリクエストを出し直す決まりです。副作用のあるツール(決済、メール送信など)は、二重実行されても結果が変わらないよう、ツール側で冪等性(同じ操作を繰り返しても結果が同じになる性質)を持たせておく必要があります。
必須になったMcp-MethodとMcp-Nameヘッダー
POSTには本文の内容をヘッダーへ写した Mcp-Method(メソッド名)が必須になり、tools/call・resources/read・prompts/get では Mcp-Name(ツール名やURI)も必須です。ロードバランサーやAPIゲートウェイ、WAFが本文のJSONを解析せずに振り分けや流量制限をかけられるようにするためです。
POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: add-note
{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"add-note","arguments":{"text":"hello"},
"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{}}}}
ヘッダーと本文が食い違う場合、サーバーは 400 Bad Request とエラーコード -32020(HeaderMismatch)で拒否しなければなりません。ゲートウェイがヘッダーを見て許可したのに、サーバーは本文の別のツールを実行する、というすり抜けを防ぐ規定です。ツールの引数を x-mcp-header 指定で Mcp-Param-名前 ヘッダーに写す仕組みも加わりました。
TypeScript SDK v2によるStreamable HTTPサーバーの実装
公式TypeScript SDKは2026年7月27日に分割パッケージのv2(@modelcontextprotocol/server・client・node ほか)を2.0.0として公開し、READMEで「2026-07-28 MCP specを実装する安定版」と位置づけています。従来の一体型パッケージ @modelcontextprotocol/sdk も更新は続いていますが、2026年9月23日公開の1.30.1でも対応する最新版は2025-11-25のままです。2026-07-28版を使うならv2への移行が前提になります。
以下はNode.js v26.5.0とSDK v2.1.0で動かした最小構成です。サーバーコードをserver.mjsに保存し、依存パッケージをインストールしてから node server.mjs で起動します。add-noteは通信確認用の疑似実装で、保存済みを示す文字列を返すだけでメモを永続化しません。
npm install @modelcontextprotocol/[email protected] @modelcontextprotocol/[email protected] @modelcontextprotocol/[email protected] [email protected]
import { createServer } from 'node:http';
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
import { toNodeHandler, localhostHostValidation, localhostOriginValidation } from '@modelcontextprotocol/node';
import * as z from 'zod/v4';
const handler = createMcpHandler(({ era }) => {
const server = new McpServer({ name: 'notes', version: '1.0.0' });
server.registerTool(
'add-note',
{ description: 'メモを保存する', inputSchema: z.object({ text: z.string() }) },
async ({ text }) => ({ content: [{ type: 'text', text: `saved: ${text} (${era})` }] })
);
return server;
});
const nodeHandler = toNodeHandler(handler);
const validateHost = localhostHostValidation();
const validateOrigin = localhostOriginValidation();
createServer((req, res) => {
if (!validateHost(req, res) || !validateOrigin(req, res)) return;
void nodeHandler(req, res);
}).listen(3000, '127.0.0.1');
createMcpHandler に渡す関数は、HTTPリクエストが来るたびにサーバーのインスタンスを新しく作ります。インスタンスに状態を持たせないので、そのまま複数台に並べてもスティッキーセッションは要りません。データベース接続のように使い回すものは、関数の外で1回だけ作ります。マウント方法の詳細はSDK公式のServe over HTTPにまとまっています。
curlで確かめた応答
このサーバーにcurlでリクエストを送りました(2026年9月26日実測。Node.js v26.5.0、server・node・client 2.1.0)。Mcp-Name を本文と違う名前にしたときのコマンドと応答は次のとおりです。
curl -s -X POST http://127.0.0.1:3000/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2026-07-28' \
-H 'Mcp-Method: tools/call' -H 'Mcp-Name: other' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"add-note","arguments":{"text":"hello"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'
HTTP/1.1 400 Bad Request
{"jsonrpc":"2.0","error":{"code":-32020,"message":"Bad Request: the request headers and body disagree: the body carries params.name=\"add-note\" but the Mcp-Name header names \"other\"", ...},"id":1}
ほかの条件の結果は次の表のとおりです。
| 送ったもの | HTTPステータス | 返ってきた内容 |
|---|---|---|
| 正しいヘッダーでtools/call | 200 | JSON。resultType: complete 付き |
| Mcp-Nameを別名にしたtools/call | 400 | -32020(ヘッダーと本文の不一致) |
| Mcp-Methodなしのtools/list | 400 | -32020(必須ヘッダーの欠落) |
| 未知の版 2027-01-01 を指定 | 400 | -32022。対応版 2026-07-28 を列挙 |
| server/discover | 200 | 対応版と機能の一覧 |
| GET・DELETE | 405 | Method Not Allowed |
| Origin: https://evil.example 付きPOST | 403 | Forbidden |
| 2025-06-18版のinitialize | 200 | SSEで初期化結果。セッションIDなし |
最後の行が示すとおり、createMcpHandler は既定(legacy: 'stateless')で2025年仕様のクライアントにも応答します。ただしセッションは発行しないので、2025年版のGETストリームやセッション終了のDELETEは405になります。2026-07-28版だけを受けたい場合は legacy: 'reject' を指定します。
SDK v2クライアントの既定値:2025年版の初期化方式
見落としやすいのがクライアント側の既定値です。v2の Client はオプションなしだと2025年版の initialize で接続し、新方式を試しません。上のサーバーにつないで getProtocolEra() を見ると、オプションなしでは legacy、versionNegotiation: { mode: 'auto' } を付けると modern になりました。
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
const client = new Client(
{ name: 'my-client', version: '1.0.0' },
{ versionNegotiation: { mode: 'auto' } }
);
await client.connect(new StreamableHTTPClientTransport(new URL('http://127.0.0.1:3000/mcp')));
console.log(client.getProtocolEra());
auto は最初に server/discover で相手の対応版を問い合わせ、2025年版しか話さないサーバーなら initialize へ戻ります。v1(1.30.1)でセッションを使うサーバーに対して実測すると、auto は legacy で接続に成功し、mode: { pin: '2026-07-28' } で版を固定した場合は ERA_NEGOTIATION_FAILED で失敗しました。
SDKとクライアントごとのStreamable HTTPの指定方法
設定ファイルやコマンドでは、Streamable HTTPを streamable-http や http と書くのが一般的です。2026年9月26日時点の公式ドキュメントでの書き方を並べます。
| SDK・クライアント | 指定方法 | 補足 |
|---|---|---|
| Python SDK(mcp 2.x) | mcp.run(transport="streamable-http") |
既定は127.0.0.1:8000の /mcp |
| Claude Code | claude mcp add --transport http 名前 URL |
SSEは非推奨と明記 |
| VS Code(mcp.json) | "type": "http" |
URLは url に書く |
| TypeScript SDK v2 | createMcpHandler |
クライアントは StreamableHTTPClientTransport |
Python SDKは2026年7月28日公開のv2.0.0で2026-07-28版に対応し、同じサーバーで2025年版のクライアントにも応答します(PyPIの最新は2026年9月7日公開の2.2.0)。v2では高水準クラスの名前が FastMCP から MCPServer に変わり、旧名でのimportはできません。
2025年版から移行するときの判断基準
2026年9月時点では、サーバーを2026-07-28版だけに絞る(legacy: 'reject')のは早すぎます。公式SDKのクライアントですら既定が旧方式なので、利用者側のツールが新方式で接続してくる保証がないからです。当面は新旧の両方を受ける構成を基本にし、アクセスログで旧方式の割合が十分下がってから絞るのが安全です。
- セッションに状態を持たせているサーバー:v1のセッション付き実装に2026-07-28版のリクエストを送ると、
400と「Server not initialized」(-32000)で拒否されました。状態をツール引数のハンドルへ移す設計変更が先に必要です。すぐに移せない場合、SDKのisLegacyRequestで旧方式のリクエストだけを既存の実装へ振り分ける構成が公式ドキュメントに載っています。 - ロードバランサー:新方式のリクエストはスティッキーセッションなしで振り分けられます。旧方式のセッション付き通信が残る間は、その経路だけ現状の設定を維持します。
- リバースプロキシ:SSEの応答がnginxなどで溜め込まれると、進捗通知がまとめて届きます。仕様はSSE応答に
X-Accel-Buffering: noを付けるよう推奨しており、v2のサーバーは実測でもこのヘッダーを付けていました。 - 再送に弱いツール:ストリームが切れたら続きから受け取れないため、上で触れた冪等性の確保が移行の前提になります。
PythonのFastMCPについてはFastMCPとは|PythonでMCPサーバーを最速構築するフレームワークの使い方【2026年版】、Cloudflare Workersへの配置はCloudflareでMCPサーバーを構築・公開する方法|Workersリモートデプロイと認証・ポータルで扱っています。
Streamable HTTPサーバーのセキュリティ要件
仕様はStreamable HTTPのサーバーに次の3点を求めています。
- すべての接続で
Originヘッダーを検証し、不正なら403 Forbiddenを返す(MUST)。 - ローカルで動かすときは0.0.0.0でなく127.0.0.1だけで待ち受ける(SHOULD)。
- すべての接続に適切な認証をかける(SHOULD)。
前の2つはDNSリバインディング攻撃への対策です。悪意あるWebサイトが自分のドメイン名を127.0.0.1に解決させると、ブラウザがローカルのMCPサーバーを同一オリジンとして扱い、閲覧者のPC上のツールを呼べてしまいます。
注意したいのは、SDK v2の createMcpHandler 自体はHostもOriginもトークンも検証しないことです。上のサンプルで localhostHostValidation と localhostOriginValidation を前段に置いているのはそのためです。Expressで使う場合は createMcpExpressApp がlocalhostで待ち受けるときにこの検証を既定で有効にしますが、0.0.0.0など外部向けに待ち受けると既定の検証が外れるため、許可するホスト名を自分で指定します。
リモートサーバーの認証はOAuthが基本です。2026-07-28版ではクライアント登録の方式として動的クライアント登録(DCR)が非推奨になり、Client ID Metadata Documents(CIMD)が推奨されました。詳しくはCIMD(Client ID Metadata Documents)とは?MCPでDCRに代わるOAuthクライアント登録の仕組みと実装【2026年9月時点】を参照してください。
よくある質問
Streamable HTTPとSSEはどちらを使うべきですか?
新しく作るならStreamable HTTPです。旧HTTP+SSE方式は2025-03-26版から非推奨で、2026-07-28版では廃止候補に分類されています。TypeScript SDK v2のサーバー本体はHTTP+SSE方式を提供しません。旧サーバーへ接続するためのクライアント実装と、移行できない環境向けにv1から凍結コピーした別パッケージ(@modelcontextprotocol/server-legacy/sse・v3で削除予定)が残るだけです。
MCPのStreamable HTTPとWebSocketは何が違いますか?
Streamable HTTPは普通のHTTP POSTと、応答用のSSEだけで成り立ちます。WebSocketのような接続の切り替え(アップグレード)を使わないので、HTTPを通すプロキシやAPIゲートウェイの後ろに置けます。WebSocketはMCPの標準トランスポートではなく、使う場合は独自トランスポートの扱いになります。
エンドポイントのURLは /mcp でなければいけませんか?
いいえ。仕様が求めるのはPOSTを受ける単一のパスを用意することだけで、https://example.com/mcp は例として挙げられているものです。ただし多くのSDKやクライアントの例が /mcp を使っているため、特に理由がなければ合わせておくと設定の食い違いが減ります。
2025年版のStreamable HTTPで作ったサーバーはもう使えませんか?
使えます。2026-07-28版の仕様は、新しいクライアントが旧版のサーバーを見分けて initialize へ戻る手順を定めています。TypeScript SDK v2のクライアントも mode: 'auto' なら旧サーバーに自動で合わせます。ただし新しい版だけに固定したクライアントからは接続できません。
セッションがなくなると、会話の文脈はどう保持しますか?
プロトコルは状態を持たないので、必要な状態はサーバーが発行するIDをツールの引数でやり取りして管理します。LLMとの会話履歴そのものはクライアント(ホストアプリ)側が持つもので、MCPのセッション廃止の影響は受けません。
関連記事
- MCP(Model Context Protocol)とは?AIと外部ツールをつなぐ標準規格の仕組みをわかりやすく解説
- FastMCPとは|PythonでMCPサーバーを最速構築するフレームワークの使い方【2026年版】
- CloudflareでMCPサーバーを構築・公開する方法|Workersリモートデプロイと認証・ポータル
- GitHub Remote MCP Serverとは?ローカル版との違い・接続手順・OAuth/PAT認証を解説
- CIMD(Client ID Metadata Documents)とは?MCPでDCRに代わるOAuthクライアント登録の仕組みと実装【2026年9月時点】