Publier un serveur MCP

ADK-Rust consomme des serveurs MCP via McpToolset, mais il n’enveloppe pas chaque SDK API côté serveur. Les auteurs de serveurs utilisent le SDK officiel rmcp réexporté par adk_tool::mcp::rmcp afin que les types de protocole client et serveur restent alignés.

Pour un crate de serveur autonome, dépendre directement de la même version de rmcp est également approprié :

[dependencies]
rmcp = { version = "2.2", features = ["transport-io", "schemars"] }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

Serveur stdio minimal

use rmcp::{
    ServerHandler,
    handler::server::{router::tool::ToolRouter, wrapper::Parameters},
    model::{ServerCapabilities, ServerInfo},
    schemars, tool, tool_handler, tool_router,
};
use serde::Deserialize;

#[derive(Debug, Deserialize, schemars::JsonSchema)]
struct LookupInput {
    order_id: String,
}

#[derive(Debug, Clone)]
struct OrderServer {
    tool_router: ToolRouter<Self>,
}

#[tool_router]
impl OrderServer {
    #[tool(description = "Read one order by its public order ID")]
    async fn read_order(
        &self,
        Parameters(input): Parameters<LookupInput>,
    ) -> String {
        format!("Order {} is ready for investigation", input.order_id)
    }
}

#[tool_handler]
impl ServerHandler for OrderServer {
    fn get_info(&self) -> ServerInfo {
        ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
            .with_instructions("Read-only order investigation tools")
    }
}

let service = rmcp::ServiceExt::serve(
    OrderServer { tool_router: OrderServer::tool_router() },
    rmcp::transport::io::stdio(),
).await?;
service.waiting().await?;

Le fixture examples/mcp_manager déterministe utilise cette structure de serveur et est compilé et exécuté par les contrôles de vérification MCP.

Publier des capacités honnêtes

N’annoncez une capacité que lorsque le gestionnaire l’implémente. Un client utilise la réponse d’initialisation pour déterminer s’il peut appeler des ressources, des invites, la complétion, l’élicitation, des abonnements ou des tâches.

Pour les outils capables de gérer des tâches, déclarez la prise en charge des tâches de l’outil et annoncez tasks.requests.tools.call. Le client peut refuser un outil nécessitant des tâches lorsque le serveur n’a pas négocié la prise en charge des tâches.

Les outils sont des frontières de sécurité

Les descriptions et le schéma JSON aident le modèle à formuler un appel ; ils ne constituent ni une validation des entrées ni une autorisation. Un serveur doit :

  • valider chaque entrée indépendamment du modèle ;
  • résoudre l’identité et le périmètre du locataire à la frontière du serveur ;
  • autoriser l’action et la ressource spécifiques ;
  • séparer les opérations de lecture des écritures ayant des conséquences ;
  • éviter de renvoyer des secrets ou des données non bornées ;
  • rendre explicites les nouvelles tentatives et l’idempotence pour les effets de bord ; et
  • consigner suffisamment d’éléments probants pour expliquer le résultat.

Choisir un transport

Utilisez stdio lorsque le client possède le processus enfant local. Utilisez Streamable HTTP lorsqu’il s’agit d’un service déployé indépendamment. Les déploiements distants nécessitent également une authentification, des limites de requêtes, une stratégie de session, de l’observabilité et une sonde de santé au niveau de l’application.

Consultez la documentation officielle de rmcp pour les routeurs de serveur, les ressources, les invites, les gestionnaires personnalisés, les transports, l’autorisation et l’extension APIs.