Claude Media
MCPホストをスケールさせる設計パターン — 数千のツールをどう捌くか

MCPホストをスケールさせる設計パターン — 数千のツールをどう捌くか

MCPホストが抱えるサーバー・ツールが増えたときのスケール手法。段階的なツール開示とコード実行による呼び出しの2パターンを解説します。

MCPホストのツールが増えると何が起きるか

MCPホストが接続するサーバーとツールが増えるほど、素朴な実装は破綻します。すべてのツール定義を会話の最初にまとめてモデルへ渡す方式は、ツール数が数十を超えたあたりからコンテキストウィンドウの大半を定義だけで消費し、レイテンシと精度の両方を悪化させます。公式ドキュメントは、この状態を「モデルがユーザーのメッセージを読む前に、ツール定義だけでコンテキストの大部分を使い切る」と表現しています。

もう1つの負荷は、ツール呼び出しの結果です。複数のツールを連鎖させる処理(文書を読む→変換する→別の場所に書く)では、途中の結果がそのままモデルのコンテキストを通過し、トークンを消費し続けます。この2つの問題に対応するパターンが2つあります。Progressive Discovery(段階的なツール開示)Programmatic Tool Calling(コード実行によるツール呼び出し)です。前者は「いつツール定義をコンテキストに入れるか」を制御し、後者は「ツールをどう呼び出すか」を制御します。

段階的なツール開示(Progressive Discovery)とは何か — カタログ・検査・実行の3層

Progressive Discoveryは、接続済みサーバーの全ツール定義を最初から渡すのではなく、モデルが必要とした時点でだけ定義を読み込む方式です。実装の一例として、公式ドキュメントは3層構造の検索ベース実装を挙げています。

  1. カタログ層: search_toolsのような軽量な検索メタツールを1つだけモデルに渡す。自然文クエリを受け取り、候補ツールの名前と一行説明だけを返す
  2. 検査層: モデルが候補を1つに絞ったら、そのツールだけの完全な定義(入力スキーマ・出力スキーマ)をget_tool_detailsのような形で取得する
  3. 実行層: 完全な定義を得たツールだけを実際に呼び出す

検索層の実装は複数の選択肢があります。BM25や正規表現によるキーワードマッチングは実装が単純で、ツール名や説明文が具体的なら十分な精度が出ます。埋め込みベースの類似検索は、同義語や意味的な近さを拾えるぶん計算コストが増えます。小型モデル(Claude HaikuやGemini Flashのような高速モデル)にツール選定そのものを任せるサブエージェント方式は、精度が高い一方でコストもかさみます。

両者のスコアを組み合わせるハイブリッド方式や、用途ごとに戦略を切り替える構成も選べます。この3層の原則は、どの検索戦略を選んでも変わりません。AnthropicやOpenAIのようにプラットフォーム側がツール検索機能を提供している場合は、まずそちらを使う方が近道です。自前実装が必要になるのは、プラットフォームが対応していないか、ドメイン固有のランキングやアクセス制御が要る場合です。

切り替えのタイミングをどう判断するか

ツールの数が少なく定義がコンテキストの小さな割合しか占めないなら、全件読み込みで十分です。公式は、ツール定義がコンテキストウィンドウの1〜5%を超えたあたりを、Progressive Discoveryへ切り替える目安のしきい値として挙げています。数値そのものより、「定義の総量を測って動的に切り替える」という設計を先に組み込んでおくことが重要です。

サーバー単位でも同じ原則が働く

Progressive Discoveryは個々のツールだけでなく、サーバー単位にも適用できます。起動時に全サーバーへ一括で接続せず、次のような運用にできます。

  1. 利用可能なサーバーとその概要をレジストリとして持つ
  2. モデルが必要と判断した時点でサーバーに接続する
  3. タスクに不要になったサーバーは切断してコンテキストを解放する

ユーザーの意図が事前に分からない汎用エージェントで特に効果があり、常時接続する最小限のサーバー群だけを起動時に持たせ、残りは必要になってから繋ぎます。Agent Skillsと組み合わせる設計もあります。スキルファイル側に「このスキルにはどのMCPサーバーが要るか」を宣言させておき、そのスキルが呼ばれたタイミングでだけホストが該当サーバーへ接続する、という連携です。

実装時に確認しておきたいポイントを4つにまとめます。

ガイドライン理由
詳細度を複数用意する理由名前のみ・名前と説明・完全なスキーマの3段階から、モデルが必要な粒度を選べるようにする
取得済み定義をキャッシュする理由一度サーバーから取得した定義をホスト側でメモ化し、再注入のたびにtools/listを呼び直さない
list_changedで再インデックス理由サーバーが変更通知を送ってきたら、検索カタログをその場で作り直す
ツールをサーバー単位でグループ化する理由モデルが関連するツール群として推論できるよう、提供元サーバーごとに整理して提示する

補足として、tools/listなどの一覧結果にはttlMscacheScopeが付き、これに従ってホスト側で定義をメモ化できます。プロンプトキャッシュとの相性にも注意が要り、tools配列を会話の途中で並べ替えると、それだけでキャッシュが無効化されコストが跳ね上がります。新しく発見した定義はキャッシュの区切りより後ろに追記するか、call_tool({name, args})のような単一の安定したメタツール経由に統一するのが公式の推奨です。

コードでツールを合成する(Programmatic Tool Calling)とは何か

