記事一覧
MCPホスティング

MCPサーバーのヘルスチェックと死活監視:pingが廃止された2026-07-28仕様で何を確認すべきか【MCP Hosting】

MCPサーバーのヘルスチェックは何を叩けばいいのか。2026-07-28仕様でpingは廃止され、GETは400を返すため、一般的な死活監視はそのままでは誤検知します。正常なサーバーと上流APIキーが切れたサーバーに10種類のプローブを実際に送った結果、3層のチェック設計、Docker・Kubernetes・Cloud Runの設定例、アラート設計、スケールゼロとの費用計算までまとめました。

最終更新:2026年10月27日 / 対象:MCP仕様 2026-07-28、MCP Python SDK 2.3.0 のサーバー(MCPServer)、2026-07-28版のMCP Inspector CLIドキュメント、2026年10月時点のDocker・Kubernetes・Google Cloud Runのヘルスチェック関連ドキュメント。検証は2026年10月27日、Linux・Python 3.13・curlで実施。

結論(30秒で読める要約)

MCPサーバーのヘルスチェックとは、ホスティングしているMCPサーバーがMCPのリクエストに応答できるか、さらにはツールが本来の仕事をこなせるかを、定期的なリクエストで確かめる仕組みです。MCP Hostingの現場では、ふつうの死活監視をそのまま使うと「正常なのに落ちている」と判定するか、「壊れているのに正常」と判定するかのどちらかになりがちです。

  • ping はもうありません。2026-07-28の変更履歴には「Remove ping」と明記されています。検証では、2026-07-28のサーバーに ping を送るとHTTP 404、JSON-RPCエラー -32601 Method not found が返りました。監視ガイドでよく見る、プロトコル用ヘッダーなしの ping は 400 "Missing session ID" でした。
  • GETによる監視も使えません。Python SDKのサーバーでは GET /mcp が 400 を返し、仕様では2026-07-28専用サーバーはGETに 405 を返すことになっています。どちらも正常かどうかの判断材料になりません。
  • HTTP 200は「動いている」ことを意味しません。上流APIのキーが切れた状態でも、server/discover、tools/list、単純なツールはすべて 200 でした。失敗したのはキーを使うツールだけで、それも 200 のレスポンスの中に "isError": true が入る形でした。存在しないツールを呼んでも 200 と isError: true が返ります。
  • 3つの層に分けて確認します。プロセスが動いているか(liveness)、MCPとして応答するか(server/discover と、期待するツールが tools/list にあるか)、ツールが働くか(読み取り専用の health_check ツールの出力を確認)です。
  • 監視がスケールゼロを打ち消すことがあります。起動中の時間で課金されるプラットフォームでは、アイドルタイムアウトより短い間隔で監視すると、サーバーは1か月ずっと起きたままになります。512MBなら約365GB時間です。

検証結果は3章、層の設計は4章、費用計算は7章にあります。

目次

  1. MCPサーバーのヘルスチェックとは
  2. 2026-07-28仕様でヘルスチェックの何が変わったか
  3. 検証:よく使われる10種類のプローブの結果
  4. ヘルスチェックの3つの層
  5. プラットフォームのプローブ:Docker、Kubernetes、Cloud Run
  6. 外部の死活監視:リクエストとアサーション
  7. 監視間隔とスケールゼロ:費用の計算
  8. 設定すべきアラート
  9. よい health_check ツールの書き方
  10. チェックリスト
  11. トラブルシューティング:症状→原因→対処
  12. NodeFlareならこう解決できる
  13. よくある質問

1. MCPサーバーのヘルスチェックとは

MCPサーバーのヘルスチェックとは、稼働中のMCPサーバーについて「プロセスに届くか」「MCPとして応答するか」「ツールが依存先に届くか」の3点を、決まった間隔の自動リクエストで確かめることです。問いごとに送るべきリクエストが違い、1つ目しか見ない監視では実際の障害の多くを見逃します。

確かめたいこと 主に見つかる障害 送るリクエスト
プロセスに届くか クラッシュ、メモリ不足での強制終了、DNSやTLSの不具合、基盤の障害 TCP接続、または別に用意した /healthz へのGET
MCPとして応答するか 起動コマンドの誤り、ブリッジの停止、プロトコルバージョンの不一致、認証層の故障 server/discover、tools/list
ツールが働くか APIキーの期限切れ、上流サービスの停止、環境変数の設定漏れ、DBに届かない 読み取り専用のヘルスツールへの tools/call

隣り合う2つのテーマはこの記事では扱いません。新しいリリースに切り替える前の1回限りの確認はデプロイ時のスモークテストで、無停止でのMCPサーバーデプロイにまとめています。誰がどのツールを呼んだかを後から追うための記録は監査ログで、MCPの監査ログで解説しています。この記事は、すでに本番で動いているサーバーを継続的に監視する話です。

2. 2026-07-28仕様でヘルスチェックの何が変わったか

