Outils MCP

Model Context Protocol (MCP) est un standard ouvert qui permet aux LLMs de communiquer avec des applications externes, des sources de données et des outils. ADK-Rust fournit un support MCP complet via le McpToolset, vous permettant de vous connecter à n'importe quel serveur compatible MCP et d'exposer ses outils à vos agents.

Aperçu

MCP suit une architecture client-serveur :

  • MCP Servers exposent des outils, des ressources et des invites
  • MCP Clients (comme les agents ADK) se connectent aux serveurs et utilisent leurs capacités

Avantages de l'intégration de MCP :

  • Connectivité universelle - Connectez-vous à n'importe quel serveur compatible MCP
  • Découverte automatique - Les outils sont découverts dynamiquement à partir du serveur
  • Indépendance linguistique - Utilisez des outils écrits dans n'importe quel langage
  • Écosystème en croissance - Accédez à des milliers de serveurs MCP existants

Prérequis

Les serveurs MCP sont généralement distribués sous forme de packages npm. Vous aurez besoin de :

  • Node.js et npm installés
  • Une LLM API key (Gemini, OpenAI, etc.)

Démarrage rapide

Connectez-vous à un serveur MCP et utilisez ses outils :

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. Démarrer le serveur MCP et se connecter
    let mut cmd = Command::new("npx");
    cmd.arg("-y").arg("@modelcontextprotocol/server-everything");

    let client = ().serve(TokioChildProcess::new(cmd)?).await?;

    // 2. Créer un toolset à partir du client
    let toolset = McpToolset::new(client)
        .with_tools(&["echo", "add"]);  // N'exposez que ces outils

    // 3. Obtenir le jeton d'annulation pour le nettoyage
    let cancel_token = toolset.cancellation_token().await;

    // 4. Découvrir les outils et les ajouter à l'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("Vous disposez d'outils MCP. Utilisez 'echo' pour répéter les messages, 'add' pour additionner les nombres.");

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

    let agent = builder.build()?;

    // 5. Exécuter la console interactive
    adk_cli::console::run_console(
        Arc::new(agent),
        "mcp_demo".to_string(),
        "user".to_string(),
    ).await?;

    // 6. Nettoyage : arrêter le serveur MCP
    cancel_token.cancel();

    Ok(())
}

// Contexte minimal pour la découverte d'outils
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"))
    }
}

Exécutez avec :

GOOGLE_API_KEY=your_key cargo run --bin basic

API McpToolset

Création d'un Toolset

use adk_tool::McpToolset;

// Création de base
let toolset = McpToolset::new(client);

// Avec un nom personnalisé
let toolset = McpToolset::new(client)
    .with_name("filesystem-tools");

Filtrage des Tool

Filtrez les Tool à exposer :

// Filtrer par fonction de prédicat
let toolset = McpToolset::new(client)
    .with_filter(|name| {
        matches!(name, "read_file" | "write_file" | "list_directory")
    });

// Filtrer par noms exacts (méthode de commodité)
let toolset = McpToolset::new(client)
    .with_tools(&["echo", "add", "get_time"]);

Nettoyage avec un Jeton d'Annulation

Obtenez toujours un jeton d'annulation pour arrêter proprement le serveur MCP :

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

// ... utiliser le toolset ...

// Avant de quitter, arrêter le serveur MCP
cancel_token.cancel();

Ceci prévient les erreurs EPIPE et assure une terminaison propre du processus.

Connexion aux Serveurs MCP

Serveurs Locaux (Stdio)

Connectez-vous à un serveur MCP local via l'entrée/sortie standard :

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

// Serveur de package 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?;

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

Serveurs Distants (SSE)

Connectez-vous à un serveur MCP distant via Server-Sent Events :

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

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

Découverte de Tool

Le McpToolset découvre automatiquement les Tool du serveur connecté :

use adk_core::{ReadonlyContext, Toolset};

// Obtenir les Tool découverts
let tools = toolset.tools(ctx).await?;

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

Chaque Tool découvert :

  • Possède son nom et sa description du serveur MCP
  • Inclut des schémas de paramètres pour la précision des LlmAgent
  • S'exécute via le protocole MCP lorsqu'il est appelé

Ajout de Tool à l'Agent

Il existe deux modèles pour ajouter des Tool MCP à un Agent :

Modèle 1 : Ajouter en tant que Toolset

let toolset = McpToolset::new(client);

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

Modèle 2 : Ajouter des Tool Individuels

Cela vous donne plus de contrôle sur les Tool ajoutés :

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

Serveurs MCP Populaires

Voici quelques serveurs MCP couramment utilisés que vous pouvez intégrer :

Everything Server (Tests)

npx -y @modelcontextprotocol/server-everything

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

Filesystem Server

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

Tools: read_file, write_file, list_directory, search_files

GitHub Server

npx -y @modelcontextprotocol/server-github

Tools: search_repositories, get_file_contents, create_issue

Slack Server

npx -y @modelcontextprotocol/server-slack

Tools: send_message, list_channels, search_messages

Memory Server

npx -y @modelcontextprotocol/server-memory

Tools: store, retrieve, search

Trouvez plus de serveurs dans le MCP Server Registry.

Gestion des erreurs

Gérez les erreurs de connexion et d'exécution du 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);
    }
}

Erreurs courantes :

  • Échec de la connexion - Le serveur ne fonctionne pas ou l'adresse est incorrecte
  • Échec de l'exécution de l'outil - Le serveur MCP a renvoyé une erreur
  • Paramètres invalides - L'outil a reçu des arguments incorrects

Bonnes pratiques

  1. Filtrer les outils - N'exposez que les outils dont l'Agent a besoin pour réduire la confusion
  2. Utiliser des jetons d'annulation - Appelez toujours cancel() avant de quitter pour le nettoyage
  3. Gérer les erreurs - Les serveurs MCP peuvent échouer ; implémentez une gestion appropriée des erreurs
  4. Utiliser des serveurs locaux - Pour le développement, le transport stdio est plus simple que le transport distant
  5. Vérifier le statut du serveur - Vérifiez que le serveur MCP est en cours d'exécution avant de créer un toolset

Exemple complet

Voici un exemple complet et fonctionnel avec un nettoyage approprié :

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!");

    // Créer un toolset filtré
    let toolset = McpToolset::new(client)
        .with_name("everything-tools")
        .with_filter(|name| matches!(name, "echo" | "add" | "printEnv"));

    // Obtenir le jeton d'annulation pour le nettoyage
    let cancel_token = toolset.cancellation_token().await;

    // Découvrir les outils
    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());
    }

    // Construire l'Agent avec les outils
    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()?;

    // Exécuter la console interactive
    let result = adk_cli::console::run_console(
        Arc::new(agent),
        "mcp_demo".to_string(),
        "user".to_string(),
    ).await;

    // Nettoyage
    println!("\nShutting down MCP server...");
    cancel_token.cancel();
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;

    result?;
    Ok(())
}

Avancé : Serveur MCP Personnalisé

Vous pouvez créer votre propre serveur MCP en Rust en utilisant le rmcp SDK :

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

Consultez la documentation rmcp pour les détails complets de l'implémentation du serveur.

Connexe


Précédent : ← UI Tools | Suivant : Sessions →