动态 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 定位一个正在运行的服务端;如果服务端未知或当前未运行,则返回 AdkError::Tool

对于单个连接(而不是管理器注册表),同样的界面可通过 McpToolset 直接使用 McpToolset::with_handlerslist_resourcesread_resourcelist_promptsget_promptsubscribe_resource 获取。请参见可运行的 agentic 示例:

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

工具名称冲突

如果两个正在运行的服务端发布 search,聚合后的名称将变为:

crm__search
knowledge__search

仅由一个服务端发布的工具名称保持不变。

关闭行为

停止某个服务端会取消其 MCP 会话,最多等待配置的宽限期让连接关闭,然后释放传输层。子进程由 TokioChildProcess 传输层管理,而不是由单独保留的进程句柄管理。

在丢弃管理器之前调用 shutdown()。在有运行中服务端时丢弃管理器会发出警告,因为 Drop 无法等待异步清理完成。

已验证示例

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

该示例会启动一个真实的 Rust MCP 子进程,发现并调用一个工具,添加并启用第二个服务端,更新它,保存注册表,禁用并移除它,并关闭所有会话。它不需要模型、API 密钥、包下载或网络访问。