◉CLAUDE LABEN
●2.1.310 — Bedrock・Vertex・Foundry でも Monitor ツールが使え、ゲートウェイの失敗を修正しました(10/23)●2.1.307 — スリープやキーチェーンロックのあとに MCP が再接続されない問題を修正しました(10/21)。2.1.306 では claude -p の誤表示も解消●SONNET 4.5 — Claude API での提供終了は 11/30、残り34日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●API — 新しい API バージョン 2026-10-21 では temperature・top_p・top_k を渡すと 400 になります●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました●2.1.310 — Bedrock・Vertex・Foundry でも Monitor ツールが使え、ゲートウェイの失敗を修正しました(10/23)●2.1.307 — スリープやキーチェーンロックのあとに MCP が再接続されない問題を修正しました(10/21)。2.1.306 では claude -p の誤表示も解消●SONNET 4.5 — Claude API での提供終了は 11/30、残り34日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●API — 新しい API バージョン 2026-10-21 では temperature・top_p・top_k を渡すと 400 になります●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました
記事一覧/Claude Code
⟐ Claude Code/2026-07-29上級

MCPサーバーは繋がっているのに、ツールが0件で返る — 無人実行の権限劣化をツール表の差分で止める

資格情報が抜けてもMCPのハンドシェイクは成功し、tools/list は空配列を返します。例外が飛ばないまま実行が完走する構造と、期待ツール表との差分で無人実行を止めるpreflightの実装を、実測値つきで記録しました。

MCP59Claude Code276CI/CD19無人実行6運用設計30

✦ プレミアム記事

無人実行のログを見ていて、手が止まった瞬間がありました。

終了コードは 0。エージェントは筋の通った要約を返しています。けれど本来そこに載っているはずの、社内の集計サーバーから取った数字が、どこにも見当たりません。

エラーは1行も出ていませんでした。

原因は資格情報の欠落でした。MCPサーバーは起動し、ハンドシェイクも通り、そして提供したツールは0件。エージェントは「使えるツールがない」という前提で、手持ちの知識だけで答えを組み立てていました。破綻していないぶん、気づくのが遅れます。

この挙動は仕様として理解すれば当たり前ですが、実装を任せている側の感覚としては裏切られた気持ちになります。個人開発で夜間に回している処理では、翌朝まで誰も見ていません。

接続の成否と、ツールが生えているかは別の層にある

MCPの起動シーケンスは大きく2段です。initialize でプロトコル版と能力をすり合わせ、そのあと tools/list で実際に呼べるツールを取得します。

資格情報を見るのは、多くの実装で後者より更に奥、あるいは tools/list を組み立てる時点です。認証に失敗したサーバーが選ぶ振る舞いは、実装者の裁量に委ねられています。

  • エラーを返して接続ごと落とす
  • 接続は維持したまま、ツールを空で返す
  • ツール表は返すが、呼び出し時に初めて失敗する

このうち2番目が厄介です。プロトコル上は何も間違っていません。JSON-RPC のレスポンスは result を持ち、error は含まれません。クライアント側の例外ハンドラは何も捕まえられません。

そして Claude Code は、使えるツールが減っていても実行を止めません。止めない設計は対話中には親切ですが、無人実行では危うさに反転します。

資格情報の有無だけを変えて測る

推測で書きたくなかったので、依存ゼロの最小サーバーを立てて実際に測りました。標準入出力でJSON-RPCを話すだけの、80行ほどのPythonです。

#!/usr/bin/env python3
"""最小のstdio MCPサーバー。REPORT_API_TOKEN があるときだけツールを公開する。"""
import json, os, sys
 
TOKEN = os.environ.get("REPORT_API_TOKEN")
 
TOOLS = [
    {"name": "fetch_daily_report", "description": "Fetch the daily sales report",
     "inputSchema": {"type": "object", "properties": {"date": {"type": "string"}}, "required": ["date"]}},
    {"name": "list_report_dates", "description": "List available report dates",
     "inputSchema": {"type": "object", "properties": {}}},
]
 
def respond(rid, result):
    sys.stdout.write(json.dumps({"jsonrpc": "2.0", "id": rid, "result": result}) + "\n")
    sys.stdout.flush()
 
def main():
    for line in sys.stdin:
        line = line.strip()
        if not line:
            continue
        req = json.loads(line)
        method, rid = req.get("method"), req.get("id")
        if method == "initialize":
            # 資格情報の状態に関わらずハンドシェイクは成功する
            respond(rid, {
                "protocolVersion": "2026-07-28",
                "capabilities": {"tools": {}},
                "serverInfo": {"name": "report-server", "version": "1.0.0"},
            })
        elif method == "notifications/initialized":
            continue
        elif method == "tools/list":
            # 未認証時はエラーではなく「空配列」を返す
            respond(rid, {"tools": TOOLS if TOKEN else []})
        elif method == "shutdown":
            respond(rid, {})
            return
        elif rid is not None:
            respond(rid, {})
 
