2026-07-17
この記事をシェア
2026年7月28日、Model Context Protocolの新しい仕様である「MCP 2026-07-28」が正式公開された。
今回の更新は、単にAPIがいくつか追加されたという規模ではない。MCPの通信モデルそのものが、従来の「初期化してセッションを維持する双方向プロトコル」から、「各リクエストが独立して完結するステートレスなリクエスト/レスポンス型プロトコル」へと大きく変更されている。
主な変更点は次のとおりだ。
initializeとinitializedの廃止Mcp-Session-Idの廃止- リクエスト単位でのクライアント情報・Capability伝達
- Multi Round-Trip Requests、以下MRTRの導入
Mcp-MethodとMcp-NameによるHTTPヘッダールーティング- ツールやリソース一覧のキャッシュ対応
- TasksやMCP Appsを収容する正式な拡張仕様
- OAuth認可仕様の強化
- Roots、Sampling、Logging、旧HTTP+SSEトランスポートの非推奨化
公式ブログでも、今回のリリースは「ステートレスなプロトコルコア」「MRTR」「ヘッダーベースのルーティング」「キャッシュ可能な一覧結果」「認可強化」「正式な拡張フレームワーク」をまとめて導入する更新として説明されている。
この記事では、前半で旧仕様との違いを整理し、後編ではTypeScript SDK v2による実装を扱う。
- デプロイ計画を作成する
- 計画情報を署名付きトークンとしてクライアントへ返す
- 実行前にMRTRでユーザーへ確認を求める
- 承認された場合だけデプロイを実行する
第1部:MCP 2026-07-28の基本仕様
1. 旧仕様との比較
今回の変更対象となる直前の仕様は、MCP 2025-11-25だ。
大まかな違いを表にすると、次のようになる。
| 項目 | MCP 2025-11-25以前 | MCP 2026-07-28 |
|---|---|---|
| 接続開始 | initializeを実行 |
原則不要 |
| 初期化完了通知 | notifications/initialized |
廃止 |
| セッション識別 | Mcp-Session-Id |
廃止 |
| Capability | 初期化時に交換 | 各リクエストの_metaに格納 |
| バージョン確認 | initializeで交渉 |
server/discoverを任意実行 |
| サーバーからクライアントへの要求 | 開いているストリーム上で送信 | MRTRとして一度レスポンスを返す |
| Elicitation | サーバー主導のelicitation/create |
input_requiredと再リクエスト |
| HTTPルーティング | JSONボディの解析が必要 | Mcp-Method、Mcp-Nameヘッダー |
| 一覧キャッシュ | 明示的な共通仕様なし | ttlMs、cacheScope |
| 変更通知 | GETや個別Subscribe API | subscriptions/listen |
| SSE再開 | Last-Event-IDで再開 |
廃止。新しいIDで再実行 |
| Tasks | 実験的なコア機能 | 正式な拡張仕様 |
| Logging設定 | セッション単位 | リクエスト単位 |
| 状態管理 | セッションに保持しやすい | 明示的なハンドルを引数として渡す |
仕様変更の完全な一覧では、プロトコルレベルのセッションとMcp-Session-Id、初期化ハンドシェイク、SSE再開機構などが削除され、代わりに各リクエストへプロトコルバージョンやCapabilityを格納する設計へ移行したことが明記されている。
2. 最大の変更は「ステートレス化」
従来のMCP
旧仕様では、クライアントは最初にinitializeを送信した。
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {
"elicitation": {}
},
"clientInfo": {
"name": "example-client",
"version": "1.0.0"
}
}
}
サーバーがこれに応答し、クライアントがさらに次の通知を送る。
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
Streamable HTTPの場合、以後のリクエストでは次のようなセッションIDが使われていた。
Mcp-Session-Id: 8073f1a8-4e2d-4dbf-8c25-...
この仕組みは、サーバーインスタンスや共有ストレージにセッション情報を保持しやすい反面、ロードバランサー配下での水平スケーリングを複雑にする。
たとえば、次のどれかが必要になる。
- スティッキーセッション
- Redisなどの共有セッションストア
- 同じ接続を維持するためのルーティング制御
- インスタンス終了時のセッション移行
- 長時間接続を考慮したデプロイ方式
新仕様のMCP
MCP 2026-07-28では、initialize、notifications/initialized、Mcp-Session-Idが廃止された。
その代わりに、各リクエストが自分自身を説明する。
概念的には、次のようなリクエストになる。
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
{
"jsonrpc": "2.0",
"id": 10,
"method": "tools/call",
"params": {
"name": "search",
"arguments": {
"query": "MCP 2026-07-28"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {
"elicitation": {
"form": {}
}
},
"io.modelcontextprotocol/clientInfo": {
"name": "example-client",
"version": "1.0.0"
}
}
}
}
重要なのは、サーバーがこのリクエストだけを見れば処理できることだ。
直前までどのインスタンスと通信していたかは関係ない。
1回目のリクエストがECSタスクAへ届き、2回目がECSタスクBへ届いてもよい。通常のラウンドロビンロードバランシングで処理でき、プロトコルセッションを共有するためだけのRedisも不要になる。
公式仕様では、クライアントのCapability、プロトコルバージョン、クライアント情報をリクエストごとの_metaへ格納し、サーバー側も結果の_metaでサーバー情報を返す設計になっている。
3. ステートレスと「状態を持たない」は同じではない
ここは誤解しやすい。
新仕様がステートレスなのは、あくまでプロトコルレベルだ。
アプリケーションが状態を持ってはいけない、という意味ではない。
たとえば、次のような処理では状態が必要になる。
- ユーザーが検索条件を登録する
- サーバーが大量の検索処理を実行する
- 次のツール呼び出しで検索結果を取得する
この場合、サーバーは検索処理を識別するハンドルを返せばよい。
{
"searchHandle": "search_01K1M6WFXE8WJ2..."
}
次の呼び出しでは、そのハンドルを通常のツール引数として渡す。
{
"name": "get_search_result",
"arguments": {
"searchHandle": "search_01K1M6WFXE8WJ2..."
}
}
状態そのものは、次のような場所へ保存できる。
- DynamoDB
- PostgreSQL
- Redis
- S3
- オブジェクトストレージ
- 署名付きトークン
- 暗号化トークン
旧仕様では、状態が暗黙的にセッションへ結び付けられがちだった。
新仕様では、モデルやクライアントから見える明示的なハンドルとして表現する。公式ブログでも、複数呼び出しにまたがる状態が必要な場合は、サーバーが明示的なハンドルを発行し、通常のツール引数として渡す方法が推奨されている。
僕はこの変更を、単なる実装都合ではなく、MCPツールのデータフローを明示化する変更だと考えている。
4. server/discoverは何をするのか
initializeはなくなったが、クライアントがサーバーの情報を事前に知りたいケースは残る。
そのために追加されたのがserver/discoverだ。
サーバーはserver/discoverを実装しなければならない。一方、クライアントが毎回呼び出す必要はない。
クライアントは、用途に応じて次の方法を選択できる。
新仕様に固定する
versionNegotiation: {
mode: {
pin: "2026-07-28"
}
}
対象サーバーが2026-07-28を提供しなければ、接続を失敗させる。
新旧を自動判定する
versionNegotiation: {
mode: "auto"
}
この場合、SDKは最初にserver/discoverを試す。
新仕様のサーバーであればmodern eraとして接続し、旧仕様のサーバーであればinitializeへフォールバックする。TypeScript SDK v2では、2025年以前の仕様をlegacy、2026-07-28以降をmodernという振る舞いの単位で扱っている。
ここでいうconnect()は、必ずしもMCPセッションを作る処理ではない。
HTTPトランスポートや認証、利用するプロトコル世代を準備するクライアントSDK上の操作、と捉えたほうがよい。
5. MRTR:双方向通信をリクエスト/レスポンスへ変換する
今回、ステートレス化と並んで重要なのがMulti Round-Trip Requests、MRTRだ。
従来のサーバー主導リクエスト
旧仕様では、ツール処理中にユーザー入力が必要になると、サーバーからクライアントへelicitation/createを送ることができた。
イメージとしては次の流れだ。
Client Server
| tools/call |
|---------------------->|
| |
| elicitation/create |
|<----------------------|
| elicitation result |
|---------------------->|
| |
| tool result |
|<----------------------|
この方式では、元のリクエストを処理している間、双方向通信可能なストリームを維持する必要がある。
MRTRの流れ
新仕様では、サーバーはクライアントへ直接リクエストをプッシュしない。
代わりに、いったんinput_requiredという結果を返す。
Client Server
| tools/call |
|---------------------->|
| |
| input_required |
|<----------------------|
| |
| tools/call + answers |
|---------------------->|
| |
| complete |
|<----------------------|
最初のレスポンスは次のようになる。
{
"jsonrpc": "2.0",
"id": 10,
"result": {
"resultType": "input_required",
"inputRequests": {
"approval": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "本番環境へデプロイしますか?",
"requestedSchema": {
"type": "object",
"properties": {
"approve": {
"type": "boolean",
"title": "デプロイを承認する"
}
},
"required": [
"approve"
]
}
}
}
}
}
}
クライアントはユーザーにフォームを表示し、その回答を付けて元のツールを再実行する。
{
"jsonrpc": "2.0",
"id": 11,
"method": "tools/call",
"params": {
"name": "execute_deployment",
"arguments": {
"planToken": "eyJpZCI6..."
},
"inputResponses": {
"approval": {
"action": "accept",
"content": {
"approve": true
}
}
}
}
}
重要なのは、2回目が別のJSON-RPCリクエストであることだ。
- JSON-RPC IDも新しくなる
- 別のHTTP接続でもよい
- 別のサーバーインスタンスへ到達してもよい
- 元の引数は再送される
- 回答は
inputResponsesへ格納される
つまり、対話性を保ちながら、サーバーの待機状態をなくしている。
MRTRは従来のelicitation/create、sampling/createMessage、roots/listなどのサーバー主導要求を置き換える仕組みとして導入された。サーバーはInputRequiredResultを返し、クライアントが回答を付加して元のリクエストを再試行する。
6. すべての結果にresultTypeが付く
MCP 2026-07-28では、結果にresultTypeが必須となった。
通常の完了結果は次のとおり。
{
"resultType": "complete",
"content": [
{
"type": "text",
"text": "処理が完了しました"
}
]
}
追加入力が必要な場合は次のとおり。
{
"resultType": "input_required",
"inputRequests": {
"approval": {
"method": "elicitation/create",
"params": {
"message": "実行しますか?"
}
}
}
}
SDKを利用する場合、多くのケースではSDKが適切なresultTypeを付けるため、ツールハンドラーが毎回直接設定する必要はない。
ただし、独自SDKや低レベル実装を作っている場合は、レスポンススキーマの変更に注意が必要だ。
旧サーバーがresultTypeを返さない場合、クライアントは後方互換性のためcompleteとして扱う。
7. Mcp-MethodとMcp-Nameによるルーティング
新しいStreamable HTTPでは、POSTリクエストに次のヘッダーが付く。
Mcp-Method: tools/call
Mcp-Name: execute_deployment
ツール一覧の場合は、概念的に次のようになる。
Mcp-Method: tools/list
ツール呼び出しの場合は、メソッド名に加えて対象ツール名も分かる。
Mcp-Method: tools/call
Mcp-Name: search
これにより、API Gateway、ALB、Envoy、Cloudflare、WAF、サービスメッシュなどがJSONボディを解析しなくても、次の処理を実施できる。
- ツール単位のアクセス制御
- ツール単位のレート制限
- 特定ツールだけ別サービスへルーティング
- メソッド単位のログ分類
- 破壊的ツールの追加認証
- 利用量メトリクスの収集
- シークレットスキャン対象の振り分け
例えば、概念的には次のようなポリシーを作れる。
Mcp-Method = tools/call
AND
Mcp-Name = delete_repository
この条件に一致した場合だけ、追加認証や厳しいレート制限を適用できる。
GitHub MCP Serverでは、新仕様への移行によってリクエストボディの詳細解析を減らし、必須HTTPヘッダーからログやシークレットスキャンに必要な情報を取得できるようになったとしている。さらにRedisベースのセッションも削除している。
AWS上でMCPサーバーを運用する場合、この変更はかなり大きい。
従来は、WAFやAPI Gatewayでツール名を判断しようとするとJSONボディを解析する必要があった。今後は、ヘッダー条件を使って、MCP固有のルーティングや認可を通常のHTTPインフラへ組み込みやすくなる。
ただし、ヘッダーをクライアントが自由に設定できる構成では、ヘッダーだけを認可判断の根拠にしてはいけない。
サーバー側でもJSON-RPCボディとの整合性を検証する必要がある。仕様では、ヘッダーとボディの不一致に対するHeaderMismatchErrorも定義されている。
8. ツール一覧がキャッシュ可能になった
MCPクライアントは、サーバーへ接続した後に次の一覧を取得することが多い。
tools/listprompts/listresources/listresources/templates/listresources/read
新仕様では、これらの結果へ次のキャッシュ情報が付く。
{
"ttlMs": 300000,
"cacheScope": "private"
}
ttlMsは、結果を新鮮なものとして扱える期間をミリ秒で示す。
{
"ttlMs": 300000
}
これは5分間キャッシュ可能という意味になる。
cacheScopeには、主に次の値がある。
{
"cacheScope": "private"
}
または、
{
"cacheScope": "public"
}
privateは、ユーザーや認証コンテキストによって結果が変化する可能性がある場合に使う。
publicは、共有キャッシュに保存しても問題ない結果に使う。
さらに、サーバーは一覧を決定的な順序で返すことが推奨されている。
例えば、同じツール集合を毎回ランダムな順序で返してはいけない。
悪い例
2回目:
- search
- delete
- create
3回目:
- create
- search
- delete
ツール名でソートするなどして、毎回同じ順序にする。
良い例
毎回:
- create
- delete
- search
これはHTTPキャッシュだけでなく、LLMのプロンプトキャッシュにも関係する。
ツール定義の並び順が変わるだけでプロンプト全体が変化すると、LLM側のキャッシュヒット率が下がる可能性があるからだ。
MCP 2026-07-28では、対象となる一覧・読み取り結果にttlMsとcacheScopeを必須化し、ツール一覧を決定的な順序で返すことも推奨している。
9. 通知はsubscriptions/listenへ集約された
ステートレス化したからといって、ストリーミング機能が完全になくなったわけではない。
サーバー側の変更を購読したい場合は、新しいsubscriptions/listenを利用する。
クライアントは、必要な通知タイプを指定して購読する。
代表的な通知タイプは次のとおりだ。
toolsListChangedpromptsListChangedresourcesListChangedresourceSubscriptions
概念的には次のようになる。
{
"jsonrpc": "2.0",
"id": 20,
"method": "subscriptions/listen",
"params": {
"subscriptions": {
"toolsListChanged": true,
"resourcesListChanged": true
}
}
}
これは長時間維持されるPOSTレスポンスストリームとなる。
ただし、ツール処理中の進捗通知やログなど、特定リクエストに紐づく通知は、subscriptions/listenではなく、そのリクエスト自身のレスポンスストリームへ流れる。
旧仕様のHTTP GETエンドポイント、resources/subscribe、resources/unsubscribeは、この仕組みに置き換えられた。
10. SSEの再開と再配信が廃止された
旧Streamable HTTPでは、SSEイベントIDとLast-Event-IDを利用した再開処理が定義されていた。
新仕様では、この再開機構が削除された。
レスポンスストリームが途中で切断された場合、クライアントは同じリクエストを継続するのではなく、新しいJSON-RPC IDを付けて再実行する。
{
"jsonrpc": "2.0",
"id": 101,
"method": "tools/call",
"params": {
"name": "execute_payment",
"arguments": {
"paymentId": "payment_123"
}
}
}
再試行時には次のようになる。
{
"jsonrpc": "2.0",
"id": 102,
"method": "tools/call",
"params": {
"name": "execute_payment",
"arguments": {
"paymentId": "payment_123"
}
}
}
この変更によって、ツール実装側では冪等性がさらに重要になる。
例えば、次のようなツールは、単純に再実行すると二重処理を起こす。
- 決済
- メール送信
- 本番デプロイ
- GitHub Issue作成
- ユーザー登録
- リソース削除
破壊的ツールや外部副作用を持つツールでは、次のような冪等キーを引数に含めたほうがよい。
{
"name": "execute_payment",
"arguments": {
"paymentId": "payment_123",
"idempotencyKey": "order_987-payment-v1"
}
}
サーバー側では、既に処理済みか確認する。
const existing = await repository.findByIdempotencyKey(
args.idempotencyKey
);
if (existing) {
return existing.result;
}
ストリーム切断時には元の処理が失われたものとして、クライアントが新しいリクエストIDで再発行することが仕様上求められている。
11. Tasksはコアから正式な拡張へ移動した
長時間処理を扱うTasksは、実験的なコア機能から、次の正式な拡張仕様へ移動した。
io.modelcontextprotocol/tasks
拡張仕様では、主に次のAPIが使われる。
tasks/get
tasks/update
旧仕様のtasks/resultによるブロッキング取得は、tasks/getによるポーリングへ置き換えられた。
また、tasks/updateによってクライアントから実行中タスクへ追加情報を渡せる。
概念的には次の流れになる。
tools/call
↓
taskHandleを返す
↓
tasks/get
↓
まだ実行中
↓
tasks/get
↓
完了結果
例えば、数十分かかるコード解析を開始する。
{
"name": "start_repository_analysis",
"arguments": {
"repository": "example/project"
}
}
サーバーはタスクハンドルを返す。
{
"task": {
"id": "task_01K1M7...",
"status": "working"
}
}
クライアントは後から取得する。
{
"jsonrpc": "2.0",
"id": 31,
"method": "tasks/get",
"params": {
"taskId": "task_01K1M7..."
}
}
Tasksをコアから切り離した理由は、MCPの最小コアをステートレスで単純なものに保ちつつ、長時間処理などの高度な機能をバージョン付き拡張として進化させるためだと考えられる。
MCP Apps、Tasks、Enterprise Managed Authorizationなどは、新しい正式な拡張フレームワーク上で扱われる。
12. Roots、Sampling、Loggingは非推奨になった
今回、次の機能が非推奨になった。
- Roots
- Sampling
- Logging
- HTTP+SSEトランスポート
- 一部の
includeContext
削除されたわけではない。
新しい非推奨ポリシーにより、少なくとも12か月の移行期間が設けられる。ただし、新規実装では採用しないことが推奨されている。
Rootsの代替
ディレクトリやファイルは、次の方法で明示的に渡す。
{
"name": "analyze_project",
"arguments": {
"repositoryUri": "file:///workspace/example"
}
}
またはリソースURIやサーバー設定を利用する。
Samplingの代替
MCPサーバーからホスト側モデルへSamplingを要求するのではなく、サーバー自身が利用するLLMプロバイダーへ直接接続する。
const response = await llmClient.generate({
model: "example-model",
messages
});
これは、認証、モデル選択、コスト管理、監査などをサーバー側で明示的に行う設計になる。
Loggingの代替
stdioサーバーではstderrへ出力する。
console.error("[mcp-server] tool execution started");
HTTPサーバーではOpenTelemetryや通常の構造化ログを利用する。
logger.info({
tool: "execute_deployment",
requestId,
clientId,
durationMs
});
新仕様では、OpenTelemetryのtraceparent、tracestate、baggageを_metaで伝播する規約も定義された。
13. 認可仕様の強化
認可関連では、主に次の変更が入った。
Issuerの検証
認可レスポンスに含まれるissを検証する。
iss=https://auth.example.com
認可コードを発行したIssuerと、トークン交換先のIssuerが一致しているか確認する。
これはAuthorization Server Mix-Up攻撃を防ぐための変更だ。
クライアント資格情報をIssuer単位で保存する
次のような保存方法は危険だ。
{
"client_id": "abc",
"client_secret": "xyz"
}
どのIssuerが発行した資格情報か分からないからだ。
次のようにIssuerをキーへ含める。
{
"https://auth.example.com": {
"client_id": "abc",
"client_secret": "xyz"
}
}
別の認可サーバーへ同じ資格情報を再利用してはいけない。
DCRからCIMDへ
Dynamic Client Registrationは非推奨となり、Client ID Metadata Documentsへ移行する方向が示されている。
ただし、DCRは後方互換性のため現時点では利用できる。
今回の認可更新では、RFC 9207に基づくIssuer検証、Issuerごとの資格情報管理、デスクトップ・CLI向けapplication_type、DCRからClient ID Metadata Documentsへの移行が含まれている。
14. GitHubとClaudeの対応状況
GitHub MCP Serverは、正式リリース前から新仕様をサポートしていた。
GitHubの説明によると、移行によって次の変更が行われている。
- Redisセッションを削除
initialize時のDB書き込みを削除- 通常リクエスト時のセッションDB読み取りを削除
- JSONボディの詳細解析をHTTPヘッダー参照へ変更
- Elicitationを新旧両方式へ対応
これは、今回の仕様変更が実際の大規模MCPサーバー運用にどのような効果を与えるかを示す具体例だ。
Anthropicも、MCP 2026-07-28への対応をClaude製品群へ順次展開すると発表している。
Claude側では、新仕様に加えて次のようなMCP関連機能が提供されている。
- 会話内にUIを表示するMCP Apps
- 組織単位で設定するEnterprise-managed auth
- Connector向けObservability
- プライベートネットワークへ接続するMCP tunnels
Claude製品全体への新仕様対応は順次展開されるとされているため、クライアントごとの対応状況は利用時点で確認したほうがよい。
15. 旧MCPサーバーからの移行チェックリスト
既存サーバーを新仕様へ移行する場合、僕なら次の順序で確認する。
通信部分
initializeへの依存を取り除くnotifications/initializedへの依存を取り除くMcp-Session-Idへの依存を洗い出す- Streamable HTTPへ移行する
- HTTP+SSEを新規採用しない
- SSE再開処理への依存を取り除く
状態管理
- セッションに保存している値を一覧化する
- 明示的なツール引数へ変換できるか確認する
- 必要に応じてサーバー発行ハンドルへ置き換える
- ハンドルの有効期限を定義する
- ハンドルと認証ユーザーを紐付ける
- 冪等性キーを導入する
Capability
- 初期化時に保存していたCapability参照を見直す
- リクエスト単位のCapabilityを参照する
- クライアント情報を認証IDとして扱わない
server/discoverへ対応する
対話処理
- サーバー主導ElicitationをMRTRへ移行する
- 追加入力を
input_requiredとして返す - 再実行時の
inputResponsesを検証する - MRTRの最大ラウンド数を制限する
- キャンセルと拒否を区別する
運用
Mcp-MethodとMcp-Nameをログへ記録する- ヘッダーとJSONボディの一致を検証する
- ツール一覧を決定的な順序にする
ttlMsとcacheScopeを適切に設定する- OpenTelemetryコンテキストを伝播する
- 公式Conformance TestをCIに組み込む
カテゴリ
AIエージェント基盤公開日
2026-07-29
💬 無料技術相談のご案内
この記事でご紹介した技術について、導入や活用のご相談を30分間無料で承っております。
- 「自社でも導入できる?」といった技術的な疑問
- 既存システムとの連携・移行に関するご相談
- コスト感や導入スケジュールの目安
30年以上のIT経験をもとに、率直にお答えします。強引なセールスや勧誘は一切ありません。
野口真一
