MCPサーバーを6台つないだセッションで、最初のプロンプトを送る前に11万トークンが埋まっていました。中身はほぼツール定義。Claude Code v2.1.220 はこれを既定で遅延ロードし、実際に呼ぶツールだけをコンテキストに載せます。
MCPのツール定義が9万トークンを占めていた
数値の出どころは claude -p のJSON出力です。/context の表示でも内訳は読めますが、設定を切り替えて機械的に比較するなら usage の数字を直接見るほうが速い。
プロンプト合計を測る
cd /tmp
claude -p "OK とだけ答えて" --output-format json \
| python3 -c "
import json, sys
u = [e for e in json.load(sys.stdin) if e['type'] == 'result'][0]['usage']
keys = ('input_tokens', 'cache_creation_input_tokens', 'cache_read_input_tokens')
print(sum(u.get(k) or 0 for k in keys))
"
実行結果:
24534
キャッシュヒットの有無で input_tokens と cache_read_input_tokens の配分は変わるので、3つを足した値で比較します。これがモデルに渡ったプロンプト全体のサイズ。
遅延ロードを切って比べる
同じコマンドを ENABLE_TOOL_SEARCH=false で回します。
ENABLE_TOOL_SEARCH=false claude -p "OK とだけ答えて" --output-format json \
| python3 -c "
import json, sys
u = [e for e in json.load(sys.stdin) if e['type'] == 'result'][0]['usage']
keys = ('input_tokens', 'cache_creation_input_tokens', 'cache_read_input_tokens')
print(sum(u.get(k) or 0 for k in keys))
"
実行結果:
114646
差は 90,112トークン。つないでいたのは claude.ai 経由の Notion / Atlassian Rovo / Google Drive / Google Calendar / Gmail と、ローカル stdio の Snowflake MCP です。同じ検証を別の回に流したときは 94,673 で、差は 70,139 まで縮みました。npx がPATHから外れていて stdio 系4台が接続に失敗し、その分の定義が最初から存在しなかった回です。接続の成否で総量が動くため、削減幅は7万〜9万トークンの間で振れます。
設定値ごとの実測は次のとおり。
| ENABLE_TOOL_SEARCH | プロンプト合計 | 既定との差 |
|---|---|---|
| 未設定(既定) | 24,534 | – |
auto | 42,186 | +17,652 |
auto:80 | 42,186 | +17,652 |
false | 94,673 〜 114,646 | +70,139 〜 +90,112 |
しきい値を10%から80%へ広げても合計は動きませんでした。settings.json の env に "ENABLE_TOOL_SEARCH": "auto:5" を書いた場合も 42,204 で、環境変数で渡したときと同じ水準です。
defer_loading が削るのはコンテキストであってリクエストではない
公式ドキュメントの “Deferred tool loading” セクションが、この機能でよくある誤解を先に潰しています。
defer_loadingcontrols what enters the context window, not what you send in the request
遅延させたツールも、定義そのものは毎リクエストの tools 配列に含めて送ります。サーバー側が検索と tool_reference の展開に全定義を必要とするためです。減るのはコンテキストウィンドウの占有量であって、リクエストボディのサイズではありません。
ツールが見つかるまでの経路
- セッション開始時にコンテキストへ載るのは、検索ツールと非遅延ツールだけ
- Claudeが追加のツールを要求した時点で検索が走る
- APIが一致したツールを
tool_referenceブロックで返す(既定で最大5件) - APIがその参照を完全な定義へ展開し、Claudeが呼び出す
Claude Code側でこれを担当するのが ToolSearch ツール。セッション中に「The following deferred tools are now available via ToolSearch」という通知が挟まるのは、この展開が起きた瞬間です。ツール名とサーバーの instructions だけは起動時から載っているので、Claudeは何が存在するかを知った上で検索します。
プロンプトキャッシュが無効化されない理由
会話の途中でツール定義が増えるなら、キャッシュのプレフィックスが崩れそうに見えます。実装はそうなっていません。
Internally, the API excludes deferred tools from the system-prompt prefix. When Claude discovers a deferred tool through tool search, the API appends a
tool_referenceblock inline in the conversation, then expands it into the full tool definition before passing it to Claude. The prefix is untouched, so prompt caching is preserved.
展開先はsystem promptの前方ではなく会話の途中。プレフィックスが変わらないので、20ターン目でツールを1つ読み込んでもキャッシュは効いたままです。ただし defer_loading: true と cache_control は同じツールに同居できません。両方付けると400が返るので、キャッシュのブレークポイントは非遅延ツール側に置きます。
ENABLE_TOOL_SEARCH に指定できる値
既定は「全MCPツールを遅延」。しきい値方式に戻したいときだけ値を指定します。
| 値 | 挙動 |
|---|---|
| 未設定 | 全MCPツールを遅延。Google CloudのAgent Platform、および ANTHROPIC_BASE_URL がfirst-party以外を指す場合は前倒しロードにフォールバック |
true | 全MCPツールを遅延。Agent Platformやプロキシ経由でもベータヘッダを送るため、tool_reference 非対応のプロキシではリクエストが失敗する |
auto | しきい値方式。コンテキストウィンドウの10%に収まれば前倒しロード、超えた分を遅延 |
auto:N | しきい値をN%(0〜100)に変更。例: auto:5 |
false | 遅延なし。全ツールを前倒しロード |
ToolSearch ツール自体を封じたい場合は permissions.deny に "ToolSearch" を並べる手もあります。ただしMCPツールは遅延されたままなので、Claudeから見えるツールがゼロになる点には注意が要ります。
alwaysLoad でサーバー単位に遅延から外す
{
"mcpServers": {
"core-tools": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"alwaysLoad": true
}
}
}
この .mcp.json を置いたセッションでは、core-tools のツールだけが起動時からコンテキストに載り、他のサーバーは遅延のままになります。ENABLE_TOOL_SEARCH の値に関わらず優先されるのがこのフィールドの性質。
効果が出るのは定義が大きいサーバー
手元のSnowflake MCP(ツール6個)を --strict-mcp-config で単独接続し、alwaysLoad のあり/なしで測ると 24,552 と 24,413 でした。定義が小さいサーバーでは、常時ロードに切り替えてもコンテキスト消費はほぼ動きません。公式の “Exempt a server from deferral” も、対象を絞るよう明記しています。
Use this for a small number of tools that Claude needs on every turn, since each upfront tool consumes context that would otherwise be available for your conversation.
起動が最大5秒ブロックされる
alwaysLoad: true にはもう1つ副作用があります。MCPの起動は既定で非ブロッキングですが、常時ロード指定のサーバーは最初のプロンプトを組み立てる時点でツールが揃っている必要がある。そのため接続完了まで起動が待たされます。上限は標準の接続タイムアウトと同じ5秒。他のサーバーは裏で接続を続けます。
フィールド自体の対応は v2.1.121 以降。サーバー側から個別ツールを指定することもでき、その場合はツールの _meta に "anthropic/alwaysLoad": true を入れます。MCPサーバーの置き場所とスコープの決め方はClaude Code MCPのスコープ管理—.mcp.jsonでチーム共有する設計で扱いました。
tool search が黙って無効になる条件
「既定で有効」と書かれていても、条件次第で前倒しロードに戻ります。戻ったことは画面に出ません。トークン消費が急に増えたときの調べ先はこのあたり。
プロキシとAgent Platform
ANTHROPIC_BASE_URL がfirst-party以外のホストを指していると、Claude Codeは自動で tool search を切ります。多くのプロキシが tool_reference ブロックを転送しないためです。Google CloudのAgent Platformでも既定でオフ。どちらも ENABLE_TOOL_SEARCH=true の明示で上書きできますが、転送に対応していないプロキシではリクエストごと失敗します。
ベータヘッダを剥がす環境変数
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS を設定していると tool search はオフのまま固定され、ENABLE_TOOL_SEARCH では上書きできません。この変数は defer_loading と tool_reference が必要とするベータヘッダごと落とすためです。社内ポリシーで実験的機能を止めている環境では、こちらが先に効いています。
モデル要件
必要なのは tool_reference ブロックに対応したモデル。Claude Sonnet 4.5 / Haiku 4.5 / Opus 4.5 以降が対象で、Opus 4.1 以前は非対応です。API側のツール版数は tool_search_tool_regex_20251119 と tool_search_tool_bm25_20251119 の2種類。/model で古いモデルに落とした瞬間にコンテキストが膨らむ、という並びは起こり得ます。
自作エージェントに defer_loading を持ち込む
同じ仕組みはClaude API側でも使えます。MCPを介さず自前でツールを定義しているエージェントなら、こちらが本体です。
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5",
max_tokens=2048,
messages=[{"role": "user", "content": "What is the weather in San Francisco?"}],
tools=[
{"type": "tool_search_tool_regex_20251119", "name": "tool_search_tool_regex"},
{
"name": "get_weather",
"description": "Get the weather at a specific location",
"input_schema": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
"defer_loading": True,
},
],
)
返ってくる content は、検索の実行と結果と本命の呼び出しが1本に並んだ形になります。
[
{"type": "server_tool_use", "id": "srvtoolu_01ABC123",
"name": "tool_search_tool_regex", "input": {"pattern": "weather"}},
{"type": "tool_search_tool_result", "tool_use_id": "srvtoolu_01ABC123",
"content": {"type": "tool_search_tool_search_result",
"tool_references": [{"type": "tool_reference", "tool_name": "get_weather"}]}},
{"type": "tool_use", "id": "toolu_01XYZ789", "name": "get_weather",
"input": {"location": "San Francisco", "unit": "fahrenheit"}}
]
srvtoolu_ で始まるIDに tool_result を返してはいけません。検索はAnthropic側で実行済みで、こちらが結果を返すとリクエストが拒否されます。実行して結果を返すのは末尾の toolu_ のほうだけ。次のターンでは server_tool_use と tool_search_tool_result をそのまま履歴に戻します。
regex と BM25 の違い
検索ツールは2種類あり、Claudeが投げるクエリの形が変わります。
tool_search_tool_regex_20251119: Python のre.search()パターン。大文字小文字は区別せず、上限200文字tool_search_tool_bm25_20251119: 自然言語クエリ。上限500文字
どちらもツール名・説明・引数名・引数の説明を対象に検索します。ツール名に github_ slack_ のような接頭辞を揃えておくと、1回の検索でサービス単位のまとまりが取れます。命名はサーバーを書く段階で決めておく話。
400で弾かれる書き方
全ツールに defer_loading: true を付けると、検索ツール自身も遅延対象になってリクエストが通りません。
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "At least one tool must have defer_loading=false. All tools cannot be deferred."
}
}
tool_reference が指すツールの定義を tools 配列から落とした場合も同じく400です。展開元がないためで、メッセージは Tool reference 'unknown_tool' not found in available tools。遅延ツールの上限は1リクエストあたり10,000個なので、実運用でここが天井になることはまずありません。
MCPサーバーを配る側が書くもの
サーバーの instructions フィールドは、tool search 下では検索のヒントとして読まれます。Claudeが起動時に持っているのはツール名と instructions だけ。「どういうタスクのときに自分を検索すべきか」をここで伝える必要があります。公式ドキュメントは Skills と同じ発想だと説明しています。
字数制限もあります。ツールの説明とサーバー instructions は、いずれも 2KB で切られる。重要な語句を後半に置くと、検索対象から丸ごと消えます。MCPの機能面はClaude Code MCPの使い方—リソースを@参照、プロンプトを/で実行を参照してください。
まとめ
- Claude Code v2.1系では tool search が既定で有効。「有効化する」手順を紹介している記事はv2.0系時点のもの
- 手元の6サーバー構成では、遅延ロードの有無で 24,534 対 114,646 トークン。差は7万〜9万
defer_loadingが削るのはコンテキストの占有量。リクエストには全定義を毎回送る- 展開は会話の途中に挿入され、system promptのプレフィックスは変わらないのでプロンプトキャッシュは維持される
- 毎ターン使うサーバーだけ
alwaysLoad: trueで常時ロードに戻す。起動が最大5秒ブロックされる CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETASが設定されているとENABLE_TOOL_SEARCHは効かない
起動直後に何がコンテキストを埋めているかは /context で確認できます。読み方はClaude Code /contextの読み方—起動直後に何がトークンを消費するかにまとめました。