if __name__ == "__main__":
    main()

計測側は、ハンドシェイクの成否・tools/list の成否・得られたツール名を1回の起動でまとめて拾います。

#!/usr/bin/env python3
"""stdio MCPサーバーを叩いて、ハンドシェイクの結果とツール表を観測する。"""
import json, os, subprocess, sys, time
 
def probe(cmd, env):
    proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE, text=True, env=env, bufsize=1)
    def call(method, params=None, rid=None):
        msg = {"jsonrpc": "2.0", "method": method}
        if params is not None:
            msg["params"] = params
        if rid is not None:
            msg["id"] = rid
        proc.stdin.write(json.dumps(msg) + "\n"); proc.stdin.flush()
        if rid is None:
            return None
        return json.loads(proc.stdout.readline())
 
    t0 = time.perf_counter()
    init = call("initialize", {"protocolVersion": "2026-07-28", "capabilities": {},
                               "clientInfo": {"name": "preflight-probe", "version": "0.1"}}, rid=1)
    call("notifications/initialized")
    listed = call("tools/list", {}, rid=2)
    elapsed_ms = (time.perf_counter() - t0) * 1000
    call("shutdown", None, rid=3)
    proc.wait(timeout=5)
 
    tools = listed.get("result", {}).get("tools", [])
    return {
        "handshake_ok": "result" in init,
        "server": init.get("result", {}).get("serverInfo", {}).get("name"),
        "tools_list_ok": "result" in listed,
        "tool_count": len(tools),
        "tool_names": sorted(t["name"] for t in tools),
        "elapsed_ms": round(elapsed_ms, 1),
    }
 
if __name__ == "__main__":
    server = [sys.executable, os.path.join(os.path.dirname(__file__), "report_server.py")]
    for label, extra in (("credential present", {"REPORT_API_TOKEN": "test-token"}),
                         ("credential missing", {})):
        env = {k: v for k, v in os.environ.items() if k != "REPORT_API_TOKEN"}
        env.update(extra)
        print(f"--- {label} ---")
        print(json.dumps(probe(server, env), ensure_ascii=False))

手元のLinux環境(Python 3.10)で走らせた出力です。

--- credential present ---
{"handshake_ok": true, "server": "report-server", "tools_list_ok": true, "tool_count": 2, "tool_names": ["fetch_daily_report", "list_report_dates"], "elapsed_ms": 19.8}
--- credential missing ---
{"handshake_ok": true, "server": "report-server", "tools_list_ok": true, "tool_count": 0, "tool_names": [], "elapsed_ms": 18.6}

事前の予想と逆だった点が、ここに出ています。

私は「認証が通らなければハンドシェイクの段階で異常が観測できる」と思い込んでいました。実際には handshake_ok も tools_list_ok も両方 true のまま、違いは tool_count の 2 と 0 だけ。所要時間も 19.8 ミリ秒と 18.6 ミリ秒で、有意な差はありません。

失敗が「遅くなる」でも「例外になる」でもなく、「静かに数が減る」形で現れます。監視対象を接続性に置いていると、この差はどこにも記録されません。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦資格情報なしでも initialize と tools/list が成功する挙動を stdio とリモートHTTPの両方で実測(ツール数 2 → 0・所要時間に差なし)
✦stdio とリモートを1つの窓口で扱い、期限つきで観測して差分ゼロでなければ終了コード78で落とす preflight の実装
✦検証ツール自身に見つかった3つの欠陥 — Popen の位置・期限の効かない readline・失敗した観測をロックへ焼き付ける生成
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

⟐ Claude Code2026-09-01
MCPサーバが十数個を超えてから、起動直後の一覧を信用しなくなりました
起動直後に「接続できません」と並ぶサーバのうち、こちらから手を打てるものは一部だけです。応答なし・起動失敗・プロトコル拒否を分けて返すプローブを実装し、14台構成で逐次22.03秒が並列5.09秒まで縮むところ、待ち時間を詰めると健全なサーバが失敗に化けるところを実測で残しました。
⟐ Claude Code2026-10-23
夜間の claude -p で MCP が「認証待ち」になった朝、sleep と keychain を切り分ける事前確認を足しました
Claude Code 2.1.307 は macOS の sleep とログインキーチェーンのロックが原因の MCP 失敗を修正しました。修正の有無に関わらず効く事前確認スクリプトと、sleep 履歴との照合手順をまとめます。
⟐ Claude Code2026-10-23
夜間の claude -p で MCP が「認証が必要」と出る朝に、Mac のスリープとキーチェーンを疑う順番
無人で回した claude -p の朝のログに「needs authentication」や MCP のタイムアウトが残っているとき、Mac のスリープとログインキーチェーンのロックを先に確かめる手順です。v2.1.307 で変わった挙動と、版を上げても残る部分の線引きをまとめました。