2026-07-28版でMCPはステートレスになり、そのうち4つの変更が、以前のバージョン向けに作ったヘルスチェックを動かなくします。

2026-07-28の変更 仕様の記述 監視への影響
ping の廃止 「Remove ping, logging/setLevel, and notifications/roots/list_changed」 ping を使った監視は新しいサーバーで失敗する
initialize ハンドシェイクとセッションの廃止 各リクエストが _meta でプロトコルバージョンとクライアント機能を運ぶ 1回のリクエストで確認でき、開いたままのセッションも残らない
server/discover の追加 サーバーはこのRPCを実装しなければならない(MUST) ping の代わりに使える。軽く、副作用がなく、準拠したサーバーなら必ずある
GETエンドポイントの廃止 MCPエンドポイントへのGETとDELETEには 405 Method Not Allowed を返す GETの監視では正常と故障を区別できない
標準ヘッダーの必須化 すべてのPOSTに MCP-Protocol-Version と Mcp-Method、tools/call には Mcp-Name ヘッダーのないプローブは 400(-32020 HeaderMismatch)になる

2025-11-25以前のセッション方式のサーバーは今も ping に対応しています。ただし状態を持つサーバーでは、セッションの中でしか使えず、先に initialize を送る必要があります。Cronitorの監視ガイドは、定期的に initialize を送る監視はサーバー側にセッションがたまるとして、まさにこれを避けるよう書いています。

3. 検証:よく使われる10種類のプローブの結果

MCP Python SDK 2.3.0 の MCPServer(Streamable HTTP、JSONレスポンス)で同じサーバーを2つ起動しました。1つは上流APIのキーが有効、もう1つは期限切れです。APIを包むタイプのサーバーで最もよくある障害の1つです。ツールは echo、get_quote(上流のキーを使う)、health_check(キーが無効なら例外)、slow_report の4つ。それぞれにcurlでプローブを送りました。

# プローブ 正常なサーバー 上流キーの期限切れ 実際に分かること
1 GET /mcp 400 400 何も分からない。正常と故障が同じ結果
2 GET /health 404 404 SDKはヘルス用のルートを作らない。自分で足す必要がある
3 ヘッダーなしの ping(多くのガイドの例) 400 "Missing session ID" 400 新しいサーバーでは何も分からない
4 2026-07-28ヘッダー付きの ping 404、-32601 Method not found 404 ping は廃止済み
5 server/discover 200、supportedVersions: ["2026-07-28"] 200 プロセスが動き、MCP層が応答している
6 tools/list 200、ツール4個 200、ツール4個 ツールが登録されている
7 tools/call echo 200、isError: false 200、isError: false ツールの実行経路が動く
8 tools/call get_quote 200、isError: false 200、isError: true、「Error executing tool get_quote」 上流の依存先が動くか
9 tools/call health_check 200、isError: false 200、isError: true 同上(監視用のツール経由)
10 存在しないツールへの tools/call 200、isError: true、「Unknown tool: nope」 200、isError: true ツール単位の確認ではHTTPステータスが役に立たない

ここから分かることは4つです。

  • 障害を捉えたのは8と9だけでした。プロトコル層の確認はすべて正常のまま、上流APIが必要な実際のリクエストはすべて失敗していたはずです。
  • 失敗は 200 の中にありました。HTTPステータスしか見ない監視は、壊れたサーバーでも8と9を合格にします。レスポンス本文を確認する必要があります。
  • SDKは存在しないツールをプロトコルエラーではなくツールのエラーとして返しました。仕様では「Unknown tool」はプロトコルエラー(JSON-RPC -32602)の例ですが、Python SDK 2.3.0 は isError: true の通常の結果として返しました。デプロイでヘルスツールの名前が変わっても、JSON-RPCの error だけを見る監視は気づきません。期待する出力の文字列で確認してください。
  • エラー文から原因が消えます。SDKは例外のメッセージ(「upstream returned 401: API key expired」)を「Error executing tool get_quote」に置き換えました。ヘルスツールは例外を投げず、通常の出力でどの依存先が失敗したかを返すのが得策です(9章)。

使ったプローブを、再利用できるスクリプトにしたものです。

#!/usr/bin/env bash
# mcp-probe.sh URL METHOD [TOOL] [ARGS_JSON] - 2026-07-28のリクエストを1回送り、ステータス・時間・本文を表示
URL=$1; M=$2; T=$3; A=${4:-{\}}
META='"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"probe","version":"1"},"io.modelcontextprotocol/clientCapabilities":{}}'
if [ -n "$T" ]; then P="{\"name\":\"$T\",\"arguments\":$A,$META}"; NAMEH=(-H "Mcp-Name: $T"); else P="{$META}"; NAMEH=(); fi
curl -s -m 10 -w '\n%{http_code} %{time_total}s\n' -X POST "$URL" \
  -H "Authorization: Bearer $MCP_API_KEY" \
  -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' -H "Mcp-Method: $M" "${NAMEH[@]}" \
  -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"$M\",\"params\":$P}"

