rmcp 3.1.0

Rust SDK for Model Context Protocol
Documentation
//! Tests for protocol version negotiation in the default ServerHandler::initialize impl.
//!
//! Known versions are echoed back; unknown versions fall back to LATEST.
#![cfg(not(feature = "local"))]
#![cfg(feature = "client")]

use std::borrow::Cow;

use rmcp::{
    ClientHandler, ErrorData, RoleServer, ServerHandler, ServiceExt,
    model::{ClientInfo, InitializeRequestParams, InitializeResult, ProtocolVersion, ServerInfo},
    service::RequestContext,
};

#[derive(Debug, Clone, Default)]
struct EchoServer;

impl ServerHandler for EchoServer {
    fn get_info(&self) -> ServerInfo {
        ServerInfo::default()
    }
}

/// Every known version except `2026-07-28`, standing in for a server that has
/// not implemented that revision.
const NARROWED_VERSIONS: &[ProtocolVersion] = &[
    ProtocolVersion::V_2024_11_05,
    ProtocolVersion::V_2025_03_26,
    ProtocolVersion::V_2025_06_18,
    ProtocolVersion::V_2025_11_25,
];

#[derive(Debug, Clone, Default)]
struct NarrowedServer;

impl ServerHandler for NarrowedServer {
    fn get_info(&self) -> ServerInfo {
        ServerInfo::default()
    }

    fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> {
        Cow::Borrowed(NARROWED_VERSIONS)
    }
}

/// Narrows the supported versions *and* overrides `initialize`, so the
/// handler's own answer never runs the default negotiation. The handshake layer
/// must still honor the narrowed list.
#[derive(Debug, Clone, Default)]
struct NarrowedOverridingServer;

impl ServerHandler for NarrowedOverridingServer {
    fn get_info(&self) -> ServerInfo {
        ServerInfo::default()
    }

    fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> {
        Cow::Borrowed(NARROWED_VERSIONS)
    }

    async fn initialize(
        &self,
        _request: InitializeRequestParams,
        _context: RequestContext<RoleServer>,
    ) -> Result<InitializeResult, ErrorData> {
        Ok(self.get_info())
    }
}

#[derive(Debug, Clone)]
struct VersionedClient {
    protocol_version: ProtocolVersion,
}

impl ClientHandler for VersionedClient {
    fn get_info(&self) -> ClientInfo {
        let mut info = ClientInfo::default();
        info.protocol_version = self.protocol_version.clone();
        info
    }
}

async fn negotiated_version(client_version: ProtocolVersion) -> ProtocolVersion {
    negotiated_version_with(EchoServer, client_version).await
}

async fn negotiated_version_with<S: ServerHandler>(
    server: S,
    client_version: ProtocolVersion,
) -> ProtocolVersion {
    let (server_transport, client_transport) = tokio::io::duplex(4096);

    tokio::spawn(async move {
        let _ = server
            .serve(server_transport)
            .await
            .expect("server should start")
            .waiting()
            .await;
    });

    let client = VersionedClient {
        protocol_version: client_version,
    }
    .serve(client_transport)
    .await
    .expect("client should connect");

    let version = client
        .peer_info()
        .expect("peer_info should be set")
        .protocol_version
        .clone();

    client.cancel().await.expect("client should cancel");
    version
}

#[tokio::test]
async fn known_version_echoed_back() {
    for version in ProtocolVersion::KNOWN_VERSIONS {
        let negotiated = negotiated_version(version.clone()).await;
        assert_eq!(
            negotiated, *version,
            "known version {version} should be echoed back"
        );
    }
}

#[tokio::test]
async fn unknown_version_falls_back_to_latest() {
    let unknown: ProtocolVersion = serde_json::from_str(r#""1999-01-01""#).unwrap();
    let negotiated = negotiated_version(unknown).await;
    assert_eq!(
        negotiated,
        ProtocolVersion::LATEST,
        "unknown version should fall back to LATEST"
    );
}

#[tokio::test]
async fn narrowed_server_still_echoes_versions_it_supports() {
    for version in NARROWED_VERSIONS {
        let negotiated = negotiated_version_with(NarrowedServer, version.clone()).await;
        assert_eq!(
            negotiated, *version,
            "supported version {version} should be echoed back"
        );
    }
}

#[tokio::test]
async fn narrowed_server_does_not_agree_to_version_it_excludes() {
    let negotiated = negotiated_version_with(NarrowedServer, ProtocolVersion::V_2026_07_28).await;
    assert_eq!(
        negotiated,
        ProtocolVersion::V_2025_11_25,
        "a version outside supported_protocol_versions should not be echoed back"
    );
}

#[tokio::test]
async fn narrowed_server_caps_even_when_it_overrides_initialize() {
    let negotiated =
        negotiated_version_with(NarrowedOverridingServer, ProtocolVersion::V_2026_07_28).await;
    assert_eq!(
        negotiated,
        ProtocolVersion::V_2025_11_25,
        "the handshake layer should not raise the version above what the server supports"
    );
}