Ferramentas MCP

Model Context Protocol (MCP) é um padrão aberto que permite que LLMs se comuniquem com aplicações externas, fontes de dados e Tools. adk-rust oferece suporte MCP completo através do McpToolset, permitindo que você se conecte a qualquer servidor compatível com MCP e exponha suas Tools aos seus Agents.

Visão Geral

MCP segue uma arquitetura cliente-servidor:

  • Servidores MCP expõem Tools, recursos e prompts
  • Clientes MCP (como Agents adk) conectam-se a servidores e usam suas capacidades

Benefícios da integração MCP:

  • Conectividade universal - Conecte-se a qualquer servidor compatível com MCP
  • Descoberta automática - As Tools são descobertas dinamicamente a partir do servidor
  • Independente de linguagem - Use Tools escritas em qualquer linguagem
  • Ecossistema em crescimento - Acesse milhares de servidores MCP existentes

Pré-requisitos

Servidores MCP são tipicamente distribuídos como pacotes npm. Você precisará de:

  • Node.js e npm instalados
  • Uma chave de API de LLM (GeminiModel, OpenAIClient, etc.)

Início Rápido

Conecte-se a um servidor MCP e use suas Tools:

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"]);  // Apenas exponha estas 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("Você tem Tools MCP. Use 'echo' para repetir mensagens, 'add' para somar números.");

    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(())
}

// Contexto mínimo para descoberta de Tools
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"))
    }
}

Run with:

GOOGLE_API_KEY=your_key cargo run --bin basic

API McpToolset

Criando um Toolset

use adk_tool::McpToolset;

// Basic creation
let toolset = McpToolset::new(client);

// With custom name
let toolset = McpToolset::new(client)
    .with_name("filesystem-tools");

Filtragem de Ferramentas

Filtra quais ferramentas expor:

// Filter by predicate function
let toolset = McpToolset::new(client)
    .with_filter(|name| {
        matches!(name, "read_file" | "write_file" | "list_directory")
    });

// Filter by exact names (convenience method)
let toolset = McpToolset::new(client)
    .with_tools(&["echo", "add", "get_time"]);

Limpeza com Cancellation Token

Sempre obtenha um cancellation token para desligar o servidor MCP de forma limpa:

let toolset = McpToolset::new(client);
let cancel_token = toolset.cancellation_token().await;

// ... use the toolset ...

// Before exiting, shutdown the MCP server
cancel_token.cancel();

Isso previne erros EPIPE e garante uma terminação limpa do processo.

Conectando-se a Servidores MCP

Servidores Locais (Stdio)

Conecte-se a um servidor MCP local via entrada/saída padrão:

use rmcp::{ServiceExt, transport::TokioChildProcess};
use tokio::process::Command;

// NPM package server
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?;

// Local binary server
let mut cmd = Command::new("./my-mcp-server");
cmd.arg("--config").arg("config.json");
let client = ().serve(TokioChildProcess::new(cmd)?).await?;

Servidores Remotos (SSE)

Conecte-se a um servidor MCP remoto via Server-Sent Events:

use rmcp::{ServiceExt, transport::SseClient};

let client = ().serve(
    SseClient::new("http://localhost:8080/sse")?
).await?;

Descoberta de Ferramentas

O McpToolset descobre automaticamente ferramentas do servidor conectado:

use adk_core::{ReadonlyContext, Toolset};

// Get discovered tools
let tools = toolset.tools(ctx).await?;

println!("Discovered {} tools:", tools.len());
for tool in &tools {
    println!("  - {}: {}", tool.name(), tool.description());
}

Cada ferramenta descoberta:

  • Tem seu nome e descrição do servidor MCP
  • Inclui esquemas de parâmetros para precisão de LLM
  • Executa via protocolo MCP quando chamada

Adicionando Ferramentas ao Agent

Existem dois padrões para adicionar ferramentas MCP a um agent:

Padrão 1: Adicionar como Toolset

let toolset = McpToolset::new(client);

