MCP 도구
Model Context Protocol (MCP)은 LLM이 외부 애플리케이션, 데이터 소스 및 도구와 통신할 수 있도록 하는 개방형 표준입니다. adk-rust는 McpToolset을 통해 완벽한 MCP 지원을 제공하여 모든 MCP 호환 서버에 연결하고 해당 도구를 Agent에 노출할 수 있습니다.
개요
MCP는 클라이언트-서버 아키텍처를 따릅니다:
- MCP 서버는 도구, 리소스 및 프롬프트를 노출합니다.
- MCP 클라이언트 (adk Agent와 같은)는 서버에 연결하여 기능을 사용합니다.
MCP 통합의 이점:
- 범용 연결성 - 모든 MCP 호환 서버에 연결합니다.
- 자동 검색 - 도구가 서버에서 동적으로 검색됩니다.
- 언어 독립적 - 모든 언어로 작성된 도구를 사용합니다.
- 성장하는 생태계 - 수천 개의 기존 MCP 서버에 액세스합니다.
전제 조건
MCP 서버는 일반적으로 npm 패키지로 배포됩니다. 다음이 필요합니다:
- Node.js 및 npm 설치
- LLM API 키 (GeminiModel, OpenAIClient 등)
빠른 시작
MCP 서버에 연결하고 해당 도구를 사용합니다:
use adk_agent::LlmAgentBuilder;
use adk_core::{Content, Part, ReadonlyContext, Toolset};
use adk_model::GeminiModel;
use adk_tool::McpToolset;
use rmcp::{ServiceExt, transport::TokioChildProcess};
use tokio::process::Command;
use std::sync::Arc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.0-flash")?);
// 1. MCP 서버 시작 및 연결
let mut cmd = Command::new("npx");
cmd.arg("-y").arg("@modelcontextprotocol/server-everything");
let client = ().serve(TokioChildProcess::new(cmd)?).await?;
// 2. 클라이언트에서 Toolset 생성
let toolset = McpToolset::new(client)
.with_tools(&["echo", "add"]); // 이 도구만 노출
// 3. 정리를 위한 취소 토큰 가져오기
let cancel_token = toolset.cancellation_token().await;
// 4. 도구 검색 및 Agent에 추가
let ctx: Arc<dyn ReadonlyContext> = Arc::new(SimpleContext);
let tools = toolset.tools(ctx).await?;
let mut builder = LlmAgentBuilder::new("mcp_agent")
.model(model)
.instruction("MCP 도구를 사용할 수 있습니다. 'echo'를 사용하여 메시지를 반복하고, 'add'를 사용하여 숫자를 더합니다.");
for tool in tools {
builder = builder.tool(tool);
}
let agent = builder.build()?;
// 5. 대화형 콘솔 실행
adk_cli::console::run_console(
Arc::new(agent),
"mcp_demo".to_string(),
"user".to_string(),
).await?;
// 6. 정리: MCP 서버 종료
cancel_token.cancel();
Ok(())
}
// 도구 검색을 위한 최소한의 Context
struct SimpleContext;
#[async_trait::async_trait]
impl ReadonlyContext for SimpleContext {
fn invocation_id(&self) -> &str { "init" }
fn agent_name(&self) -> &str { "init" }
fn user_id(&self) -> &str { "user" }
fn app_name(&self) -> &str { "mcp" }
fn session_id(&self) -> &str { "init" }
fn branch(&self) -> &str { "main" }
fn user_content(&self) -> &Content {
static CONTENT: std::sync::OnceLock<Content> = std::sync::OnceLock::new();
CONTENT.get_or_init(|| Content::new("user").with_text("init"))
}
}
다음으로 실행:
GOOGLE_API_KEY=your_key cargo run --bin basic
McpToolset API
Toolset 생성하기
use adk_tool::McpToolset;
// 기본 생성
let toolset = McpToolset::new(client);
// 사용자 정의 이름으로
let toolset = McpToolset::new(client)
.with_name("filesystem-tools");
도구 필터링
노출할 도구를 필터링합니다:
// 술어 함수로 필터링
let toolset = McpToolset::new(client)
.with_filter(|name| {
matches!(name, "read_file" | "write_file" | "list_directory")
});
// 정확한 이름으로 필터링 (편의 메서드)
let toolset = McpToolset::new(client)
.with_tools(&["echo", "add", "get_time"]);
취소 토큰을 사용한 정리
MCP 서버를 깔끔하게 종료하려면 항상 취소 토큰을 얻어야 합니다:
let toolset = McpToolset::new(client);
let cancel_token = toolset.cancellation_token().await;
// ... toolset 사용 ...
// 종료하기 전에 MCP 서버 종료
cancel_token.cancel();
이는 EPIPE 오류를 방지하고 깔끔한 프로세스 종료를 보장합니다.
MCP 서버 연결
로컬 서버 (Stdio)
표준 입출력을 통해 로컬 MCP 서버에 연결합니다:
use rmcp::{ServiceExt, transport::TokioChildProcess};
use tokio::process::Command;
// NPM 패키지 서버
let mut cmd = Command::new("npx");
cmd.arg("-y")
.arg("@modelcontextprotocol/server-filesystem")
.arg("/path/to/allowed/directory");
let client = ().serve(TokioChildProcess::new(cmd)?).await?;
// 로컬 바이너리 서버
let mut cmd = Command::new("./my-mcp-server");
cmd.arg("--config").arg("config.json");
let client = ().serve(TokioChildProcess::new(cmd)?).await?;
원격 서버 (SSE)
Server-Sent Events를 통해 원격 MCP 서버에 연결합니다:
use rmcp::{ServiceExt, transport::SseClient};
let client = ().serve(
SseClient::new("http://localhost:8080/sse")?
).await?;
도구 검색
McpToolset는 연결된 서버에서 도구를 자동으로 검색합니다:
use adk_core::{ReadonlyContext, Toolset};
// 검색된 도구 가져오기
let tools = toolset.tools(ctx).await?;
println!("Discovered {} tools:", tools.len());
for tool in &tools {
println!(" - {}: {}", tool.name(), tool.description());
}
각 검색된 도구는 다음을 가집니다:
- MCP 서버로부터 이름과 설명을 가집니다
- LLM 정확도를 위한 파라미터 스키마를 포함합니다
- 호출 시 MCP 프로토콜을 통해 실행됩니다
Agent에 도구 추가하기
Agent에 MCP 도구를 추가하는 두 가지 패턴이 있습니다:
패턴 1: Toolset으로 추가
let toolset = McpToolset::new(client);
let agent = LlmAgentBuilder::new("agent")
.model(model)
.toolset(Arc::new(toolset))
.build()?;
패턴 2: 개별 도구 추가
이는 어떤 도구를 추가할지에 대한 더 많은 제어권을 제공합니다:
let toolset = McpToolset::new(client)
.with_tools(&["echo", "add"]);
let tools = toolset.tools(ctx).await?;
let mut builder = LlmAgentBuilder::new("agent")
.model(model);
for tool in tools {
builder = builder.tool(tool);
}
let agent = builder.build()?;
인기 있는 MCP 서버
다음은 통합할 수 있는 일반적으로 사용되는 MCP 서버입니다:
Everything Server (테스트용)
npx -y @modelcontextprotocol/server-everything
도구: echo, add, longRunningOperation, sampleLLM, getAlerts, printEnv
Filesystem Server
npx -y @modelcontextprotocol/server-filesystem /path/to/directory
도구: read_file, write_file, list_directory, search_files
GitHub 서버
npx -y @modelcontextprotocol/server-github
도구: search_repositories, get_file_contents, create_issue
Slack 서버
npx -y @modelcontextprotocol/server-slack
도구: send_message, list_channels, search_messages
Memory 서버
npx -y @modelcontextprotocol/server-memory
도구: store, retrieve, search
MCP Server Registry에서 더 많은 서버를 찾아보세요.
오류 처리
MCP 연결 및 실행 오류를 처리합니다:
use adk_core::AdkError;
match toolset.tools(ctx).await {
Ok(tools) => {
println!("Discovered {} tools", tools.len());
}
Err(AdkError::Tool(msg)) => {
eprintln!("MCP error: {}", msg);
}
Err(e) => {
eprintln!("Other error: {}", e);
}
}
일반적인 오류:
- 연결 실패 - 서버가 실행 중이 아니거나 주소가 잘못됨
- Tool 실행 실패 - MCP 서버에서 오류를 반환함
- 잘못된 매개변수 - Tool이 잘못된 인수를 받음
모범 사례
- tools 필터링 - Agent의 혼동을 줄이기 위해 필요한 tools만 노출합니다.
- cancellation tokens 사용 - 정리 작업을 위해 종료하기 전에 항상
cancel()을 호출합니다. - 오류 처리 - MCP 서버가 실패할 수 있으므로, 적절한 오류 처리를 구현합니다.
- 로컬 서버 사용 - 개발용으로는 원격보다 stdio transport가 더 간단합니다.
- 서버 상태 확인 - toolset을 생성하기 전에 MCP 서버가 실행 중인지 확인합니다.
전체 예시
적절한 정리 작업을 포함한 전체 작동 예시는 다음과 같습니다:
use adk_agent::LlmAgentBuilder;
use adk_core::{Content, Part, ReadonlyContext, Toolset};
use adk_model::GeminiModel;
use adk_tool::McpToolset;
use rmcp::{ServiceExt, transport::TokioChildProcess};
use std::sync::Arc;
use tokio::process::Command;
struct SimpleContext;
#[async_trait::async_trait]
impl ReadonlyContext for SimpleContext {
fn invocation_id(&self) -> &str { "init" }
fn agent_name(&self) -> &str { "init" }
fn user_id(&self) -> &str { "user" }
fn app_name(&self) -> &str { "mcp" }
fn session_id(&self) -> &str { "init" }
fn branch(&self) -> &str { "main" }
fn user_content(&self) -> &Content {
static CONTENT: std::sync::OnceLock<Content> = std::sync::OnceLock::new();
CONTENT.get_or_init(|| Content::new("user").with_text("init"))
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.0-flash")?);
println!("Starting MCP server...");
let mut cmd = Command::new("npx");
cmd.arg("-y").arg("@modelcontextprotocol/server-everything");
let client = ().serve(TokioChildProcess::new(cmd)?).await?;
println!("MCP server connected!");
// Create filtered toolset
let toolset = McpToolset::new(client)
.with_name("everything-tools")
.with_filter(|name| matches!(name, "echo" | "add" | "printEnv"));
// Get cancellation token for cleanup
let cancel_token = toolset.cancellation_token().await;
// Discover tools
let ctx = Arc::new(SimpleContext) as Arc<dyn ReadonlyContext>;
let tools = toolset.tools(ctx).await?;
println!("Discovered {} tools:", tools.len());
for tool in &tools {
println!(" - {}: {}", tool.name(), tool.description());
}
// Build agent with tools
let mut builder = LlmAgentBuilder::new("mcp_demo")
.model(model)
.instruction(
"You have access to MCP tools:\n\
- echo: Repeat a message back\n\
- add: Add two numbers (a + b)\n\
- printEnv: Print environment variables"
);
for tool in tools {
builder = builder.tool(tool);
}
let agent = builder.build()?;
// Run interactive console
let result = adk_cli::console::run_console(
Arc::new(agent),
"mcp_demo".to_string(),
"user".to_string(),
).await;
// Cleanup
println!("\nShutting down MCP server...");
cancel_token.cancel();
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
result?;
Ok(())
}
고급: 커스텀 MCP 서버
rmcp SDK를 사용하여 Rust에서 자신만의 MCP 서버를 생성할 수 있습니다:
use rmcp::{tool, tool_router, handler::server::tool::ToolRouter, model::*};
#[derive(Clone)]
pub struct MyServer {
tool_router: ToolRouter<Self>,
}
#[tool_router]
impl MyServer {
fn new() -> Self {
Self { tool_router: Self::tool_router() }
}
#[tool(description = "Add two numbers")]
async fn add(&self, a: i32, b: i32) -> Result<CallToolResult, ErrorData> {
Ok(CallToolResult::success(vec![Content::text((a + b).to_string())]))
}
#[tool(description = "Multiply two numbers")]
async fn multiply(&self, a: i32, b: i32) -> Result<CallToolResult, ErrorData> {
Ok(CallToolResult::success(vec![Content::text((a * b).to_string())]))
}
}
완전한 서버 구현 세부 정보는 rmcp 문서를 참조하십시오.
관련
- Function Tools - Rust에서 커스텀 도구 생성
- 내장 도구 - ADK에 포함된 사전 구축 도구
- LlmAgent - Agent에 도구 추가
- rmcp SDK - 공식 Rust MCP SDK
- MCP Specification - 프로토콜 문서
이전: ← UI 도구 | 다음: Sessions →