Dynamic MCP サーバー管理

McpServerManager は、ローカル MCP サーバーの子プロセスの実行時レジストリを保持します。ワークスペース、テナント、管理者の選択、またはデプロイ設定によって統合が変わる場合に使用してください。

これはリモート HTTP サービスの接続プールではありません。それらは McpHttpClientBuilder で構築し、ライフサイクルはリモート構成を所有するアプリケーション側で管理してください。

ライフサイクル

Rendering architecture…

モニターは MCP 接続が切断されたかどうかを確認します。クラッシュした開始や失敗した開始の再試行は、設定された RestartPolicy に残り試行回数がある間だけ行われます。

設定

{
  "mcpServers": {
    "workspace-tools": {
      "command": "/opt/company/bin/workspace-mcp",
      "args": ["--stdio", "--root", "/srv/workspace"],
      "env": {
        "RUST_LOG": "info"
      },
      "disabled": false,
      "autoApprove": [],
      "restartPolicy": {
        "initialDelayMs": 500,
        "maxDelayMs": 15000,
        "backoffMultiplier": 2.0,
        "maxRestartAttempts": 5
      }
    }
  }
}

サーバー ID は ASCII の文字、数字、ハイフン、アンダースコアを受け付けます。これは衝突前置き付きのツール名の一部になるため、安定した ID を使用してください。

autoApprove は設定互換性のために読み書きされます。このフィールドからは、マネージャーの承認は得られません。

ツールの開始と集約

use adk_tool::mcp::manager::McpServerManager;
use std::sync::Arc;
use std::time::Duration;

let manager = Arc::new(McpServerManager::from_json_file("mcp.json")?
    .with_name("workspace_mcp")
    .with_health_check_interval(Duration::from_secs(15))
    .with_grace_period(Duration::from_secs(2)));

let outcomes = manager.start_all().await;
for (server_id, outcome) in outcomes {
    if let Err(error) = outcome {
        eprintln!("{server_id}: {error}");
    }
}

manager.start_monitoring();

let agent = LlmAgentBuilder::new("operator")
    .model(model)
    .toolset(manager.clone())
    .build()?;

子プロセスがその MCP ハンドシェイクを完了する間、レジストリの変更は直列化されます。start_all は有効な各サーバーごとに独立した結果を返しますが、現時点では起動は並列ハンドシェイクの経路ではありません。

実行時にレジストリを変更する

manager.add_server("billing".into(), billing_config).await?;
manager.start_server("billing").await?;

manager.update_server("billing", replacement_config).await?;
manager.disable_server("billing").await?;
manager.enable_server("billing").await?;

let snapshot = manager.all_configs().await;
manager.save_json_file("mcp.json").await?;

manager.remove_server("billing").await?;
manager.shutdown().await?;

稼働中のサーバーを更新すると、そのサーバーは停止され、置き換え対象が起動されます。置き換え対象が失敗した場合、マネージャーは置き換えエラーを返す前に、以前の定義を復元して再起動します。

save_json_file は宛先ディレクトリに一時ファイルを書き込み、その後それを宛先の上にリネームします。

リソース、プロンプト、および通知

管理されたサーバーは、ツールに加えてリソースとプロンプトを公開できます。マネージャーは各サーバーのリソースおよびプロンプトの表面をサーバー ID ごとに公開し、すべての管理対象接続で共有されるハンドラーへ resources/updated / resources/list_changed 通知を配信します。

ハンドラーは一度登録してください。手動および自動の再起動をまたいで保持されます:

use adk_tool::{ResourceNotificationHandler, mcp::manager::McpServerManager};
use std::sync::Arc;

struct ReloadOnChange;

#[async_trait::async_trait]
impl ResourceNotificationHandler for ReloadOnChange {
    async fn handle_resource_updated(
        &self,
        uri: &str,
    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        tracing::info!(%uri, "resource changed; re-read it to refresh cached state");
        Ok(())
    }

    async fn handle_resource_list_changed(
        &self,
    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        Ok(())
    }
}

let manager = Arc::new(
    McpServerManager::from_json_file("mcp.json")?
        .with_resource_notification_handler(Arc::new(ReloadOnChange)),
);
manager.start_server("workspace-tools").await?;

その後、サーバーごとに読み取りと購読を行います:

let resources = manager.list_server_resources("workspace-tools").await?;
let templates = manager.list_server_resource_templates("workspace-tools").await?;
let contents = manager.read_server_resource("workspace-tools", "config://policy").await?;

let prompts = manager.list_server_prompts("workspace-tools").await?;
let review = manager
    .get_server_prompt("workspace-tools", "review_pr", None)
    .await?;

// Subscribe / unsubscribe. Subscriptions are restored automatically if the
// managed process reconnects.
manager.subscribe_server_resource("workspace-tools", "config://policy").await?;
manager.unsubscribe_server_resource("workspace-tools", "config://policy").await?;

*_server_* メソッドは、ID によって 1 つの稼働中サーバーを対象にし、サーバーが不明、または現在実行中でない場合は AdkError::Tool を返します。

単一の接続(マネージャーのレジストリではなく)では、同じ表面が McpToolset 上で McpToolset::with_handlerslist_resourcesread_resourcelist_promptsget_prompt、および subscribe_resource を通じて直接利用できます。実行可能な agentic の例を参照してください:

cargo run --manifest-path examples/mcp_resources/Cargo.toml --bin resources-client

ツール名の衝突

2 つの稼働中サーバーが search を公開すると、集約後の名前は次のようになります:

crm__search
knowledge__search

1 つのサーバーだけが公開するツール名は変更されません。

終了時の挙動

サーバーを停止すると、その MCP セッションがキャンセルされ、接続が閉じるまで設定された猶予期間の間待機し、その後トランスポートを破棄します。子プロセスは、別途保持されたプロセスハンドルではなく、TokioChildProcess トランスポートによって所有されます。

マネージャーを破棄する前に shutdown() を呼び出してください。稼働中のサーバーを持つマネージャーを破棄すると、Drop は非同期クリーンアップを待機できないため、警告が出ます。

検証済みの例

cargo run --manifest-path examples/mcp_manager/Cargo.toml

この例は、実際の Rust MCP 子プロセスを起動し、ツールを検出して呼び出し、2 つ目のサーバーを追加して有効化し、それを更新して、レジストリを保存し、無効化して削除し、すべてのセッションを閉じます。モデル、API キー、パッケージのダウンロード、またはネットワークアクセスは必要ありません。