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

Rendering architecture…

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.