Claude Media
MCPのCancellationとは — リクエストを安全に中断する仕組み

MCPのCancellationとは — リクエストを安全に中断する仕組み

MCPのCancellation仕様がリクエストの中断をどう定義しているか、notifications/cancelledの流れとstdio/Streamable HTTPでの挙動の違いを解説します。

なぜこの仕様がいま重要なのか

MCPの2026-07-28リビジョンは、プロトコルの土台を大きく作り変えました。initialize ハンドシェイクとセッションを廃止し、リクエストごとにプロトコルバージョンとクライアント情報を載せるステートレスな設計に切り替えています。以前の2025-11-25までの仕様は「セッションを開いて、その中でやり取りする」モデルでしたが、いまはリクエスト1本1本が独立しています。

この土台の変更は、Cancellationのような細部の仕様にも波及します。セッションという単位がなくなった以上、「このセッションのリクエストを全部中断する」という発想は成立しません。中断は常に個々のリクエストID単位で行う、という前提がこれまで以上にはっきり書き直されています。MCPサーバーやクライアントSDKを自作している開発者にとっては、読み流せない変更点です。

古いSDKをそのまま使い続けている実装は、この前提の変化に気づかないまま動いていることも珍しくありません。プロトコルバージョンをまたいで動く実装を書く場合は、セッション単位の考え方を残していないか、自分のコードを一度洗い直す価値があります。

MCPのCancellationとは何か

MCP(Model Context Protocol)のCancellationは、クライアントが送ったリクエストを処理の途中で打ち切るための通知の仕組みです。notifications/cancelled という1つの通知メソッドだけで成り立っています。

MCPのリクエストはもともと非同期です。クライアントがツール呼び出しを送ってからサーバーが応答するまでの間に、ユーザーが操作を取り消したり、タイムアウトが先に来たりすることがあります。Cancellationはこの「もう結果はいらない」という意思表示を、サーバー側に伝える手段です。

サーバー側が中断できるのは、あくまで通知を受け取ったあとの話です。JSON-RPCの通知はレスポンスを持たない一方向のメッセージなので、Cancellationは「送ったら終わり」の一方向通知(fire-and-forget)として設計されています。届く保証も、間に合う保証もありません。

notifications/cancelledはどう流れるか

クライアントは中断したいリクエストのIDと、任意の理由文字列を添えて通知を送ります。

{
  "jsonrpc": "2.0",
  "method": "notifications/cancelled",
  "params": {
    "requestId": "123",
    "reason": "User requested cancellation"
  }
}

仕様が定める挙動要件は次の5点です。

項目内容
対象範囲内容クライアントが以前に送った、まだ処理中と思われるリクエストだけを参照する
サーバーの対応内容処理を止め、確保していたリソースを解放し、以後レスポンスを返さない
無視してよい場合内容未知のリクエストID・処理完了済み・そもそも中断不可能な処理
クライアントの対応内容中断後に届いたレスポンスは無視してよい
ログ内容両者とも中断理由をデバッグ用に記録することが望ましい

サーバーがこの通知を無視しても仕様違反にはなりません。中断できない処理(すでに外部APIに書き込み済みなど)を抱えるサーバーは、リクエストを最後まで走らせてから応答を破棄するだけでよい設計です。

サーバーからクライアントへ向けて notifications/cancelled を送るケースは、用途が1つに絞られています。subscriptions/listen で開いた購読ストリームを、サーバー側の都合(シャットダウンなど)で終了するときだけです。それ以外の目的でサーバーがこの通知を送ることは仕様上禁止されています。クライアントが送るリクエスト中断の通知と、サーバーが送るストリーム終了の通知は、同じメソッド名を使いながら役割が非対称になっている点に注意が必要です。

stdioとStreamable HTTPで中断の合図が違う

トランスポートによって、Cancellationの伝え方がまったく異なります。ここがこの仕様で一番間違えやすいところです。

Streamable HTTPでは、SSEレスポンスストリームを閉じること自体が中断の合図です。サーバーはクライアントの切断を検知したら、それだけでそのリクエストが中断されたとみなさなければなりません。notifications/cancelled を別送りする必要はありません。

stdioでは話が変わります。標準入出力にはリクエストごとの独立したストリームが存在しないため、通知を明示的に送る以外に中断を伝える手段がありません。stdioのクライアントは、中断したいリクエストIDを乗せた notifications/cancelled を必ず送ります。

同じ「中断」という操作が、トランスポートが変わるだけで「ストリームを閉じる」と「通知を送る」の2通りに分かれます。この違いを知らずに実装すると、stdioサーバーだけ中断が効かないという不具合を踏みやすくなります。接続そのものが不安定なときの切り分け方はMCPサーバーに接続できないときの切り分け手順にまとめています。

タイムアウトはCancellationの主要な発火源

仕様はすべてのリクエストにタイムアウトを設けることを推奨しています。応答が一定時間内に来なければ、送信側はそのリクエストを中断し、待つのをやめることが推奨されています。

