Gerenciamento dinâmico do servidor MCP

McpServerManager mantém um registro em tempo de execução de processos filho locais do servidor MCP. Use-o quando as integrações mudarem por workspace, tenant, seleção do administrador ou configuração de implantação.

Ele não é o pool de conexões para serviços remotos HTTP. Construa esses com McpHttpClientBuilder e mantenha o ciclo de vida deles na aplicação que é dona da configuração remota.

Ciclo de vida

Rendering architecture…

O monitor verifica se a conexão MCP foi encerrada. Ele tenta novamente inicializações com falha ou que travaram somente enquanto o RestartPolicy configurado ainda tiver tentativas restantes.

Configuração

{
  "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
      }
    }
  }
}

Os IDs de servidor aceitam letras, números, hífens e sublinhados ASCII. Use um ID estável porque ele passa a fazer parte de um nome de ferramenta com prefixo de colisão.

autoApprove é lido e gravado por compatibilidade de configuração. O gerenciador não concede aprovação com base nesse campo.

Iniciar e agregar ferramentas

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()?;

As mutações do registro são serializadas enquanto um filho conclui seu handshake MCP. start_all retorna um resultado independente para cada servidor habilitado, mas a inicialização atualmente não é um caminho de handshake paralelo.

Alterar o registro em tempo de execução

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?;

Atualizar um servidor em execução o interrompe e inicia a substituição. Se a substituição falhar, o gerenciador restaura e reinicia a definição anterior antes de retornar o erro da substituição.

save_json_file grava um arquivo temporário no diretório de destino e então o renomeia sobre o destino.

Recursos, prompts e notificações

Um servidor gerenciado pode publicar recursos e prompts além de ferramentas. O gerenciador expõe a superfície de recursos e prompts de cada servidor pelo ID do servidor e entrega notificações resources/updated / resources/list_changed para um handler compartilhado em todas as conexões gerenciadas.

Registre o handler uma vez; ele é mantido entre reinicializações manuais e automáticas:

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?;

Em seguida, leia e assine 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_* direciona um único servidor em execução pelo ID e retorna AdkError::Tool se o servidor for desconhecido ou não estiver em execução no momento.

Para uma única conexão (em vez do registro do gerenciador), a mesma superfície está disponível diretamente em McpToolset por meio de McpToolset::with_handlers, list_resources, read_resource, list_prompts, get_prompt e subscribe_resource. Veja o exemplo agentic executável:

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

Colisões de nomes de ferramentas

Se dois servidores em execução publicarem search, os nomes agregados passam a ser:

crm__search
knowledge__search

Um nome de ferramenta publicado por apenas um servidor não é alterado.

Comportamento de encerramento

Parar um servidor cancela sua sessão MCP, aguarda até o período de tolerância configurado para a conexão ser encerrada e então descarta o transporte. O processo filho é controlado pelo transporte TokioChildProcess em vez de um handle de processo retido separadamente.

Chame shutdown() antes de descartar o gerenciador. Descarta um gerenciador com servidores em execução emite um aviso porque Drop não pode aguardar a limpeza assíncrona.

Exemplo verificado

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

O exemplo inicia um processo filho real de servidor Rust MCP, descobre e chama uma ferramenta, adiciona e habilita um segundo servidor, atualiza-o, salva o registro, desabilita e remove-o, e fecha todas as sessões. Ele não requer modelo, chave API, download de pacote ou acesso à rede.