MCP 工具
Model Context Protocol (MCP) 是一种开放标准,它使 LLM 能够与外部应用程序、数据源和工具进行通信。ADK-Rust 通过 McpToolset 提供全面的 MCP 支持,允许您连接到任何符合 MCP 标准的服务器并将其工具暴露给您的 Agent。
概述
MCP 遵循客户端-服务器架构:
- MCP Servers 暴露工具、资源和提示
- MCP Clients (如 ADK agents) 连接到服务器并使用其能力
MCP 集成的优势:
- 通用连接性 - 连接到任何符合 MCP 标准的服务器
- 自动发现 - 工具从服务器动态发现
- 语言无关 - 使用任何语言编写的工具
- 不断增长的生态系统 - 访问数千个现有 MCP 服务器
前提条件
MCP servers 通常以 npm packages 形式分发。您需要:
- 已安装 Node.js 和 npm
- 一个 LLM API key (Gemini, OpenAI 等)
快速入门
连接到 MCP server 并使用其工具:
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. Start MCP server and connect
let mut cmd = Command::new("npx");
cmd.arg("-y").arg("@modelcontextprotocol/server-everything");
let client = ().serve(TokioChildProcess::new(cmd)?).await?;
// 2. Create toolset from the client
let toolset = McpToolset::new(client)
.with_tools(&["echo", "add"]); // Only expose these tools
// 3. Get cancellation token for cleanup
let cancel_token = toolset.cancellation_token().await;
// 4. Discover tools and add to 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("You have MCP tools. Use 'echo' to repeat messages, 'add' to sum numbers.");
for tool in tools {
builder = builder.tool(tool);
}
let agent = builder.build()?;
// 5. Run interactive console
adk_cli::console::run_console(
Arc::new(agent),
"mcp_demo".to_string(),
"user".to_string(),
).await?;
// 6. Cleanup: shutdown MCP server
cancel_token.cancel();
Ok(())
}
// Minimal context for tool discovery
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 server:
let toolset = McpToolset::new(client);
let cancel_token = toolset.cancellation_token().await;
// ... 使用 toolset ...
// 退出前,关闭 MCP server
cancel_token.cancel();
这可以防止 EPIPE 错误并确保进程干净终止。
连接到 MCP Servers
本地服务器 (Stdio)
通过标准输入/输出连接到本地 MCP server:
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 server:
use rmcp::{ServiceExt, transport::SseClient};
let client = ().serve(
SseClient::new("http://localhost:8080/sse")?
).await?;
工具发现
McpToolset 会自动从连接的 server 发现工具:
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 server 的名称和描述
- 包含用于 LLM 准确性的参数 schema
- 被调用时通过 MCP protocol 执行
将工具添加到 Agent
有两种模式可以将 MCP 工具添加到 agent:
模式 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 Servers
以下是一些您可以集成的常用 MCP server:
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 Server
npx -y @modelcontextprotocol/server-github
工具: search_repositories, get_file_contents, create_issue
Slack Server
npx -y @modelcontextprotocol/server-slack
工具: send_message, list_channels, search_messages
Memory Server
npx -y @modelcontextprotocol/server-memory
工具: store, retrieve, search
在 MCP Server Registry 查找更多 server。
错误处理
处理 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);
}
}
常见错误:
- 连接失败 - 服务器未运行或地址错误
- 工具执行失败 - MCP 服务器返回错误
- 无效参数 - 工具接收到不正确的参数
最佳实践
- 过滤工具 - 仅暴露 Agent 所需的工具,以减少混淆
- 使用取消令牌 - 在退出前务必调用
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 documentation。
相关
- 函数工具 - 在 Rust 中创建自定义工具
- 内置工具 - ADK 附带的预构建工具
- LlmAgent - 向 Agent 添加工具
- rmcp SDK - 官方 Rust MCP SDK
- MCP Specification - 协议文档
上一页:← UI 工具 | 下一页:Sessions →