進捗通知(notifications/progress)を受け取った場合に限り、タイムアウトの時計をリセットしてよいという緩和もあります。ただし実装は、進捗通知の有無にかかわらず上限のタイムアウトを必ず設ける、とも定められています。進捗を送り続けるだけで処理を無限に引き延ばせてしまうと、暴走したサーバーやクライアントへの歯止めが効かなくなるためです。

タイムアウト後の動作は、前節のトランスポート差分とそのままつながります。Streamable HTTPならストリームを閉じ、stdioなら notifications/cancelled を送る。タイムアウト機構を自作するなら、この分岐を意識して実装する必要があります。

Claude CodeのMCPタイムアウトは中断まで明かさない

Claude CodeはMCPサーバーへのツール呼び出しに複数のタイムアウトを持ちます。MCP_TOOL_TIMEOUT によるハードリミット(既定は約28時間)、サーバー種別ごとのアイドルタイムアウト(HTTP/SSE/WebSocketは5分、stdioは30分が既定)、そして .mcp.jsontimeout フィールドによるサーバー単位の上書きです。ミリ秒単位の環境変数として指定します。

# MCPツール実行のハードリミットを10分に短縮する例
MCP_TOOL_TIMEOUT=600000 claude

公式ドキュメントは、アイドルウィンドウを超えたツール呼び出しが「エラーで中断(abort)される」と説明しています。ただし、その中断がstdioサーバーに対して notifications/cancelled を送る形で実装されているのか、単にレスポンス待ちを打ち切ってサーバー側には何も伝えないのかまでは明記していません。

これは製品ドキュメントとしては珍しいことではありません。Cancellationはそもそも送りっぱなしの通知で、届かなくても仕様違反にならない設計です。実務上の含意ははっきりしています。自作MCPサーバーは、クライアントからの明示的な通知が来なくても、応答不要になったリクエストを自分でタイムアウトさせる作りにしておくのが安全です。Claude Code側の中断が届く前提で設計すると、リソースを解放し損ねる余地が残ります。

自作サーバーの実装や動作確認には、MCP Inspectorの使い方でリクエストのやり取りを直接見ながら進める方法がまとまっています。MCP全体の仕組みを先に押さえたい場合はMCPとはを、タイムアウトの既定値を細かく知りたい場合はMCP_TIMEOUTとMCP_TOOL_TIMEOUTの違いを参照してください。

実装で踏みやすい落とし穴

Cancellationを自作サーバー・クライアントに組み込むとき、仕様の細部を見落とすと動作が不安定になりやすい箇所があります。とくにトランスポートの分岐とタイムアウト処理は、実装を先に書いてから仕様書に戻ると手戻りが大きくなりがちな部分です。あらかじめ次の点だけでもチェックリスト化しておくと、実装レビューの時間を減らせます。

  • 競合状態への対応漏れ: ネットワーク遅延により、処理完了後や応答送信後に中断通知が届くことがあります。サーバーは「もう終わった処理への中断通知」を安全に無視できる作りにしておく必要があります
  • 中断できない処理での誤動作: 外部システムへの書き込みなど後戻りできない処理は、中断通知を受けても最後まで実行し、レスポンスだけ返さないようにする
  • サーバー発の中断を誤用しない: サーバー側から送る notifications/cancelled は、subscriptions/listen のストリームを終了する目的専用です。それ以外の用途でサーバーがこの通知を送ってはいけません
  • stdioでの通知漏れ: HTTPの実装をそのままstdioに移植すると、ストリームを閉じるだけで満足してしまいがちです。stdioでは通知の明示送信が必須です
  • タイムアウトの二重管理: 進捗通知でタイムアウトの時計をリセットする実装は便利ですが、上限を設けずに延長し続けると、暴走したサーバーを検知できなくなります。リセットする場合も必ず絶対上限を並行して持たせます
  • ログを取らずに運用する: 中断理由の文字列はデバッグでしか使いませんが、記録していないと「なぜあのリクエストだけ応答が来なかったのか」を後から追えなくなります

まとめ

MCPのCancellationは、notifications/cancelled という1つの通知だけで構成されるシンプルな仕組みです。ただしその意味はトランスポートによって変わります。Streamable HTTPはストリームを閉じるだけ、stdioは通知を明示的に送る、という違いを押さえておく必要があります。

サーバーが送る側に回る notifications/cancelled は、subscriptions/listen のストリームを終了する用途に限定されている点も見落としやすいところです。クライアントからサーバーへの中断依頼と、サーバーからクライアントへのストリーム終了通知を、同じメソッド名のまま混同しないようにコードを書く必要があります。

自作のMCPサーバーを書く人は、クライアントからの中断通知が届くことを前提にせず、自分自身のタイムアウトで不要な処理を打ち切れる設計にしておくと安全です。Claude CodeのようなクライアントのMCP実装がどこまでCancellationの語彙を使っているかは、公式ドキュメントの更新を待つことになります。

この記事を共有:XはてブLinkedIn
MCP をもっと見る →