使い方:./mcp-probe.sh https://mcp.example.com/mcp server/discover、または ./mcp-probe.sh https://mcp.example.com/mcp tools/call health_check。

4. ヘルスチェックの3つの層

役に立つMCPの監視は、liveness・プロトコル・機能を別々に、別の頻度で、別の担当者に向けて確認します。混ぜると、上流の障害を再起動でさらに悪化させるか、本当の障害中にダッシュボードが緑のままになるか、のどちらかが起きます。

層 リクエスト 確認する内容 失敗時に動くもの 目安の間隔 やってはいけないこと
1. liveness TCP接続、または自分で足した GET /healthz 接続できる、または 200 基盤がコンテナを再起動 10〜30秒 上流APIを呼ぶ(上流の障害で正常なコンテナまで再起動される)
2. プロトコル server/discover、続けて tools/list 200、supportedVersions に 2026-07-28、期待するツール名がある 人にアラート。デプロイを止める 1〜5分(常時起動の場合) ping や initialize を使う
3. 機能 tools/call health_check 本文に HEALTH_OK(自分で決めた目印)、応答時間がしきい値以内 人にアラート 5〜15分。スケールゼロではもっと間隔をあける(7章) 書き込む、1回ごとに費用がかかる、管理者権限の認証情報を使う
4. 受動監視(実トラフィック) プローブなし。リクエストログを読む ツールごとの isError: true と5xxの割合(10〜15分) 人にアラート 常時 利用の少ないサーバーで唯一の手段にする

分ける理由は再起動にあります。KubernetesやCloud Runは、livenessが失敗したコンテナを停止して起動し直します。livenessに上流APIの確認を入れると、そのAPIが落ちたときに全レプリカが再起動を繰り返し、まだ動くはずのツールまで応答しなくなります。再起動はプロセスの状態に、アラートは機能の状態に結びつけます。

5. プラットフォームのプローブ:Docker、Kubernetes、Cloud Run

基盤のプローブはGETを前提にしていますが、MCPにはヘッダーとJSON-RPC本文つきのPOSTが必要です。選択肢は、TCPで確認する、別のGETルートを用意する、コンテナ内で小さなスクリプトを実行するexecプローブ、の3つです。

基盤 プローブの種類 JSON-RPCをPOSTできるか 既定値 失敗したときの動き
Docker HEALTHCHECK 任意のコマンド(exec) スクリプト経由で可能 間隔30秒、タイムアウト30秒、リトライ3回 コンテナが unhealthy になる。素のDockerは再起動しない(再起動ポリシーは終了に反応し、ヘルス状態に反応するのはオーケストレーター)
Kubernetes httpGet、tcpSocket、grpc、exec exec のときだけ 間隔10秒、タイムアウト1秒、失敗しきい値3。httpGet は200〜399で成功 liveness:再起動。readiness:Serviceの宛先から外れる
Google Cloud Run startup:HTTP・TCP・gRPC。liveness・readiness:HTTP・gRPC できない。HTTPプローブはGET(独自ヘッダーは可) startupプローブ未設定なら、タイムアウト240秒のTCP startupプローブが自動で付く。livenessとstartupは間隔10秒・タイムアウト1秒・失敗しきい値3 liveness:失敗が続くとSIGKILL。処理中のリクエストは 503 で終わり、新しいインスタンスが起動

方法A:liveness用のGETルートを足す。SDKは用意しませんが、Starletteのアプリにはルートを追加できます。検証したコードです。

# server.py - MCP Python SDK 2.3.0
from starlette.responses import JSONResponse
from starlette.routing import Route
from mcp.server.mcpserver import MCPServer

mcp = MCPServer("my-server")
# ... @mcp.tool() の定義 ...

async def healthz(request):          # livenessのみ:プロセスとイベントループ。上流は呼ばない
    return JSONResponse({"status": "alive"})

app = mcp.streamable_http_app(json_response=True)
app.router.routes.append(Route("/healthz", healthz))
# 起動:uvicorn server:app --host 0.0.0.0 --port 8000

GET /healthz は正常なサーバーでも壊れたサーバーでも 200 {"status":"alive"} を返しました。プロセス自体は正常だったので、livenessとしてはこれが正しい結果です。

方法B:MCPを話すexecプローブ。readinessの確認や、手を入れられないサーバー(ブリッジの裏のstdioサーバー、GitHubから見つけてそのまま動かしているサーバー)では、server/discover をPOSTするスクリプトを実行します。標準ライブラリだけで書いているので、イメージに追加の依存は入りません。

#!/usr/bin/env python3
# mcp_healthcheck.py URL - server/discover が 2026-07-28 で応答すれば終了コード0、それ以外は1
import json, sys, urllib.request
url = sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:8000/mcp"
meta = {"io.modelcontextprotocol/protocolVersion": "2026-07-28",
        "io.modelcontextprotocol/clientInfo": {"name": "healthcheck", "version": "1"},
        "io.modelcontextprotocol/clientCapabilities": {}}
