//! Tests for protocol version negotiation in the default ServerHandler::initialize impl. //! //! Handshake versions are echoed back; every other version falls back to one //! the server can serve over `initialize`. #![cfg(not(feature = "local"))] #![cfg(feature = "client")] use std::{ borrow::Cow, sync::{ Arc, atomic::{AtomicUsize, Ordering}, }, }; use rmcp::{ ClientHandler, ErrorData, RoleServer, ServerHandler, ServiceExt, model::{ ClientCapabilities, ErrorCode, Implementation, InitializeRequestParams, InitializeResult, ProtocolVersion, }, service::{ClientInitializeError, RequestContext}, }; #[derive(Debug, Clone, Default)] struct EchoServer; impl ServerHandler for EchoServer { fn get_info(&self) -> InitializeResult { InitializeResult::default() } } /// Every known version whose lifecycle still runs the `initialize` handshake. /// `2026-07-28` replaced the handshake with per-request metadata, so this is /// also the list a server that has not implemented that revision supports. const HANDSHAKE_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) -> InitializeResult { InitializeResult::default() } fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> { Cow::Borrowed(HANDSHAKE_VERSIONS) } } /// Supports only revisions that have no `initialize` handshake at all. #[derive(Debug, Clone, Default)] struct ModernOnlyServer; const MODERN_ONLY_VERSIONS: &[ProtocolVersion] = &[ProtocolVersion::V_2026_07_28]; impl ServerHandler for ModernOnlyServer { fn get_info(&self) -> InitializeResult { let mut info = InitializeResult::default(); info.protocol_version = ProtocolVersion::V_2026_07_28; info } fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> { Cow::Borrowed(MODERN_ONLY_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) -> InitializeResult { InitializeResult::default() } fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> { Cow::Borrowed(HANDSHAKE_VERSIONS) } async fn initialize( &self, _request: InitializeRequestParams, _context: RequestContext, ) -> Result { Ok(self.get_info()) } } #[derive(Debug, Clone)] struct VersionedClient { protocol_version: ProtocolVersion, } impl ClientHandler for VersionedClient { fn get_info(&self) -> InitializeRequestParams { let mut info = InitializeRequestParams::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( server: S, client_version: ProtocolVersion, ) -> ProtocolVersion { negotiate_with(server, client_version) .await .expect("client should connect") } async fn negotiate_with( server: S, client_version: ProtocolVersion, ) -> Result { let (server_transport, client_transport) = tokio::io::duplex(4096); tokio::spawn(async move { if let Ok(running) = server.serve(server_transport).await { let _ = running.waiting().await; } }); let client = VersionedClient { protocol_version: client_version, } .serve(client_transport) .await?; let version = client .peer_info() .expect("peer_info should be set") .protocol_version .clone(); client.cancel().await.expect("client should cancel"); Ok(version) } #[tokio::test] async fn handshake_version_echoed_back() { for version in HANDSHAKE_VERSIONS { let negotiated = negotiated_version(version.clone()).await; assert_eq!( negotiated, *version, "handshake version {version} should be echoed back" ); } } /// `initialize` disappeared in `2026-07-28`, so agreeing to it here would leave /// the peers speaking a revision that has no handshake at all. #[tokio::test] async fn handshake_never_agrees_to_a_version_that_dropped_it() { let negotiated = negotiated_version(ProtocolVersion::V_2026_07_28).await; assert_eq!( negotiated, ProtocolVersion::LATEST, "a version that dropped the handshake should fall back to the server's own" ); } #[tokio::test] async fn modern_only_server_rejects_the_handshake() { let error = negotiate_with(ModernOnlyServer, ProtocolVersion::V_2026_07_28) .await .expect_err("a server with no handshake version cannot answer initialize"); let ClientInitializeError::JsonRpcError(error) = error else { panic!("expected a JSON-RPC error, got {error:?}"); }; assert_eq!( error.code, ErrorCode::UNSUPPORTED_PROTOCOL_VERSION, "a server with no handshake version should reject initialize" ); assert_eq!( error.data, Some(serde_json::json!({ "requested": "2026-07-28", "supported": ["2026-07-28"], })), "the rejection should name the versions the server does support" ); } #[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 HANDSHAKE_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" ); } /// Overrides `initialize` to run a side effect, then delegates the version /// answer back to the SDK with [`ServerHandler::negotiate_initialize`]. #[derive(Debug, Clone, Default)] struct DelegatingServer { initializations: Arc, } impl ServerHandler for DelegatingServer { fn get_info(&self) -> InitializeResult { InitializeResult::default() } fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> { Cow::Borrowed(HANDSHAKE_VERSIONS) } async fn initialize( &self, request: InitializeRequestParams, context: RequestContext, ) -> Result { self.initializations.fetch_add(1, Ordering::Relaxed); context.peer.set_peer_info(request.clone()); self.negotiate_initialize(&request) } } fn initialize_params(protocol_version: ProtocolVersion) -> InitializeRequestParams { let mut params = InitializeRequestParams::new( ClientCapabilities::default(), Implementation::new("test-client", "0.0.0"), ); params.protocol_version = protocol_version; params } #[test] fn negotiate_initialize_echoes_a_supported_version() { let result = NarrowedServer .negotiate_initialize(&initialize_params(ProtocolVersion::V_2025_06_18)) .expect("a supported handshake version should negotiate"); assert_eq!(result.protocol_version, ProtocolVersion::V_2025_06_18); } #[test] fn negotiate_initialize_caps_at_supported_versions() { let result = NarrowedServer .negotiate_initialize(&initialize_params(ProtocolVersion::V_2026_07_28)) .expect("an unsupported version should fall back rather than fail"); assert_eq!(result.protocol_version, ProtocolVersion::V_2025_11_25); } #[test] fn negotiate_initialize_keeps_the_rest_of_get_info() { let server = NarrowedServer; let result = server .negotiate_initialize(&initialize_params(ProtocolVersion::V_2026_07_28)) .expect("an unsupported version should fall back rather than fail"); assert_eq!(result.capabilities, server.get_info().capabilities); } #[test] fn negotiate_initialize_rejects_when_no_handshake_version_is_supported() { let error = ModernOnlyServer .negotiate_initialize(&initialize_params(ProtocolVersion::V_2026_07_28)) .expect_err("a server with no handshake version cannot answer initialize"); assert_eq!(error.code, ErrorCode::UNSUPPORTED_PROTOCOL_VERSION); } #[tokio::test] async fn delegating_server_negotiates_like_the_default_initialize() { let negotiated = negotiated_version_with(DelegatingServer::default(), ProtocolVersion::V_2026_07_28).await; assert_eq!( negotiated, ProtocolVersion::V_2025_11_25, "an override that delegates should answer what the default initialize would" ); } #[tokio::test] async fn delegating_server_still_runs_its_own_side_effect() { let server = DelegatingServer::default(); let initializations = Arc::clone(&server.initializations); negotiated_version_with(server, ProtocolVersion::V_2025_06_18).await; assert_eq!(initializations.load(Ordering::Relaxed), 1); }