CLAUDE LABEN
2.1.268 — Claude Code の最新版が出ました。目玉は新機能ではなく、互換エンドポイントを使う人を2.1.265 からずっと止めていた不具合の修正ですBASE_URL — ANTHROPIC_BASE_URL を立てた第三者の互換エンドポイントが、全ターン HTTP 400 になっていました。設定を疑う前に版を上げてくださいWEBFETCH — 応答を閉じないサーバーで WebFetch が永久に待つ問題に、300秒の期限が付きました。CLAUDE_CODE_WEBFETCH_DEADLINE_MS で変えられますSECRET — MCP 設定の ${VAR} から解決した秘密情報が /mcp や claude mcp list に表示されていました。画面を共有する方はご確認くださいCPU — アイドル状態の長いセッションが CPU コアを張り付かせるビジーループも直っています。ファンが回り続ける心当たりがある方へRESUME — --continue と --resume が SessionStart フックを待たずに会話を表示するようになりました。起動まわりの体感が変わります2.1.268 — Claude Code の最新版が出ました。目玉は新機能ではなく、互換エンドポイントを使う人を2.1.265 からずっと止めていた不具合の修正ですBASE_URL — ANTHROPIC_BASE_URL を立てた第三者の互換エンドポイントが、全ターン HTTP 400 になっていました。設定を疑う前に版を上げてくださいWEBFETCH — 応答を閉じないサーバーで WebFetch が永久に待つ問題に、300秒の期限が付きました。CLAUDE_CODE_WEBFETCH_DEADLINE_MS で変えられますSECRET — MCP 設定の ${VAR} から解決した秘密情報が /mcp や claude mcp list に表示されていました。画面を共有する方はご確認くださいCPU — アイドル状態の長いセッションが CPU コアを張り付かせるビジーループも直っています。ファンが回り続ける心当たりがある方へRESUME — --continue と --resume が SessionStart フックを待たずに会話を表示するようになりました。起動まわりの体感が変わります
記事一覧/Claude Code
Claude Code/2026-07-29上級

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

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

MCP53Claude Code254CI/CD19無人実行6運用設計25

プレミアム記事

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

終了コードは 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_oktools_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/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

関連記事

Claude Code2026-09-01
MCPサーバが十数個を超えてから、起動直後の一覧を信用しなくなりました
起動直後に「接続できません」と並ぶサーバのうち、こちらから手を打てるものは一部だけです。応答なし・起動失敗・プロトコル拒否を分けて返すプローブを実装し、14台構成で逐次22.03秒が並列5.09秒まで縮むところ、待ち時間を詰めると健全なサーバが失敗に化けるところを実測で残しました。
Claude Code2026-07-18
宣言したMCPサーバーが一覧から消えていた — 名前空間をベンダーと共有する前提で設定を組み直す
MCPサーバー名がベンダーに予約され、自分の .mcp.json の宣言が黙って無効になる。無人実行で気づけないこの欠落を、宣言と実体を突き合わせる preflight と接頭辞規約で落とす設計をまとめました。
Claude Code2026-07-16
夜間セッションの常駐メモリが朝には数GB — 蓄積の四つの出どころと、RSS を刻んで切り分ける
夜間に回した Claude Code のプロセスを朝に見ると、常駐メモリが数GBまで膨らんでいました。2.1.209 で塞がれた四つの蓄積源と、自分の構成に残る分を RSS の傾きで切り分ける方法、しきい値で畳む運用までを個人開発の目線で整理します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます