# MCP進捗通知とキャンセルの非対称設計——2026仕様

> MCPの進捗通知とキャンセレーションは2026-07-28確定仕様でクライアント起点に非対称化された。トランスポート別のキャンセル信号とタイムアウト設計を整理する。

- Canonical: https://kuucorp.com/blog/mcp-progress-cancellation-notification-design/
- Date: 2026-08-20
- Last modified: 2026-08-20
- Publisher: Kuu株式会社 (https://kuucorp.com)

---
長時間かかるMCPツール呼び出しをどう可視化し、途中で止めるか。2026-07-28に確定したMCP仕様は、この2つの基本ユーティリティ「進捗通知」と「キャンセレーション」を、旧仕様の双方向設計からクライアント起点の非対称設計へ作り替えた。ここを誤って実装すると、切断後もサーバー側でツール処理が動き続けたり、進捗UIが更新されないまま処理がハングして見えたりする。

MCPの[プロトコル](/glossary/mcp/)を実装するバックエンドエンジニア・プラットフォームエンジニア向けに、確定仕様の挙動とタイムアウト設計の要点を整理する。

## MCPの進捗通知はどう設計するか

> クライアントが `progressToken` を発行し、サーバーはそれに紐づく `notifications/progress` を任意頻度で返す。progress値は単調増加が必須。

進捗通知の流れはシンプルだ。クライアントがリクエストの `_meta.progressToken` にトークン（文字列または整数）を含めると、サーバーは同じトークンを参照する `notifications/progress` 通知を返せる。ペイロードは `progress`（現在値）・`total`（任意、総量）・`message`（任意、人間可読の説明文）の3フィールドで構成される。仕様は `progress` の値が毎回増加することを必須とし、`total` が不明な場合でもこの制約は変わらない。

2026-07-28確定仕様で明確になったのは、進捗通知が**クライアント起点の一方向**であるという点だ。旧仕様（2025-06-18）は「いずれの当事者も進捗を送れる」とする双方向の書き方だったが、確定仕様は「クライアントが進捗を受け取りたい場合にトークンを含め、サーバーがそれに応じて通知を送ってもよい」という一方向の関係に整理されている。エージェント基盤を設計する際は、サーバー側からクライアントへ一方的に進捗を押し込む設計を前提にしてはならない。

実装上の注意点も仕様に明記されている。双方は有効な進捗トークンを追跡すべきであり、通知の氾濫を防ぐレート制限を双方が実装すべきとされる。加えて、操作完了後は進捗通知を止めなければならない。ポーリング頻度を上げすぎるクライアント実装は、サーバー側のレート制限にすぐ引っかかる設計になっている点を踏まえてリトライ間隔を設計する必要がある。

## MCPのキャンセレーションは2026-07-28仕様でどう変わったか

> キャンセルはトランスポート依存になった。Streamable HTTPはSSEストリームの切断そのものが合図で、`notifications/cancelled` は不要。stdioのみ明示送信が必要。

最大の変更は、キャンセル信号がトランスポートに応じて異なる手段に分岐したことだ。Streamable HTTPでは、クライアントがSSEレスポンスストリームを閉じる行為そのものがキャンセルの合図になり、サーバーはクライアントの切断をそのリクエストのキャンセルとして扱わなければならない。`notifications/cancelled` メッセージは不要かつ想定されていない。一方stdioにはリクエストごとのストリームが存在しないため、クライアントはリクエストIDを参照する `notifications/cancelled` 通知を明示的に送る必要がある。

サーバー側からのキャンセル送信も制限された。確定仕様では、サーバーが `notifications/cancelled` を送ってよいのは `subscriptions/listen` のサブスクリプションストリームを終了する場合に限られ、それ以外の目的で送ってはならないと明記されている。旧仕様の「いずれの側もキャンセル通知を送れる」という汎用的な扱いから、サーバー起点のキャンセルは用途が絞り込まれた形だ。

レースコンディションの扱いは変わっていない。ネットワーク遅延により、処理が完了した後にキャンセル通知が届くケースは避けられず、双方はこれを許容する設計が必須とされる。サーバーは未知のリクエストID・完了済みリクエスト・キャンセル不能なリクエストへの通知を無視してよく、クライアントはキャンセル後に届いた応答を無視すべきとされている。

## タイムアウトとレースコンディションはどう設計すべきか

> 確定仕様はタイムアウト運用を明文化した。進捗通知でタイムアウトの時計をリセットしてよいが、上限は進捗の有無によらず必ず適用する。

2026-07-28仕様で新たに明文化されたのがタイムアウトの運用指針だ。実装は送信するすべてのリクエストにタイムアウトを設定すべきであり、成功・エラーいずれの応答も期限内に届かない場合、送信側はそのリクエストをキャンセルして応答待ちを止めるべきとされる。Streamable HTTPではリクエストのレスポンスストリームを閉じる操作、stdioでは `notifications/cancelled` の送信が、それぞれタイムアウト時のキャンセル手段になる。

進捗通知はタイムアウト設計に組み込める。実装は、対象リクエストに対応する進捗通知を受け取った際にタイムアウトの時計をリセットしてよいとされている——処理が実際に進行している証拠として扱えるからだ。ただし仕様は「進捗通知の有無にかかわらず、不正な動作をするクライアント・サーバーの影響を抑えるため、最大タイムアウトは常に適用すべき」と釘を刺している。進捗通知だけを頼りに無制限リトライを許す設計は避け、上限値を必ず併設する必要がある。

もう一つの関連変更が、SSEストリームの再開可能性（`Last-Event-ID` ヘッダーとイベントID）の削除だ。ストリームが切断された場合、進行中だったリクエストはその時点で失われ、クライアントは新しいリクエストIDで再発行しなければならない。これはキャンセレーションの設計と表裏一体で、切断＝キャンセルという単純化と引き換えに、再接続によるリクエストの自動再開は仕様から失われた。エンタープライズ環境でLLMゲートウェイやAPIゲートウェイ経由でMCPトラフィックをルーティングする場合、この切断時セマンティクスをゲートウェイの再試行ロジックに正しく反映させておく必要がある。

セッション廃止・`server/discover`・Tasks拡張への移行など仕様全体の変更点は[MCP 2026-07-28確定仕様の移行ガイド](/blog/mcp-2026-stateless-spec-migration-guide/)で解説している。MCPサーバーの実装設計は[MCPサーバー実装ガイド](/blog/mcp-server-implementation-tool-design/)、複数チームのMCPトラフィック統制は[LLMゲートウェイ設計](/blog/llm-gateway-routing-rate-limiting/)も参照してほしい。

## 参考

- [Progress | Model Context Protocol](https://modelcontextprotocol.io/specification/2026-07-28/basic/utilities/progress)
- [Cancellation | Model Context Protocol](https://modelcontextprotocol.io/specification/2026-07-28/basic/utilities/cancellation)
- [Key Changes | Model Context Protocol](https://modelcontextprotocol.io/specification/2026-07-28/changelog)

## まとめ

MCPの進捗通知とキャンセレーションは、2026-07-28確定仕様でクライアント起点の非対称設計に整理された。進捗はクライアントがトークンを発行しサーバーが応答する一方向、キャンセルはトランスポート依存（Streamable HTTPはストリーム切断、stdioは明示通知）で、サーバー起点のキャンセル送信は `subscriptions/listen` の終了用途に限定される。タイムアウトは進捗通知でリセットしてよいが上限は必ず設ける——この3点を押さえずに実装すると、切断後もサーバー側処理が残留したり、進捗UIがハングして見えたりする不具合につながる。複数チームでMCPインフラを運用する規模のプラットフォーム設計は、[Kuuの RDE サービス](https://kuucorp.com/services/rde/)でも技術支援している。