body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "server/discover", "params": {"_meta": meta}}).encode()
req = urllib.request.Request(url, data=body, method="POST", headers={
    "Content-Type": "application/json", "Accept": "application/json, text/event-stream",
    "MCP-Protocol-Version": "2026-07-28", "Mcp-Method": "server/discover"})
try:
    with urllib.request.urlopen(req, timeout=3) as r:
        raw = r.read().decode()
    if raw.lstrip().startswith(("event:", "data:")):          # SSEの場合は最後のdata行を使う
        raw = [l[5:] for l in raw.splitlines() if l.startswith("data:")][-1]
    ok = "2026-07-28" in json.loads(raw)["result"]["supportedVersions"]
except Exception as e:
    print(f"unhealthy: {e}", file=sys.stderr); sys.exit(1)
sys.exit(0 if ok else 1)

検証では、動いているサーバー(JSON応答・SSE応答の両モード)に対して 0、停止したサーバーに対して「Connection refused」とともに 1 で終了しました。組み込み方は次のとおりです。

# Dockerfile
COPY mcp_healthcheck.py /app/
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
  CMD ["python3", "/app/mcp_healthcheck.py", "http://127.0.0.1:8000/mcp"]
# Kubernetes のコンテナ定義
livenessProbe:
  httpGet: { path: /healthz, port: 8000 }
  periodSeconds: 10
  failureThreshold: 3
readinessProbe:
  exec:
    command: ["python3", "/app/mcp_healthcheck.py", "http://127.0.0.1:8000/mcp"]
  periodSeconds: 15
  timeoutSeconds: 5

サーバーがすべてのリクエストを認証する場合は、認証を前段のゲートウェイが担う構成で 127.0.0.1 のポートを叩くか、環境変数でプローブ専用のキーを渡します。Cloud RunのHTTPプローブはPOSTの本文を送れないため、livenessには /healthz のルートを使います。

6. 外部の死活監視:リクエストとアサーション

基盤のプローブは自分のインフラの内側から動くため、DNS・TLS・CDN・認証層を、クライアントと同じ見え方では確認できません。そこを埋めるのが外部の監視サービスです。JSON本文と独自ヘッダーつきのPOSTを送れて、レスポンス本文を条件にできるものを選びます。たとえばCronitorは、この方法でのMCPの監視手順を公開しています。

外部監視から送る機能チェックのリクエストです。

POST /mcp HTTP/1.1
Host: mcp.example.com
Authorization: Bearer <監視用キー>
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: health_check

{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"health_check","arguments":{},
 "_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28",
          "io.modelcontextprotocol/clientInfo":{"name":"uptime-monitor","version":"1"},
          "io.modelcontextprotocol/clientCapabilities":{}}}}
確認する条件 理由
ステータスコードが 200 停止、認証の失敗(401/403)、ヘッダーの不備(400)、基盤のエラー(5xx)を検出
本文に HEALTH_OK を含む isError: true、依存先の劣化、ヘルスツールの改名や削除を検出。どれも 200 で返るため
本文に "isError":true を含まない ヘルスツールの出力形式が変わったときの保険
応答時間がしきい値以内(例:3秒) タイムアウトになる前に、上流の遅延を検出
TLS証明書の残り期間が14〜30日以上 証明書の期限切れは、すべてのリモートクライアントを同時に止める

監視用の認証情報には3つのルールがあります。

  • 監視専用のキーを発行する。単独で無効化でき、利用状況の集計からも除外できます。
  • ヘルスツールだけに権限を絞る。ホスティングやゲートウェイがツール単位のスコープに対応していれば、監視用キーは health_check 以外を呼べないようにします。キーのスコープ設計はMCPセキュリティのカテゴリで扱っています。
  • IP許可リストを使っている場合は、監視サービスの送信元IPを許可する。そうしないと、関係のない理由で監視が失敗します。

スクリプトで済ませるなら、MCP InspectorのCLIも使えます。cronやCIから実行でき、終了コードがそのままアラートの種類に対応します(2026-07-28版のInspectorドキュメントより)。

終了コード 意味 アラートの種類
0 成功 正常
3 認証が必要(HTTP 401/403) 監視用キーの失効、または認証設定の誤り
4 到達できない(DNS、接続拒否、タイムアウト) 停止
5 tools/call が isError: true、またはツールが見つからない 機能の障害
mcp-inspector --cli https://mcp.example.com/mcp --transport http \
  --header "Authorization: Bearer $MCP_MONITOR_KEY" \
  --method tools/call --tool-name health_check --format json

OAuthで保護されたサーバーをCIで確認する場合は --stored-auth-only を付けます。TTYがない環境では、ブラウザを待たずに auth_required ですぐ失敗します。

7. 監視間隔とスケールゼロ:費用の計算

インスタンスが起きている間メモリに課金されるプラットフォームでは、プローブは無料ではありません。プローブのたびにスケールゼロのサーバーが起き、アイドルタイムアウトの間は動き続けます。監視間隔がアイドルタイムアウトより短ければ、サーバーは一度も止まりません。

