Gestión dinámica de servidores MCP
McpServerManager posee un registro en tiempo de ejecución de procesos hijo locales de servidor MCP.
Úsalo cuando las integraciones cambien según el espacio de trabajo, el inquilino, la selección del administrador
o la configuración de implementación.
No es el pool de conexiones para servicios HTTP remotos. Compáralos con
McpHttpClientBuilder y mantén su ciclo de vida en la aplicación que posee la
configuración remota.
Ciclo de vida
El monitor comprueba si la conexión MCP se ha cerrado. Solo reintenta inicios fallidos o
que se hayan bloqueado mientras el RestartPolicy configurado tenga intentos restantes.
Configuración
{
"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
}
}
}
}
Los ID de servidor aceptan letras, números, guiones y guiones bajos ASCII. Usa un ID estable porque pasa a formar parte de un nombre de herramienta con prefijo de colisión.
autoApprove se lee y se escribe por compatibilidad de configuración. El administrador
no concede aprobación a partir de este campo.
Iniciar y agregar herramientas
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()?;
Las mutaciones del registro se serializan mientras un hijo completa su
handshake MCP. start_all devuelve un resultado independiente para cada servidor habilitado, pero el inicio
actualmente no es una ruta de handshake en paralelo.
Cambiar el registro en tiempo de ejecución
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?;
Actualizar un servidor en ejecución lo detiene e inicia el reemplazo. Si el reemplazo falla, el administrador restaura y reinicia la definición anterior antes de devolver el error del reemplazo.
save_json_file escribe un archivo temporal en el directorio de destino y luego
lo renombra sobre el destino.
Recursos, prompts y notificaciones
Un servidor administrado puede publicar recursos y prompts además de herramientas. El
administrador expone la superficie de recursos y prompts de cada servidor mediante el ID del servidor, y
entrega notificaciones resources/updated / resources/list_changed a un
manejador compartido entre todas las conexiones administradas.
Registra el manejador una sola vez; se conserva entre reinicios manuales y automáticos:
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?;
Luego lee y suscríbete por servidor:
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?;
Cada método *_server_* apunta a un servidor en ejecución por ID y devuelve
AdkError::Tool si el servidor es desconocido o no se está ejecutando actualmente.
Para una sola conexión (en lugar del registro del administrador), la misma superficie está
disponible directamente en McpToolset mediante McpToolset::with_handlers,
list_resources, read_resource, list_prompts, get_prompt y
subscribe_resource. Consulta el ejemplo ejecutable agentic:
cargo run --manifest-path examples/mcp_resources/Cargo.toml --bin resources-client
Colisiones de nombres de herramientas
Si dos servidores en ejecución publican search, los nombres agregados pasan a ser:
crm__search
knowledge__search
Un nombre de herramienta publicado por un solo servidor no cambia.
Comportamiento al apagar
Detener un servidor cancela su sesión MCP, espera hasta el período de gracia configurado
para que la conexión se cierre, y luego libera el transporte. El proceso hijo
está controlado por el transporte TokioChildProcess en lugar de por un
manejador de proceso retenido por separado.
Llama a shutdown() antes de desechar el administrador. Desechar un administrador con servidores en ejecución emite una advertencia porque Drop no puede esperar la limpieza asíncrona.
Ejemplo verificado
cargo run --manifest-path examples/mcp_manager/Cargo.toml
El ejemplo inicia un proceso hijo real de Rust MCP, descubre y llama a una herramienta, añade y habilita un segundo servidor, lo actualiza, guarda el registro, lo deshabilita y lo elimina, y cierra todas las sesiones. No requiere modelo, clave API, descarga de paquetes ni acceso a red.