Dynamische MCP-Serververwaltung
McpServerManager besitzt eine Laufzeit-Registrierung lokaler MCP-Server-Child-Prozesse.
Verwende sie, wenn sich Integrationen je nach Arbeitsbereich, Mandant, Administrationsauswahl
oder Bereitstellungskonfiguration ändern.
Sie ist nicht der Connection Pool für entfernte HTTP-Dienste. Erstelle diese mit
McpHttpClientBuilder und verwalte ihren Lebenszyklus in der Anwendung, die die
entfernte Konfiguration besitzt.
Lebenszyklus
Der Monitor prüft, ob die MCP-Verbindung geschlossen wurde. Er wiederholt abgestürzte oder
fehlgeschlagene Starts nur, solange die konfigurierte RestartPolicy noch Versuche übrig hat.
Konfiguration
{
"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
}
}
}
}
Server-IDs akzeptieren ASCII Buchstaben, Zahlen, Bindestriche und Unterstriche. Verwende eine stabile ID, weil sie Teil eines kollisionspräfixierten Tool-Namens wird.
autoApprove wird aus Kompatibilitätsgründen für die Konfiguration gelesen und geschrieben. Der Manager
gewährt aus diesem Feld keine Freigabe.
Tools starten und zusammenführen
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()?;
Registrierungsänderungen werden serialisiert, während ein Child seinen MCP-Handshake abschließt.
start_all gibt für jeden aktivierten Server ein unabhängiges Ergebnis zurück, aber der Start
ist derzeit kein Pfad mit parallelem Handshake.
Die Registrierung zur Laufzeit ändern
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?;
Das Aktualisieren eines laufenden Servers stoppt ihn und startet den Ersatz. Falls der Ersatz fehlschlägt, stellt der Manager die vorherige Definition wieder her und startet sie neu, bevor der Ersatzfehler zurückgegeben wird.
save_json_file schreibt eine temporäre Datei im Zielverzeichnis und benennt sie dann
über das Ziel um.
Ressourcen, Prompts und Benachrichtigungen
Ein verwalteter Server kann zusätzlich zu Tools auch Ressourcen und Prompts veröffentlichen. Der
Manager stellt für jede Server-ID die Ressourcen- und Prompt-Oberfläche jedes Servers bereit und
liefert resources/updated / resources/list_changed-Benachrichtigungen an einen
Handler, der über alle verwalteten Verbindungen hinweg gemeinsam genutzt wird.
Registriere den Handler einmal; er bleibt über manuelle und automatische Neustarts hinweg erhalten:
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?;
Dann pro Server lesen und abonnieren:
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?;
Jede *_server_*-Methode adressiert genau einen laufenden Server per ID und gibt
AdkError::Tool zurück, wenn der Server unbekannt ist oder derzeit nicht läuft.
Für eine einzelne Verbindung (statt der Manager-Registrierung) ist dieselbe Oberfläche
direkt auf McpToolset über McpToolset::with_handlers,
list_resources, read_resource, list_prompts, get_prompt und
subscribe_resource verfügbar. Siehe das ausführbare agentische Beispiel:
cargo run --manifest-path examples/mcp_resources/Cargo.toml --bin resources-client
Tool-Namenskonflikte
Wenn zwei laufende Server search veröffentlichen, werden die aggregierten Namen zu:
crm__search
knowledge__search
Ein Tool-Name, der nur von einem Server veröffentlicht wird, bleibt unverändert.
Verhalten beim Herunterfahren
Das Stoppen eines Servers bricht seine MCP-Sitzung ab, wartet bis zu dem konfigurierten Grace
Period darauf, dass die Verbindung geschlossen wird, und trennt dann den Transport. Der Child-Prozess
gehört dem TokioChildProcess-Transport und nicht einem separat aufbewahrten
Prozess-Handle.
Rufe shutdown() auf, bevor du den Manager entfernst. Das Entfernen eines Managers mit laufenden
Servern erzeugt eine Warnung, weil Drop die asynchrone Bereinigung
nicht abwarten kann.
Verifiziertes Beispiel
cargo run --manifest-path examples/mcp_manager/Cargo.toml
Das Beispiel startet einen echten Rust-MCP-Child-Prozess, entdeckt und ruft ein Tool auf, fügt einen zweiten Server hinzu und aktiviert ihn, aktualisiert ihn, speichert die Registrierung, deaktiviert und entfernt ihn und schließt alle Sitzungen. Es benötigt kein Modell, keinen API-Schlüssel, keinen Paket-Download und keinen Netzwerkzugriff.