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
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.