Azure Functions で MCP の SSE エンドポイント(/runtime/webhooks/mcp/sse)が、さっきまで動いていたのに突然 404 になる――。この症状はコードやアクセスキーではなく、拡張バンドルが取得できず関数自体が読み込まれていないことが原因のケースがあります。
現象:MCP エンドポイントが一斉に HTTP 404 になる
Azure Functions 上に MCP 用の Function App をデプロイし、以下のようなエンドポイントでクライアントから接続していたとします。
https://<アプリ名>.azurewebsites.net/runtime/webhooks/mcp/sse?code=xxxxxxxx
数時間前までは正常に SSE(Server-Sent Events)のストリーミングが返っていたのに、あるタイミングからすべてのリクエストが HTTP 404になってしまう。しかもコード変更やデプロイはしていない、という状況です。
| 観測できる症状 | よくある誤解 | 本当に起きていること(今回のパターン) |
|---|---|---|
| /runtime/webhooks/mcp/sse が 404 | アクセスキー(code)が間違い | 関数が読み込まれておらず、ルート自体が存在しない |
| Azure ポータルの「関数」一覧が空/開けない | ポータル不調 | ホスト起動時に拡張が読めず、関数定義が生成されていない |
| ローカルの func start で No job functions found | ビルド漏れ・関数名のミス | Extension Bundle が落ちず、バインディング拡張がロードされていない |
切り分け:404 は「鍵の問題」ではなく「関数が存在しないサイン」
HTTP ステータスは原因推定の近道になります。とくに /runtime/webhooks 配下のようなエンドポイントでは、401/403 と 404 で意味が大きく違うことが多いです。
| ステータス | 起こりやすい原因 | 最初に確認するポイント |
|---|---|---|
| 401 / 403 | キー(code)不一致、権限不足 | Function Key / Host Key の種類、値、付与場所 |
| 404 | ルート未登録、関数未認識、ホストが「関数ゼロ」で起動 | ホスト起動ログ(拡張バンドル、No job functions found) |
| 500 | 関数は存在するが処理中に例外 | Function のログ、例外スタック、依存サービス |
| 502 / 503 | ホスト停止、起動失敗、プラン側の問題 | プラットフォーム障害、再起動、ヘルスチェック |
ログが示すヒント:Extension Bundle の ZIP ダウンロードが 404
今回の決定打は、ローカルで func start したときに出る起動ログです。典型的には次のような流れになります。
- Experimental/Preview の Extension Bundle を ZIP でダウンロードしようとする
- 取得先が 404(NotFound)で落ちる
- 結果として
Unable to find or download extension bundleが出る - 拡張がロードできないため
No job functions found.となる
ポイントは「関数コードが壊れた」より前に、ランタイムが必要な拡張を持ってこれていないことです。Azure Functions はトリガーやバインディングを拡張として読み込むため、拡張の取得・展開に失敗すると、関数が 0 件として扱われてしまいます。その結果、いつも返っていた /runtime/webhooks/mcp/sse もルート未登録になり、404 を返します。
Extension Bundle とは何か(なぜ 1 つ落ちると全部 404 になるのか)
Extension Bundle は、Azure Functions が使う各種バインディング拡張を「まとめて配布する ZIP」のようなものです。Node.js / Python / PowerShell などでは、個別の拡張 DLL を自分で管理するより、host.json にバンドル ID とバージョンレンジを書いて自動取得する方式が一般的です。
一方で、起動時にバンドルを取得できないと、HTTP トリガーを含む複数の拡張が一気にロードできなくなり、結果として関数定義(function.json 相当)が生成されず、関数が 0 件扱いになることがあります。これが「突然、全部 404」につながる仕組みです。
| レイヤー | 失敗ポイント | 起きること | 外から見える症状 |
|---|---|---|---|
| ホスト起動 | Extension Bundle の取得/展開に失敗 | 拡張がロードできず、関数が見つからない | 関数一覧が消える、エンドポイントが 404 |
| 関数実行 | アプリコードの例外 | 関数は存在するが処理失敗 | 500、ログに例外 |
原因:host.json が Preview の Extension Bundle を参照していた
結論から言うと、host.json がプレビュー用の拡張バンドル(Preview/Experimental)を参照していたことが原因です。以下のように Microsoft.Azure.Functions.ExtensionBundle.Preview を指定しているケースが該当します。
{
"version": "2.0",
"isDefaultHostConfig": true,
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle.Preview",
"version": "[4.*, 5.0.0)"
}
}
Preview バンドルは名前の通り、内容や配布先が変わりやすい(あるいは一時的に参照できなくなる)ことがあります。そこを本番相当の環境で参照していると、外部要因で「突然」落ちるという事故が起きやすくなります。今回のように、直前で Preview バンドル側のリリースが入ったタイミングで参照 URL が 404 になったり、バージョンレンジが意図せず最新へ追従してしまうと、ホスト起動が崩れて 404 連発という形で表面化します。
解決:GA(正式版)の Extension Bundle に切り替える
修正はシンプルで、Preview ではなく正式版(GA)の拡張バンドルを参照するように host.json を変更します。
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.0.0, 5.0.0)"
}
}
- id:
Microsoft.Azure.Functions.ExtensionBundle.Preview→Microsoft.Azure.Functions.ExtensionBundle - version:安定版のレンジとして
[4.0.0, 5.0.0)を指定(更新ポリシーの考え方は次の章で解説)
バージョンレンジはどう決めるべきか
レンジ指定は「安全」と「追従性」のトレードオフです。チームの運用ルールに合わせて決めましょう。迷う場合は、まず GA バンドルに切り替えたうえで、次のような方針が現実的です。
| 方針 | 例 | メリット | 注意点 |
|---|---|---|---|
| ほどよく追従(推奨されやすい) | [4.0.0, 5.0.0) | パッチやマイナーの改善を取り込みやすい | まれに互換性差分が混ざる可能性はゼロではない |
| 広く追従 | [4.*, 5.0.0) | 保守の手間が少ない | 「昨日まで OK」が起きやすい。検証環境が必須 |
| 強く固定(慎重運用) | [4.10.0, 4.11.0) | 更新タイミングを完全に管理できる | 更新忘れで脆弱性/不具合修正を取り逃すリスク |
ポイントは「Preview を本番に入れない」だけでなく、本番に入るバージョンの更新タイミングを誰がどう管理するかまで決めておくことです。バージョンレンジが広いほど、更新は楽になりますが、予期しない変化も入りやすくなります。
最短復旧のチェックリスト(インシデント対応向け)
障害対応では「やる順番」が重要です。特に 404 が出ている間は、クライアント側のリトライで負荷が上がりやすいので、迷わず最短で復旧に寄せます。
| 手順 | 確認/操作 | 期待する変化 | うまくいかない場合 |
|---|---|---|---|
| ログ確認 | Log stream / func start で extension bundle 関連のエラーを見る | Unable to find or download extension bundle が見える | 404 以外(401/403/502)なら別原因を疑う |
| 設定確認 | host.json の extensionBundle.id が Preview か確認 | Preview 参照が見つかる | Preview でないなら別の起動失敗要因を探す |
| 設定修正 | GA バンドルへ切り替え、デプロイ | 関数一覧が復活する | 反映されない場合はデプロイ方法/スロットを確認 |
| 再起動 | Function App を再起動 | 起動ログが正常化する | 再起動で戻らない場合はプラン/設定を確認 |
| 疎通確認 | curl で SSE エンドポイントを確認 | 404 ではなく 200 系になり、ストリームが開始 | 500 なら関数処理側の例外へ |
手順:host.json 修正から復旧まで
原因が Extension Bundle にある場合、復旧はhost.json を直して、ホストを再起動し、関数が認識されたことを確認するのが王道です。
host.json を修正する
- リポジトリの
host.jsonを開く extensionBundle.idが Preview になっていないか確認する- GA バンドルに変更し、コミットする
再デプロイ、または Function App を再起動する
Azure 側で関数が 0 件扱いになっている場合、設定ファイルの反映だけで復旧することもありますが、確実にするなら次のいずれかを実施します。
- CI/CD で再デプロイ(Zip Deploy / Run-From-Package など)
- Azure ポータルで Function App を「再起動」
ポータルで「関数」が見えるか確認する
復旧確認は、まず Azure ポータルが早いです。次を満たしているか見てください。
- 「関数」一覧に MCP 関数が表示される
- 「統合」タブがエラーなく開ける
- ログストリームで
No job functions foundが消える
クライアントから SSE を再確認する
SSE はブラウザで開くと挙動が分かりにくいことがあるため、まずは curl などで HTTP ヘッダーを確認すると切り分けが早いです。
curl -i "https://<アプリ名>.azurewebsites.net/runtime/webhooks/mcp/sse?code=xxxxxxxx"
ストリーミング確認まで行うなら、接続を維持するオプションを付けます。
curl -N "https://<アプリ名>.azurewebsites.net/runtime/webhooks/mcp/sse?code=xxxxxxxx"
修正後もローカルで直らない場合:拡張バンドルキャッシュを疑う
host.json を直したのにローカルの func start がまだ不安定、という場合は拡張バンドルのキャッシュが悪さをしていることがあります。拡張バンドルは一度ダウンロードされるとローカルに展開・キャッシュされるため、壊れたキャッシュを掴んだままだと正常な起動に戻りません。
安全なやり方:ログに出る「展開先ディレクトリ」を消す
環境によってキャッシュの場所は異なります。最も確実なのは、func start --verbose のログから以下のような文言を探し、そこに出てくるディレクトリを削除する方法です。
- Extension bundle path
- Extracting extension bundle
- Downloading extension bundle
削除後に再度 func start を実行すると、拡張バンドルが再ダウンロードされ、正常に起動することがあります。
どうしても Preview を使う必要がある場合の現実的な運用
「MCP の新機能を試したい」「Preview でしか提供されていない拡張が必要」といった事情で Preview を使うこともあります。その場合は、次のように事故が本番に波及しない設計に寄せるのが現実的です。
| やること | 具体例 | 狙い |
|---|---|---|
| 環境分離 | 検証用 Function App / 検証スロットのみ Preview | Preview 起因の障害を本番から切り離す |
| 更新の可視化 | 拡張バンドルの更新をリリースノートで追う運用 | 「いつ変わったか」を説明できる状態にする |
| ロールバック手段 | GA バンドルへ戻す PR/ブランチを準備しておく | 事故時に最短で復旧できる |
| 監視とアラート | 404 急増、起動ログのエラー検知 | 気付くのが遅れて被害が広がるのを防ぐ |
運用での再発防止:本番安定性を上げるチェックポイント
今回の原因が Extension Bundle だと分かったら、同じ事故を繰り返さないために、次の観点を押さえておくと効果的です。
本番では GA バンドルを基本にする
- 本番(Production)と同等のスロットは GA バンドルを使う
- Preview が必要な機能検証は、別スロット/別 Function App に分離する
- 「Preview でないと動かない」要件は、運用リスクとしてチーム合意を取る
変更検知と監視を入れる
「突然 404」は監視で早期に掴めます。最低限、次のどれかを入れると復旧が早くなります。
| 監視対象 | 例 | 狙い |
|---|---|---|
| 404 の急増 | /runtime/webhooks/mcp/sse の 404 比率 | ルート未登録/関数未認識を早期検知 |
| 起動失敗ログ | Unable to find or download extension bundle | 原因を「拡張バンドル」に即座に寄せる |
| ポータル上の関数数 | 関数が 0 件になったら通知(運用で観測) | ホスト起動の異常を目視/運用で拾う |
Application Insights を使っている場合、requests テーブルから該当 URL と 404 を集計すると傾向が見やすいです。
requests
| where url has "/runtime/webhooks/mcp/sse"
| summarize count() by bin(timestamp, 5m), resultCode
CI/CD で「関数が認識されること」をゲートにする
拡張バンドル起因の事故は、デプロイ後に発覚しがちです。簡易でもよいので、次のようなゲートを用意すると事故を潰せます。
- ビルド後にローカル起動(Functions Core Tools)を走らせ、
No job functions foundが出ないことを確認する - デプロイ後にヘルスチェック(ステータスコード、Content-Type、初回レスポンス時間)を確認する
- スロットで先に検証し、問題なければスワップする
まだ 404 が続くときに見るポイント
host.json を GA に変えても 404 のままなら、次の「ありがちな落とし穴」を順番に潰すと早いです。
host.json がデプロイ先に反映されているか
- ローカルで直っているのに Azure だけ直らない場合、デプロイ対象に古い host.json が残っていることがあります。
- Run-From-Package の場合、デプロイしたパッケージに含まれる host.json が正しいか確認します。
Functions ランタイムの世代とバンドルの世代が合っているか
同じ「拡張バンドル」でも、Functions のランタイム世代で推奨レンジが変わります。少なくとも、Functions v4 相当なら ExtensionBundle v4 を使う、という対応関係は意識してください。
| Functions ランタイムの目安 | extensionBundle の例 | 補足 |
|---|---|---|
| v4 系(一般的な現行) | id: Microsoft.Azure.Functions.ExtensionBundle version: [4.0.0, 5.0.0) | レンジの指定は運用方針で調整 |
| Preview を使う検証環境 | id: Microsoft.Azure.Functions.ExtensionBundle.Preview | 本番での使用は慎重に |
404 の出どころが「Azure Front」なのか「Functions Host」なのか
同じ 404 でも、返している主体が違うと原因も変わります。
- ホストが「関数ゼロ」で起動している 404:ログに No job functions found が出やすい
- アプリがそもそも起動していない/ルーティングされていない 404:プラン停止、設定ミス、デプロイ失敗の可能性
Log stream(ストリーム ログ)や Application Insights のトレースで、ホスト起動のメッセージが出ているかを確認してください。
まとめ:MCP 404 は host.json の Extension Bundle を最初に疑う
/runtime/webhooks/mcp/sse のような MCP エンドポイントが突然 404 になったとき、アプリコードやキーよりも先に、Azure Functions の起動ログと host.json の extensionBundle 設定を確認すると復旧が早くなります。Preview バンドル参照は便利な反面、外部要因で落ちる可能性があるため、本番は GA バンドルを基本にし、Preview は検証環境に分離する運用をおすすめします。

コメント