동적 MCP 서버 관리
McpServerManager는 로컬 MCP 서버 자식 프로세스의 런타임 레지스트리를 소유합니다.
워크스페이스, 테넌트, 관리자 선택, 또는 배포 설정에 따라 통합 구성이 바뀔 때 사용하세요.
이는 원격 HTTP 서비스용 연결 풀은 아닙니다. 그런 것은 McpHttpClientBuilder으로 구성하고,
원격 구성을 소유한 애플리케이션에서 해당 수명을 관리하세요.
수명 주기
모니터는 MCP 연결이 닫혔는지 확인합니다. 충돌하거나 시작에 실패한 경우는 설정된
RestartPolicy에 남은 시도가 있을 때만 재시도합니다.
구성
{
"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
}
}
}
}
서버 ID는 ASCII 문자, 숫자, 하이픈, 밑줄을 허용합니다. 충돌 접두사가 붙은 도구 이름의 일부가 되므로 안정적인 ID를 사용하세요.
autoApprove는 구성 호환성을 위해 읽고 씁니다. 이 필드만으로 관리자가 승인을 부여하지는 않습니다.
도구 시작 및 집계
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()?;
자식이 MCP 핸드셰이크를 완료하는 동안 레지스트리 변경은 직렬화됩니다.
start_all는 활성화된 각 서버마다 독립적인 결과를 반환하지만, 현재 시작은 병렬 핸드셰이크 경로가 아닙니다.
런타임에서 레지스트리 변경
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?;
실행 중인 서버를 업데이트하면 이를 중지하고 대체 서버를 시작합니다. 대체가 실패하면, 관리자는 대체 오류를 반환하기 전에 이전 정의를 복구하고 다시 시작합니다.
save_json_file는 대상 디렉터리에 임시 파일을 쓴 다음, 이를 대상 파일로 이름을 바꿉니다.
리소스, 프롬프트, 알림
관리되는 서버는 도구 외에도 리소스와 프롬프트를 게시할 수 있습니다. 관리자는 각 서버의 리소스와 프롬프트 표면을 서버 ID별로 노출하고,
모든 관리 연결에서 공유되는 핸들러에 resources/updated / resources/list_changed 알림을 전달합니다.
핸들러는 한 번만 등록하세요. 수동 및 자동 재시작 사이에도 유지됩니다:
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?;
그다음 서버별로 읽고 구독하세요:
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?;
각 *_server_* 메서드는 ID로 실행 중인 서버 하나를 대상으로 하며, 서버를 알 수 없거나 현재 실행 중이 아니면
AdkError::Tool를 반환합니다.
단일 연결의 경우(관리자 레지스트리 대신) 동일한 표면을 McpToolset에서 McpToolset::with_handlers,
list_resources, read_resource, list_prompts, get_prompt, 그리고
subscribe_resource를 통해 직접 사용할 수 있습니다. 실행 가능한 에이전틱 예제를 참조하세요:
cargo run --manifest-path examples/mcp_resources/Cargo.toml --bin resources-client
도구 이름 충돌
두 개의 실행 중인 서버가 search를 게시하면, 집계된 이름은 다음과 같습니다:
crm__search
knowledge__search
한 서버만 게시한 도구 이름은 변경되지 않습니다.
종료 동작
서버를 중지하면 해당 MCP 세션이 취소되고, 구성된 유예 기간 동안 연결이 닫히기를 기다린 다음 전송 계층을 제거합니다. 자식 프로세스는 별도로 보관된 프로세스 핸들보다 TokioChildProcess 전송 계층이 소유합니다.
관리자를 제거하기 전에 shutdown()를 호출하세요. 실행 중인 서버가 있는 상태에서 관리자를 제거하면
Drop가 비동기 정리를 기다릴 수 없으므로 경고가 발생합니다.
검증된 예제
cargo run --manifest-path examples/mcp_manager/Cargo.toml
이 예제는 실제 Rust MCP 자식 프로세스를 시작하고, 도구를 찾고 호출하며, 두 번째 서버를 추가하고 활성화한 뒤, 이를 업데이트하고 레지스트리를 저장하며, 비활성화하고 제거한 다음, 모든 세션을 닫습니다. 모델, API 키, 패키지 다운로드, 또는 네트워크 액세스가 필요하지 않습니다.