本記事はMCP仕様レベルでのホスト実装設計を扱います。同じProgrammatic Tool Callingを「数百ツールを抱えたMCPホスト」の現実解として、Anthropicエンジニアリングブログの削減事例から読み解いた記事にMCPでコード実行があります。実装の詳細を先に知りたい場合はそちらも参照してください。

直接的なツール呼び出しは、1回の呼び出しごとにモデルとのラウンドトリップが発生し、結果全体がモデルのコンテキストに戻ります。Programmatic Tool Calling(Code Modeとも呼ばれる)は、モデルにツールを直接呼ばせません。代わりにツールを呼び出すコードを書かせ、そのコードをサンドボックスで実行する方式です。モデルに返るのは実行結果そのものではなく、コードが出力した要約だけです。

実装の流れは3ステップです。まずホストがMCPのツールスキーマから型付きAPIを生成します。outputSchemaをサーバーが提供していれば戻り値の型まで正確に生成できますが、無い場合は汎用型で受けるか、小型モデルで構造化を試みるかの二択になります。次にモデルがそのAPIを呼ぶスクリプトを書きます。たとえば「直近1時間のエラーログをすべて集めて重複を除き、種類ごとにチケットを作る」という処理なら、ログ1件ずつをモデルに読ませず、サンドボックス内でフィルタして実行します。最後にサンドボックスがコードを実行し、関数呼び出しはホスト経由でMCPサーバーへ中継され、console.logの出力など最小限の結果だけがモデルに返ります。

サンドボックスの選び方

実行言語代表的な実行環境特徴
JavaScript代表的な実行環境Deno / isolated-vm特徴V8ベース。権限を細かく無効化でき、完全なロックダウンも可能
Python代表的な実行環境Monty(実験的)特徴AI用途向けの最小限のPythonインタプリタ。既定でI/Oなし
TypeScript代表的な実行環境pctx(初期段階)特徴コードモードの概念をライブラリ化。Rust製の低レイヤーを持つ
任意の言語(Wasm経由)代表的な実行環境Wasmtime特徴任意の言語をWasmにコンパイルし、ケーパビリティベースで実行

どのサンドボックスを選んでも統合パターンは同じです。ホストが関数スタブを注入し、プロセス内通信かstdio経由で呼び出しを横取りして、tools/callリクエストとしてMCPサーバーへ振り分けます。ネットワーク権限は常に拒否したままで構いません。

セキュリティで確認しておきたいこと

コード実行という経路が増える以上、直接呼び出しと同じ確認は省略できません。スクリプトの承認は、そのスクリプトが実行時に行うすべてのツール呼び出しを一括承認したことにはなりません。ブローカー(ホスト)は各呼び出しをその承認範囲と照らして評価する必要があります。あるサーバーの結果を別のサーバーへの入力として使う場合、その結果は「信頼できない入力」として扱う必要があります。出力を切り詰めるだけでは、機密情報を少しずつ外部に持ち出す攻撃を防げません。

エラー処理 — isErrorを例外に変換する

MCPのツールエラーは、通信自体の失敗ではなくisError: trueを持つ正常なレスポンスとして返ってきます。生成されたラッパー関数は、このisErrorを検知したら例外としてスローし直すように作るのが公式の推奨です。そうすることで、モデルが書いたコード側は普通のtry/catchでエラーを扱えます。スクリプトが未捕捉のエラーで止まった場合は、その例外をスクリプトの実行結果としてそのままモデルに返し、モデル自身に「途中まで実行された副作用が残っているかもしれない」と判断させて自己修正させます。

2つのパターンは組み合わせて使う

Progressive DiscoveryとProgrammatic Tool Callingは競合しません。モデルは検索ツールで必要なツールを見つけ、スキーマを読み込み、それらを呼び出す1本のスクリプトを書いて一度に実行できます。定義のトークンコストと、結果のトークンコストの両方を同時に抑えられる組み合わせです。

Agent SDKでは何がすでに実装されているか

自分でホストを実装せず、Anthropic純正のAgent SDKを使う場合は事情が変わります。Progressive Discoveryに相当する機能は、すでにENABLE_TOOL_SEARCHとして組み込まれています。既定でオンになっており、関連度の高い上位5件だけを読み込みます。圧縮のたびにコンテキストから外れて再検索する挙動まで含めて実装済みです(詳細はAgent SDK Tool Searchの使い方)。本記事で扱った3層の原則は、Agent SDKを使わず独自のホストアプリケーションを書く場合や、Tool Searchの挙動そのものを理解したい場合に効いてきます。Programmatic Tool Callingのようなサンドボックス実行の仕組みまで既製のSDKがカバーしているかは、製品ごとに差があります。採用前に各SDKのドキュメントで対応状況を確認するのが確実です。

まとめ

ツールが数十を超えたら、まず全件読み込みからProgressive Discoveryへの切り替えしきい値を設計に組み込みます。ツールの連鎖処理でトークンを圧迫しているなら、Programmatic Tool Callingでサンドボックス実行に寄せることを検討します。どちらも「モデルのコンテキストに何を入れるか」をホスト側が制御するという同じ発想から来ており、MCPサーバーの数やツールの数が増えるほど効果が大きくなります。MCPの基本的な仕組みはMCPとはにまとめています。自分でホストアプリケーションを書く手順はMCPクライアント自作ガイドで扱っています。

この記事を共有:XはてブLinkedIn
MCP をもっと見る →