プローブだけで起きている時間 ≈ 730時間 ×(アイドルタイムアウト ÷ 監視間隔、上限1)。512MBのサーバーで、アイドルタイムアウトを5分とした例です(実際の値は利用中の基盤で確認してください)。

監視間隔 プローブによる月間稼働時間 月間GB時間(512MB) 影響
1分 約730時間 約365 常時起動と同じ
5分 約730時間 約365 常時起動と同じ(止まる直前に次のプローブが来る)
15分 約243時間 約122 月の3分の1は起きている
30分 約122時間 約61
60分 約61時間 約30

これが問題になるかどうかは課金方式で決まります。

課金方式 例 プローブ1回の費用
リクエスト単位 Cloud Runの既定のリクエストベース課金:「リクエストを処理している間と、起動時・終了時だけ」課金 小さい。プローブの処理時間と、起こした場合の起動・終了分
インスタンスの稼働時間単位 Cloud Runのインスタンスベース課金(インスタンスの存続期間全体に課金)、起動中のGB時間で課金する多くのコンテナ・マイクロVM型ホスティング 1回ごとに最大でアイドルタイムアウト分

スケールゼロのサーバーを監視すると、ほかにも2つの影響があります。

  • 健康状態ではなくコールドスタートを測ってしまう。止まっているサーバーへのプローブには起動時間が含まれます。監視のタイムアウトをコールドスタートより長くするか、2回連続で失敗したときだけアラートにします。コールドスタートの測り方と短縮方法は最初のMCP呼び出しが遅いのはなぜかにまとめています。
  • アイドルのインスタンスはいずれ止まる。Cloud Runは、インスタンスがリクエスト処理後15分を超えてアイドルのままでいることはないとしています。頻繁なプローブは、このカウントを毎回リセットします。

実用的な目安は次のとおりです。

サーバーの種類 おすすめの監視
常時起動、または最小インスタンス数1以上 プロトコルの確認を1〜5分ごと、機能の確認を5〜15分ごと
スケールゼロで、業務時間に使う 実トラフィックの受動監視に加え、機能の確認を30〜60分ごと、または業務時間だけ
スケールゼロで、たまにしか使わない 受動監視、1日1回の機能確認、デプロイ後のスモークテスト

8. 設定すべきアラート

アラートは1つの「ありそうな原因」に対応させます。呼び出された人がどこを見ればいいか、すぐ分かるようにするためです。検証と仕様から出てきたシグナルは次のとおりです。

シグナル しきい値 最も考えられる原因
接続拒否、DNSの失敗、TLSエラー(Inspectorの終了コード 4) 2回連続 サーバー停止・クラッシュ、証明書やDNSの不具合
プローブが 401 / 403(終了コード 3) 1回 監視用キーの失効・期限切れ、認証層の設定ミス
400 と -32020(HeaderMismatch) 1回 監視側に Mcp-Method / Mcp-Name がない。監視を直す
400 と -32022(UnsupportedProtocolVersion) 1回 サーバーか監視のどちらかが別のプロトコルバージョンに上がった
ping に 404 と -32601 1回でも セッション時代向けに作った監視。server/discover に切り替える
tools/list に期待するツールがない 1回 デプロイでツールが改名・削除された
ヘルスツールの出力に HEALTH_OK がない、または isError: true 2回連続 上流の認証情報、上流の停止、設定漏れ
実トラフィックで特定ツールの isError: true の割合が上がる 15分間で通常の2〜3倍以上 そのツールの依存先、またはクライアントからのおかしな入力
ヘルスツールの応答時間p95 15分間しきい値超え 上流の遅延、メモリ不足、同居する負荷
プローブが 429 1回でも プローブがレート制限に数えられている。間隔をあけるか除外する
402 や課金関連のエラー 1回でも 利用上限やクォータに達した

9. よい health_check ツールの書き方

ヘルスツールは、安く、読み取り専用で、速く、依存先ごとの状態を通常の出力で名前つきで返すように作ります。そうすれば数分おきに呼んでも安全で、失敗したときにも役に立ちます。検証に使ったものです。

import os, time, asyncio
from mcp.server.mcpserver import MCPServer
from mcp.types import ToolAnnotations

mcp = MCPServer("my-server")
_cache = {"at": 0.0, "result": None}

async def check_upstream() -> str:
    # 本物のツールと同じキーで、安い読み取りAPI(例:GET /v1/me)を呼ぶ処理に置き換える
    ...
    return "ok"