let agent = LlmAgentBuilder::new("agent")
    .model(model)
    .toolset(Arc::new(toolset))
    .build()?;

Padrão 2: Adicionar Ferramentas Individuais

Isso lhe dá mais controle sobre quais ferramentas são adicionadas:

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()?;

Servidores MCP Populares

Aqui estão alguns servidores MCP comumente usados que você pode integrar:

Everything Server (Teste)

npx -y @modelcontextprotocol/server-everything

Ferramentas: echo, add, longRunningOperation, sampleLLM, getAlerts, printEnv

Servidor Filesystem

npx -y @modelcontextprotocol/server-filesystem /path/to/directory

Ferramentas: read_file, write_file, list_directory, search_files

Servidor GitHub

npx -y @modelcontextprotocol/server-github

Ferramentas: search_repositories, get_file_contents, create_issue

Servidor Slack

npx -y @modelcontextprotocol/server-slack

Ferramentas: send_message, list_channels, search_messages

Servidor Memory

npx -y @modelcontextprotocol/server-memory

Ferramentas: store, retrieve, search

Encontre mais servidores no MCP Server Registry.

Tratamento de Erros

Lida com erros de conexão e execução do 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);
    }
}

Erros comuns:

  • Falha na conexão - Servidor não está em execução ou endereço errado
  • Falha na execução da Tool - Servidor MCP retornou um erro
  • Parâmetros inválidos - A Tool recebeu argumentos incorretos

Melhores Práticas

  1. Filtrar Tools - Exponha apenas as Tools que o Agent precisa para reduzir a confusão
  2. Usar tokens de cancelamento - Sempre chame cancel() antes de sair para realizar a limpeza
  3. Lidar com erros - Servidores MCP podem falhar; implemente tratamento de erros apropriado
  4. Usar servidores locais - Para desenvolvimento, o transporte stdio é mais simples do que o remoto
  5. Verificar status do servidor - Verifique se o servidor MCP está em execução antes de criar o toolset

Exemplo Completo

Aqui está um exemplo completo e funcional com a limpeza adequada:

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!("Iniciando servidor MCP...");
    let mut cmd = Command::new("npx");
    cmd.arg("-y").arg("@modelcontextprotocol/server-everything");

    let client = ().serve(TokioChildProcess::new(cmd)?).await?;
    println!("Servidor MCP conectado!");

    // Criar toolset filtrado
    let toolset = McpToolset::new(client)
        .with_name("everything-tools")
        .with_filter(|name| matches!(name, "echo" | "add" | "printEnv"));

    // Obter token de cancelamento para limpeza
    let cancel_token = toolset.cancellation_token().await;

    // Descobrir tools
    let ctx = Arc::new(SimpleContext) as Arc<dyn ReadonlyContext>;
    let tools = toolset.tools(ctx).await?;

    println!("{} tools descobertas:", tools.len());
    for tool in &tools {
        println!("  - {}: {}", tool.name(), tool.description());
    }

    // Construir Agent com tools
    let mut builder = LlmAgentBuilder::new("mcp_demo")
        .model(model)
        .instruction(
            "Você tem acesso às tools do MCP:\n\
             - echo: Repete uma mensagem de volta\n\
             - add: Adiciona dois números (a + b)\n\
             - printEnv: Imprime variáveis de ambiente"
        );

    for tool in tools {
        builder = builder.tool(tool);
    }

    let agent = builder.build()?;

    // Executar console interativo
    let result = adk_cli::console::run_console(
        Arc::new(agent),
        "mcp_demo".to_string(),
        "user".to_string(),
    ).await;

    // Limpeza
    println!("\nDesligando o servidor MCP...");
    cancel_token.cancel();
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;

    result?;
    Ok(())
}

Avançado: Servidor MCP Personalizado

Você pode criar seu próprio servidor MCP em Rust usando o SDK rmcp:

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())]))
    }
}

Consulte a documentação do rmcp para obter detalhes completos da implementação do servidor.

Relacionado


Anterior: ← UI Tools | Próximo: Sessions →