fix: align protocol version negotiation (#855)

* fix: align protocol version negotiation

* ci: relax semver-checks to allow minor changes
This commit is contained in:
Dale Seo 2026-05-18 16:54:08 -04:00 • committed by GitHub
commit d328751dc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -4,8 +4,11 @@ mod common;
use common::handlers::TestServer;
use rmcp::{
ServiceExt,
model::{ClientJsonRpcMessage, ServerJsonRpcMessage, ServerResult},
ServerHandler, ServiceExt,
model::{
ClientJsonRpcMessage, ProtocolVersion, ServerCapabilities, ServerInfo,
ServerJsonRpcMessage, ServerResult,
},
transport::{IntoTransport, Transport},
};
@ -220,6 +223,82 @@ async fn server_init_buffers_request_before_initialized() {
result.unwrap().cancel().await.unwrap();
}
fn init_request_with_version(v: &str) -> ClientJsonRpcMessage {
msg(&format!(
r#"{{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {{
"protocolVersion": "{v}",
"capabilities": {{}},
"clientInfo": {{ "name": "test-client", "version": "0.0.1" }}
}}
}}"#
))
}
async fn negotiate_version<H>(handler: H, client_version: &str) -> ProtocolVersion
where
H: ServerHandler + 'static,
{
let (server_transport, client_transport) = tokio::io::duplex(4096);
let _server = tokio::spawn(async move { handler.serve(server_transport).await });
let mut client = IntoTransport::<rmcp::RoleClient, _, _>::into_transport(client_transport);
client
.send(init_request_with_version(client_version))
.await
.unwrap();
let response = client.receive().await.unwrap();
let ServerJsonRpcMessage::Response(r) = response else {
panic!("expected initialize response, got {response:?}");
};
let ServerResult::InitializeResult(init) = r.result else {
panic!("expected InitializeResult");
};
init.protocol_version
}
#[tokio::test]
async fn server_echoes_client_protocol_version_when_known_old() {
let negotiated = negotiate_version(TestServer::new(), "2024-11-05").await;
assert_eq!(negotiated, ProtocolVersion::V_2024_11_05);
}
#[tokio::test]
async fn server_echoes_client_protocol_version_when_latest() {
let negotiated = negotiate_version(TestServer::new(), "2025-11-25").await;
assert_eq!(negotiated, ProtocolVersion::LATEST);
}
#[tokio::test]
async fn server_falls_back_when_client_protocol_version_unknown() {
let negotiated = negotiate_version(TestServer::new(), "2099-99-99").await;
assert_eq!(negotiated, ProtocolVersion::LATEST);
}
struct PinnedServer;
impl ServerHandler for PinnedServer {
fn get_info(&self) -> ServerInfo {
ServerInfo::new(ServerCapabilities::builder().build())
.with_protocol_version(ProtocolVersion::V_2025_06_18)
}
}
#[tokio::test]
async fn server_pinned_version_does_not_override_known_client_request() {
let negotiated = negotiate_version(PinnedServer, "2025-11-25").await;
assert_eq!(negotiated, ProtocolVersion::LATEST);
}
#[tokio::test]
async fn server_pinned_version_used_as_fallback_for_unknown_client_request() {
let negotiated = negotiate_version(PinnedServer, "2099-99-99").await;
assert_eq!(negotiated, ProtocolVersion::V_2025_06_18);
}
// Server buffers multiple requests before initialized and processes them in order.
#[tokio::test]
async fn server_init_buffers_multiple_requests_before_initialized() {