@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, idempotentHint=True))
async def health_check() -> str:
    """Read-only self test for monitoring. Reports each dependency; never writes."""
    if time.monotonic() - _cache["at"] < 30 and _cache["result"]:
        return _cache["result"]                          # 上流の確認は30秒に1回まで
    checks = {}
    try:
        checks["upstream"] = await asyncio.wait_for(check_upstream(), timeout=3)
    except asyncio.TimeoutError:
        checks["upstream"] = "timeout"
    except Exception as e:
        checks["upstream"] = type(e).__name__
    status = "OK" if all(v == "ok" for v in checks.values()) else "DEGRADED"
    detail = " ".join(f"{k}={v}" for k, v in checks.items())
    result = f"HEALTH_{status} {detail} version={os.environ.get('APP_VERSION', 'dev')}"
    _cache.update(at=time.monotonic(), result=result)
    return result

2つのテスト用サーバーでの結果:正常な方は HEALTH_OK upstream=ok version=dev を55ミリ秒で返し、30秒のキャッシュが効いている間は2〜3ミリ秒でした。壊れた方は HEALTH_DEGRADED upstream=401 from upstream version=dev を返したので、アラートの文面に「Error executing tool」ではなく、何が壊れたかがそのまま出ます。

設計のルールです。

  • 本物のツールと同じ認証情報を使う。ヘルスチェックだけ別のAPIキーを使うと、本番のキーが切れたことに気づけません。
  • 書き込まない。テスト用の行もメッセージもチケットも作りません。書き込みでしか確かめられない上流なら、同じサービスの読み取り用エンドポイントで確認します。
  • 依存先ごとに時間を区切る(ここでは3秒)。上流が固まっても、詳細のない監視タイムアウトではなく timeout と返せます。
  • 短時間キャッシュする。監視サービスが複数あったり、複数リージョンやレプリカから呼ばれたりしても、上流への呼び出しが増えません。
  • バージョン文字列を返す。デプロイ後に、どのビルドが応答しているか確認できます。
  • 出力は短く、変えない。監視は HEALTH_OK で照合しているので、気軽に文言を変えないでください。
  • モデルから隠すことも検討する。ほかのツールと同じく tools/list に載り、少しだけコンテキストを使います。ホスティングやゲートウェイがスコープでツールを絞れるなら、health_check は監視用キーにだけ見せます。

10. チェックリスト

  • [ ] ping、initialize、MCPエンドポイントへのGETを使う監視がない
  • [ ] livenessはプロセスだけを確認し(TCPか /healthz)、上流APIを呼ばない
  • [ ] プロトコルの確認で、MCP-Protocol-Version: 2026-07-28 と Mcp-Method を付けて server/discover をPOSTしている
  • [ ] クライアントが頼っているツール名が tools/list にあるか確認している
  • [ ] 読み取り専用の health_check ツールが、本番と同じ認証情報で依存先ごとの状態を名前つきで返す
  • [ ] 外部監視がHTTP 200 だけでなく本文(HEALTH_OK)を確認している
  • [ ] 監視用キーは専用で、単独で無効化でき、ヘルスツールだけに絞られている
  • [ ] IP許可リストを使う場合、監視サービスのIPを許可している
  • [ ] 監視間隔を、アイドルタイムアウトと課金方式を踏まえて決めた
  • [ ] スケールゼロのサーバーでは、監視のタイムアウトがコールドスタートより長い
  • [ ] 実トラフィックを受動監視している(ツールごとの isError と5xxの割合)
  • [ ] アラートごとに、ありそうな原因と担当者が決まっている

11. トラブルシューティング:症状→原因→対処

症状 原因 対処
監視は「停止」なのに、クライアントは普通に使えている 監視がGETか ping を送っている 2026-07-28のヘッダー付きで server/discover をPOSTする
プローブに 400 "Missing session ID" MCP-Protocol-Version のないリクエストが、セッション方式にも対応したサーバーに届いた ヘッダーと _meta を付ける
プローブに 404 -32601 2026-07-28で ping が廃止された server/discover に切り替える
400 -32020 HeaderMismatch Mcp-Method や Mcp-Name がない、または本文と食い違う method と params.name をヘッダーにも書く
監視は緑なのに、利用者は失敗している ステータスコードしか見ていない。失敗は 200 と isError: true で返る 本文の HEALTH_OK を確認する
上流の障害中に全レプリカが再起動する livenessで上流を呼んでいる livenessはプロセスだけにし、上流の確認は機能の層へ移す
Kubernetesのプローブでリクエストを送れない httpGet はGETのみ mcp_healthcheck.py を使うexecプローブ、または /healthz ルート
unhealthy のまま何も起きない 素のDockerは unhealthy のコンテナを再起動しない オーケストレーターを使う、またはプロセス管理側で対処する
監視を入れたら請求が増えた 稼働時間課金で、監視間隔がアイドルタイムアウトより短い 間隔をあけるか、受動監視を主にする(7章)
スケールゼロのサーバーでときどきタイムアウト プローブがコールドスタートを含んでいる タイムアウトを延ばし、2回連続の失敗でアラートにする
ヘルスツールはOKなのに、ツールが失敗する ヘルスツールが別の依存先や認証情報を確認している 本物のツールと同じ認証情報・エンドポイントを確認する
デプロイ後にヘルスツールが消えたのに監視が緑 200 しか見ておらず、存在しないツールも 200 で返る 出力の目印で確認する

