以下は、複数のMCPサーバーに接続するクライアント側プログラムの開発方法についてまとめたブログ記事です。MCPクライアントの自作経験はあるものの、より実践的なTIPSや設計ノウハウを知りたい開発者向けの内容になっています。
Model Context Protocol(MCP)は、LLM(Large Language Model)やAIツールとのやり取りを標準化するプロトコルとして進化を続けています。現在は STDIO 経由での利用が中心ですが、今後はネットワーク越しのリモート呼び出しが増えることが ロードマップ に示唆されています。
本記事では、複数のMCPサーバーに接続するクライアントを開発する際のポイントを、高レベルなアーキテクチャから具体的なTipsまで整理して紹介します。既に単一サーバー向けに MCP クライアントを自作したことがある方にとって、「どうやって複数のサーバーをまとめて管理するか」を実践するうえで役立つ内容となっています。
MCPサーバーは、LLMをはじめとするAI機能を外部プロセスまたはリモートサービスとして扱うための共通インターフェースを提供します。
ですが、実際の開発現場では下記のような理由により複数のMCPサーバーを利用するケースが増えてきています。
- 役割ごとの分割
- 例: 「文章要約」を行うサーバーAと、「画像認識」を行うサーバーB
- 負荷分散・フェイルオーバー
- 負荷の高いタスクは別のサーバーに回したり、障害時に切り替えたりする
- STDIOサーバーとリモートサーバーの共存
- ローカル開発時は STDIO 接続、運用時は HTTP/HTTPS 経由のリモートサーバーなど
このように、単一サーバーだけでなく複数のMCPサーバーをまとめて取り扱う仕組みが必要になります。
- MCPプロトコルに準拠したコマンドを発行し、応答を受け取る側のプログラム
- mcpservers.json のような設定ファイルを読み込み、複数のサーバーをまとめて初期化・管理する役割を担う
- LLMや各種AIツールを実際に動かす。
- STDIOだとローカル環境(子プロセス)として動作、リモートだとHTTP/WebSocketなど経由で呼び出し
- タスクの内容や文脈から、「どのツールを使うか」を自然言語ベースでLLMに決定させる
- 実際の接続先決定やフェイルオーバーはクライアント側のロジックが制御する場合も多い
複数のサーバーを扱う場合、**ユーザー(開発者)が「ここにサーバー一覧を書けばOK」**と分かりやすい形式を用意すると、運用や拡張が容易です。
mcpservers.json
のようなファイルを用意し、以下のような情報を定義します。
[
{
"id": "local-llm",
"type": "stdio",
"cmd": "python",
"args": ["-m", "some_local_mcp_llm"]
},
{
"id": "remote-llm",
"type": "remote",
"url": "https://example.com/api/mcp",
"apiKey": "SECRET_TOKEN"
}
]
- id: 識別用のサーバーID
- type:
stdio
orremote
など - cmd, args: STDIOサーバーを起動するコマンド・引数
- url, apiKey: リモート呼び出しのエンドポイントURLやトークン
このファイルを編集するだけで新しいサーバーの追加や不要サーバーの削除が可能になります。
サーバーが提供するツールや機能の一覧も、JSONファイルや別の方法で定義しておくと便利です。
LLMに対して「サーバーAは文章要約、サーバーBは画像認識」という情報を与える場合でも、そこからプロンプトの一部を生成することができます。
ClineのMcpHub.ts の例 では、複数のMCPサーバーをひとつのHubクラスで一元的に管理しています。
このように「ラップレイヤー」を用意する設計が、現在のベストプラクティスとして推奨されます。
class McpHub {
private servers: Record<string, McpClient>;
constructor(mcpServersConfigPath: string) {
const config = this.loadConfig(mcpServersConfigPath);
this.servers = this.initializeClients(config);
}
public async callTool(toolId: string, input: any): Promise<any> {
const serverId = this.resolveServerByTool(toolId);
const client = this.servers[serverId];
return client.callTool(toolId, input);
}
// ... 追加のロードバランシング・エラーハンドリングなど ...
}
- 設定ファイル読み込み:
mcpservers.json
の内容を参照し、サーバーインスタンスを初期化 - サーバーID → McpClient のマッピングを作り、呼び出し時に適切なサーバーを選択
- 実際の通信やエラー処理は、McpClient(STDIOまたはリモート実装)に委譲
ロードマップにある通り、STDIOからリモートに移行するサーバーも増えていきます。
そこで、type
フィールドで分岐し、STDIOならプロセス生成、リモートならHTTP/WebSocket といった具体的処理をHub内部で切り替えます。呼び出し元はcallTool()
といった共通メソッドを呼ぶだけでOKです。
llms-full.txt のように、LLMによる対話フローの中で「次に呼び出すツールは何か」「どのサーバー(AIモデル)を使うか」を決めるケースがあります。
- LLMに促す: 「あなたのタスクは ‘文章要約’ です。この機能を実行するには ‘toolId: summarizer’ を選んでください。」
- LLMの出力:
<tool: summarizer> ユーザーの入力文書をまとめます
この際、実際にどのサーバーで’summarizer’機能を提供しているかは、Hubが持っているマッピング情報を使って決定します。
LLMに接続先URLやコマンドラインといった詳細を意識させる必要はなく、ツールIDやタスク名だけを与えれば十分です。
LLMが「サーバーAがダメならサーバーBを試そう」と逐一考えるより、Hub側で以下のような仕組みを持つほうが安定します。
- ツール呼び出しに失敗 → リトライ(数回)
- リトライしても失敗 → Hubが代替サーバーを選び直す
- ユーザーやLLMに「フェイルオーバーで別のサーバーを使いました」と結果を返す
こうしておけば、LLMや呼び出し元のアプリケーションがエラー時のリカバリをすべて意識する必要がなく、Hubの内部ロジックだけで対処できるため、運用コストが下がります。
LLMのやり取り上は「タスク失敗時はどうするか」を再プロンプトで投げかける必要がほぼなくなるので、シンプルな会話フローを保ちやすいのもメリットです。
- mcpservers.json を環境別に用意する:
mcpservers.dev.json
(テスト用のサーバー定義)mcpservers.prod.json
(本番用サーバー定義)
- Hubのコンストラクタに渡すファイルパスを、
NODE_ENV
や環境変数で切り替えると便利です。
- どのサーバーを選んだか、呼び出しにどれくらい時間がかかったかなどを、Hub内部で一元的にログ出力する設計が望ましいです。
- 特に複数サーバーが並行して動いている場合、ログやメトリクスを管理できる仕組みをあらかじめ準備しておくと障害解析が簡単になります。
- LLMが呼び出したいツールを特定する際、「ツール一覧」「各ツールの概要」をまとめたプロンプトを少量で済むようにする工夫がポイントです。
- 例:ツールIDと短い説明だけを列挙する
- 詳細な接続先情報は渡さず、機密性を保つ
- ツールIDやタスク名が固定的である場合は、ユーザーとLLMのやり取りにおいても**「次に行うツール=summarizer」**のようにリファレンスしやすくしておくと混乱が減ります。
- 裏でフェイルオーバーをしても結果が成功すれば問題ないケースがほとんどですが、呼び出し先が変更されたことをユーザーに通知したい場合もあります。
- その際は、Hubが「最初のサーバー呼び出しに失敗→別サーバー呼び出しに成功」というイベントをフックして、フロントエンドやログに情報を残すようにするとよいでしょう。
- APIキーやトークンが必要なサーバーの場合、
mcpservers.json
内に直接平文で書くとリスクがあります。- 別ファイルや環境変数に保存し、Hub初期化時に読み込む工夫が必要です。
- 外部リモートサーバーへの接続時は、**通信暗号化(HTTPS/WSSなど)**も必須です。
- mcpservers.json のような外部設定ファイルを用意し、複数のMCPサーバー情報(STDIO / リモートなど)を一元管理する
- Hub(ラップ層) を用いて、複数サーバーへの接続・切り替え・フェイルオーバーをシンプルなAPIで呼び出せるようにする
- LLMに与えるのは「どのツールを呼ぶか」というタスク情報であり、実際のサーバー接続やエラー処理はHubに任せて責務を分離する
これらの設計を組み合わせれば、今後のMCPアップデートやリモート化の進展にもスムーズに対応しやすくなります。
単一サーバー向けMCPクライアントの自作を経験されている方は、ぜひ一歩進んだ「複数サーバー対応設計」にチャレンジしてみてください!
以上、複数サーバーを扱うMCPクライアント開発のベストプラクティスでした。ぜひご自身のプロジェクトにも取り入れてみてください!