Gestion dynamique du serveur MCP
McpServerManager possĂšde un registre dâexĂ©cution des processus enfants de serveur MCP locaux.
Utilisez-le lorsque les intĂ©grations changent selon lâespace de travail, le locataire, la sĂ©lection de lâadministrateur
ou la configuration de déploiement.
Ce nâest pas le pool de connexions pour les services HTTP distants. Construisez ceux-ci avec
McpHttpClientBuilder et conservez leur cycle de vie dans lâapplication qui possĂšde la
configuration distante.
Cycle de vie
Le moniteur vérifie si la connexion MCP a été fermée. Il réessaie les démarrages plantés ou
échoués uniquement tant que le RestartPolicy configuré a encore des tentatives restantes.
Réglages du gestionnaire
{
"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
}
}
}
}
Les identifiants de serveur acceptent des lettres ASCII, des chiffres, des tirets et des underscores. Utilisez un ID stable, car il devient une partie dâun nom dâoutil prĂ©fixĂ© par collision.
autoApprove est lu et écrit pour la compatibilité de configuration. Le gestionnaire
nâaccorde pas dâapprobation Ă partir de ce champ.
Démarrer et agréger les outils
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()?;
Les mutations du registre sont sĂ©rialisĂ©es pendant quâun enfant termine sa poignĂ©e de main MCP.
start_all renvoie un résultat indépendant pour chaque serveur activé, mais le démarrage
nâest pas actuellement un chemin de poignĂ©e de main parallĂšle.
Modifier le registre Ă lâexĂ©cution
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?;
La mise Ă jour dâun serveur en cours dâexĂ©cution lâarrĂȘte et dĂ©marre le remplacement. Si le remplacement Ă©choue, le gestionnaire restaure et redĂ©marre la dĂ©finition prĂ©cĂ©dente avant de renvoyer lâerreur du remplacement.
save_json_file écrit un fichier temporaire dans le répertoire de destination puis
le renomme par-dessus la destination.
Ressources, invites et notifications
Un serveur géré peut publier des ressources et des invites en plus des outils. Le
gestionnaire expose la surface des ressources et des invites de chaque serveur par ID de serveur, et
transmet les notifications resources/updated / resources/list_changed Ă un
gestionnaire partagé entre toutes les connexions gérées.
Enregistrez le gestionnaire une seule fois ; il est conservé entre les redémarrages manuels et automatiques :
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?;
Puis lisez et abonnez-vous par serveur :
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?;
Chaque mĂ©thode *_server_* cible un serveur en cours dâexĂ©cution par ID et renvoie
AdkError::Tool si le serveur est inconnu ou nâest pas actuellement en cours dâexĂ©cution.
Pour une seule connexion (plutĂŽt que le registre du gestionnaire), la mĂȘme surface est
disponible directement sur McpToolset via McpToolset::with_handlers,
list_resources, read_resource, list_prompts, get_prompt, et
subscribe_resource. Voir lâexemple agentique exĂ©cutable :
cargo run --manifest-path examples/mcp_resources/Cargo.toml --bin resources-client
Conflits de noms dâoutils
Si deux serveurs en cours dâexĂ©cution publient search, les noms agrĂ©gĂ©s deviennent :
crm__search
knowledge__search
Un nom dâoutil publiĂ© par un seul serveur reste inchangĂ©.
Comportement Ă lâarrĂȘt
LâarrĂȘt dâun serveur annule sa session MCP, attend jusquâĂ la pĂ©riode de grĂące configurĂ©e
pour que la connexion se ferme, puis supprime le transport. Le processus enfant
est détenu par le transport TokioChildProcess plutÎt que par un handle de processus
conservé séparément.
Appelez shutdown() avant de supprimer le gestionnaire. Supprimer un gestionnaire avec des serveurs
en cours dâexĂ©cution Ă©met un avertissement car Drop ne peut pas attendre le nettoyage asynchrone.
Exemple vérifié
cargo run --manifest-path examples/mcp_manager/Cargo.toml
Lâexemple dĂ©marre un vrai processus enfant Rust MCP, dĂ©couvre et appelle un outil, ajoute et active un second serveur, le met Ă jour, enregistre le registre, dĂ©sactive et supprime celui-ci, et ferme toutes les sessions. Il ne nĂ©cessite aucun modĂšle, aucune clĂ© API, aucun tĂ©lĂ©chargement de paquet, ni accĂšs au rĂ©seau.