12. NodeFlareならこう解決できる

NodeFlareは、GitHubのリポジトリからMCPサーバー(stdio専用のものを含む)を、認証つきのHTTPSエンドポイントとしてデプロイするMCP Hostingサービスです。この記事で挙げた課題に、次のように対応します。

この記事の課題 NodeFlareでの対応
ポートは開いているのにMCPを話さないサーバー デプロイのたびに本物のMCP initialize リクエスト(バックオフつきで最大8回)で確認し、Verified・Broken・Inconclusiveのいずれかで結果を示す。クラッシュを繰り返すプロセスは公開されず、デプロイ失敗になる
監視の合間にstdioサーバーやブリッジが落ちる 同梱のstdioアダプターが、1秒から30秒までの指数バックオフでサーバーのプロセスを再起動する。再起動の上限に達するとアダプターの /health は 503 を返す
HTTP 200の中に隠れた失敗 すべてのMCPリクエストを tool_name、http_status、outcome(ok、scope_denied、rate_limited、budget_exceeded など)、duration_ms、error_message、user_agent、client_ip つきで記録し、ツール名・エラー・メソッド・outcome・IPで検索できる。4章の受動監視(第4層)の材料になる
監視用キーの権限が広すぎる mcp_* のAPIキーに tools:call:health_check のようなスコープを付けられ、監視用キーはヘルスツールしか呼べない。tool_list_filter_by_scope で、呼べるツールだけを一覧に見せられる
プローブがスケールゼロを打ち消す アイドル状態が数分続くとスナップショットを取って停止し、1秒未満で復元する。課金は起動中だけなので、間隔をあけたプローブなら費用は小さい。頻繁に監視したい場合は、Always-On(Pro以上)で1レプリカを常に起動しておける
監視で請求が知らないうちに増える 有料プランでは月の利用上限を設定でき、上限に達するとサーバーは 402 を返す。サーバーごとの月間上限も設定できる
監視がネットワーク制限で弾かれる IP許可リストはIPv4/IPv6アドレスとCIDRを受け付け、認証より前に適用される。監視サービスの送信元IPを追加する

できないこと:NodeFlareのドキュメントには、組み込みの死活監視アラートや通知機能の記載はありません。第2層と第3層は外部の監視サービスで行い、第4層にリクエストログを使ってください。ログの検索範囲は1時間・24時間・7日・30日(Freeは1時間固定)、保存期間はFreeが7日、ProとTeamが30日です。

料金:Freeは0円で、MCPサーバー1つと月30GB時間の計算資源つき。Proは月2,980円で、サーバー数無制限、300GB時間の計算資源と10GBのストレージを含みます。超過分の計算資源は1GB時間あたり5円で、アイドル中のサーバーは0円です。

最小の始め方:

  1. nodeflare.tech にGitHubでログインし、MCPサーバーのリポジトリを選ぶ。公開前にNodeFlareが本物のMCPリクエストで動作を確認します。
  2. 読み取り専用の health_check ツールを足し、監視用に tools:call:health_check に絞ったAPIキーを作る。
  3. 外部の監視サービスに /mcp のURLと6章のリクエストを設定し、実トラフィックはログの outcome で見る。

1サーバーまで無料で試せます → nodeflare.tech

13. よくある質問

MCPサーバーのヘルスチェックはどうやるの?

GETではなく、JSON-RPCのリクエストをPOSTします。手早くプロトコル層を確認するなら、MCP-Protocol-Version: 2026-07-28 と Mcp-Method のヘッダーを付けて server/discover を呼びます。ツールが動くことまで確かめるなら、読み取り専用の health_check ツールを呼んで出力を確認します。ツールの失敗はHTTP 200 と isError: true で返るためです。

MCPにpingはある?

今はありません。2026-07-28の仕様で、initialize ハンドシェイクやセッションとともに ping は廃止されました。検証では、2026-07-28のサーバーは ping に 404 と -32601 Method not found を返しました。2025-11-25以前のセッション方式のサーバーでは使えますが、セッションの中に限られます。

pingの代わりに何を使えばいい?

server/discover です。2026-07-28のサーバーは必ず実装しなければならず、対応するプロトコルバージョン、機能、サーバー情報を返します。副作用がなくセッションも不要なので、軽いlivenessやreadinessの確認に使えます。

動いているのに監視サービスが「停止」と判定するのはなぜ?

監視がGETか ping を送っている可能性が高いです。MCPエンドポイントはPOSTしか受け付けず、2026-07-28のサーバーはGETにエラー(検証したPython SDKのサーバーでは 400、仕様では 405)を、ping に 404 を返します。プロトコル用のヘッダーを付けた server/discover のPOSTに切り替えてください。

HTTP 200が返ればMCPサーバーは正常?

