动态 MCP 服务端管理
McpServerManager 持有本地 MCP 服务端子进程的运行时注册表。
当工作区、租户、管理员选择或部署配置发生变化时使用它。
它不是远程 HTTP 服务的连接池。请使用 McpHttpClientBuilder 构建这些服务,并将其生命周期保留在拥有远程配置的应用中。
生命周期
监视器会检查 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_handlers、list_resources、read_resource、list_prompts、get_prompt 和 subscribe_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 密钥、包下载或网络访问。