いいえ。検証では、上流APIのキーが切れたサーバーでも server/discover、tools/list、すべてのツール呼び出しが 200 でした。失敗したツールは、200 のレスポンスの中で isError: true を返していました。存在しないツールを呼んでも 200 です。レスポンス本文で判定してください。

ヘルスチェックでtools/callを呼ぶべき?

機能の層では呼ぶべきです。ただし、書き込みをせず、数秒で終わり、本物のツールと同じ認証情報を使う専用の読み取りツールに限ります。基盤のlivenessには入れないでください。上流が落ちたときに、正常なコンテナまで再起動されてしまいます。

KubernetesやCloud RunでMCPサーバーをプローブできる?

工夫すればできます。HTTPプローブはGETを送るため、livenessには /healthz のような小さなGETルートを足すか、Kubernetesなら server/discover をPOSTするスクリプトを exec プローブで動かします。Cloud Runのlivenessとreadinessが対応するのはHTTPとgRPCだけなので、ルートを足すのが最も簡単です。

ヘルスチェックでスケールゼロが効かなくなる?

なることがあります。起動中の時間で課金される基盤では、プローブのたびにサーバーが起き、アイドルタイムアウトの間動き続けます。監視間隔がアイドルタイムアウトより短ければ一度も止まらず、512MBで月約365GB時間になります。Cloud Runの既定のようなリクエスト単位の課金なら、費用はプローブの処理時間と起動分だけです。

どのくらいの間隔で監視すればいい?

常時起動のサーバーなら、プロトコルの確認を1〜5分ごと、機能の確認を5〜15分ごとが一般的です。スケールゼロのサーバーは、実際のリクエストの受動監視を主にし、機能の確認は30〜60分ごと、または業務時間だけにします。

stdioのMCPサーバーはどう監視する?

stdioサーバー自体にはネットワークの入り口がないので、それを公開している側を監視します。supergatewayやmcp-proxyのようなブリッジの裏で動かしているなら、ブリッジのHTTPエンドポイントに server/discover とツール呼び出しを送ります。クライアントの中でローカルに動かしているだけなら外から確認する手段はないため、クライアントのMCPログを見ます。

MCP Inspectorは監視に使える?

使えます。CLIモードは1回リクエストを送って終了し、失敗の種類ごとに終了コードを返します。認証が必要なら 3、到達できなければ 4、isError: true やツールが見つからない場合は 5 です。cronやCIから実行し、終了コードごとに別のアラートにできます。

まとめ

古いMCPサーバーや一般的なWeb API向けに作ったヘルスチェックは、2026-07-28のサーバーでは判断を誤らせます。ping はなくなり、GETは拒否され、本当の失敗はHTTP 200 と isError: true で返ってくるからです。役割を層に分けましょう。コンテナの再起動は、プロセスだけを見るlivenessに任せます。人へのアラートは、server/discover、tools/list に期待するツールがあるか、依存先ごとの状態を返す読み取り専用の health_check ツールで判断します。判定はステータスコードではなく本文で行います。監視間隔はアイドルタイムアウトと課金方式から決め、監視のせいでスケールゼロのサーバーが常時起動にならないようにします。4つ目のシグナルとして、実トラフィックの isError の割合も見てください。

参考資料

NodeFlare

MCP専用ホスティングサービス

GitHubにあるMCPを簡単デプロイ・簡単運用で最適化
無料で始める

他の記事

MCPトークン2026年10月27日

どのMCPサーバーがトークンを食っている?MCP Token使用量をサーバー・ツール別に計測する方法【2026年版】

どのMCPサーバー・ツールがトークンを使っているかを突き止める方法。Claude Codeの/context・/usage・OpenTelemetryで分かること、Claude APIのusageとツール呼び出しの突き合わせ方、再送分まで数えるツール別コスト式、実測したバイト→トークン換算、キャッシュで順位が逆転する40リクエストの計算例をまとめました。

MCPセキュリティ2026年10月27日

MCPセキュリティテストの実践:自社MCPサーバーを検証する34項目のテスト計画とプローブ【MCP Security 2026-07-28版】

自社のMCPサーバーをリリース前とCIで検証するためのMCPセキュリティテスト。2026-07-28版仕様にもとづく期待応答(401、insufficient_scopeの403、Originの403、-32020)付きの34項目、仕込んだ4つのバグを誤検出なしで見つけたプローブ、ツール比較、CI設定、実際の事例。

MCPトークン2026年10月26日

MCP Tokenを減らす「ツールにしない」設計:ResourcesとPromptsの使い分けと費用計算【2026年版】

MCPサーバーのデータや手順は、ツール・リソース・プロンプトのどれで出すとトークンが少ないのか。省ける1往復、定義のコスト、プロンプトとサーバー説明文の比較をClaude Sonnet 5.5の料金で計算し、Claude Code・VS Code・Cursor・Claude APIの対応状況と2026-07-28版仕様の変更点をまとめました。

MCPサーバーのヘルスチェックと死活監視:pingが廃止された2026-07-28仕様で何を確認すべきか【MCP Hosting】 | Blog | Nodeflare