Skip to content

Commit 2536a05

Browse files
authored
fix: update peer info on duplicate initialize (#862)
1 parent 2d3d187 commit 2536a05

3 files changed

Lines changed: 69 additions & 12 deletions

File tree

crates/rmcp/src/handler/server.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,7 @@ macro_rules! server_handler_methods {
184184
request: InitializeRequestParams,
185185
context: RequestContext<RoleServer>,
186186
) -> impl Future<Output = Result<InitializeResult, McpError>> + MaybeSendFuture + '_ {
187-
if context.peer.peer_info().is_none() {
188-
context.peer.set_peer_info(request);
189-
}
187+
context.peer.set_peer_info(request);
190188
std::future::ready(Ok(self.get_info()))
191189
}
192190
fn complete(

crates/rmcp/src/service.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ pub struct Peer<R: ServiceRole> {
384384
tx: mpsc::Sender<PeerSinkMessage<R>>,
385385
request_id_provider: Arc<dyn RequestIdProvider>,
386386
progress_token_provider: Arc<dyn ProgressTokenProvider>,
387-
info: Arc<tokio::sync::OnceCell<R::PeerInfo>>,
387+
info: Arc<std::sync::RwLock<Option<Arc<R::PeerInfo>>>>,
388388
}
389389

390390
impl<R: ServiceRole> std::fmt::Debug for Peer<R> {
@@ -423,7 +423,7 @@ impl<R: ServiceRole> Peer<R> {
423423
tx,
424424
request_id_provider,
425425
progress_token_provider: Arc::new(AtomicU32ProgressTokenProvider::default()),
426-
info: Arc::new(tokio::sync::OnceCell::new_with(peer_info)),
426+
info: Arc::new(std::sync::RwLock::new(peer_info.map(Arc::new))),
427427
},
428428
rx,
429429
)
@@ -484,16 +484,14 @@ impl<R: ServiceRole> Peer<R> {
484484
peer: self.clone(),
485485
})
486486
}
487-
pub fn peer_info(&self) -> Option<&R::PeerInfo> {
488-
self.info.get()
487+
/// Snapshot of the peer's handshake info.
488+
pub fn peer_info(&self) -> Option<Arc<R::PeerInfo>> {
489+
self.info.read().expect("peer info lock poisoned").clone()
489490
}
490491

492+
/// Stores the peer's handshake info, overwriting any previous value.
491493
pub fn set_peer_info(&self, info: R::PeerInfo) {
492-
if self.info.initialized() {
493-
tracing::warn!("trying to set peer info, which is already initialized");
494-
} else {
495-
let _ = self.info.set(info);
496-
}
494+
*self.info.write().expect("peer info lock poisoned") = Some(Arc::new(info));
497495
}
498496

499497
pub fn is_transport_closed(&self) -> bool {

crates/rmcp/tests/test_server_initialization.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,67 @@ async fn server_pinned_version_used_as_fallback_for_unknown_client_request() {
299299
assert_eq!(negotiated, ProtocolVersion::V_2025_06_18);
300300
}
301301

302+
fn duplicate_init_request(id: u64, version: &str) -> ClientJsonRpcMessage {
303+
msg(&format!(
304+
r#"{{
305+
"jsonrpc": "2.0",
306+
"id": {id},
307+
"method": "initialize",
308+
"params": {{
309+
"protocolVersion": "{version}",
310+
"capabilities": {{ "sampling": {{}} }},
311+
"clientInfo": {{ "name": "renegotiated-client", "version": "9.9.9" }}
312+
}}
313+
}}"#
314+
))
315+
}
316+
317+
#[tokio::test]
318+
async fn server_accepts_duplicate_initialize() {
319+
let (server_transport, client_transport) = tokio::io::duplex(4096);
320+
let _server = tokio::spawn(async move { TestServer::new().serve(server_transport).await });
321+
let mut client = IntoTransport::<rmcp::RoleClient, _, _>::into_transport(client_transport);
322+
323+
do_initialize(&mut client).await;
324+
client.send(initialized_notification()).await.unwrap();
325+
326+
client
327+
.send(duplicate_init_request(2, "2025-11-25"))
328+
.await
329+
.unwrap();
330+
let response = client.receive().await.unwrap();
331+
assert!(
332+
matches!(response, ServerJsonRpcMessage::Response(_)),
333+
"expected successful InitializeResult, got: {response:?}"
334+
);
335+
}
336+
337+
#[tokio::test]
338+
async fn server_session_remains_usable_after_renegotiation() {
339+
let (server_transport, client_transport) = tokio::io::duplex(4096);
340+
let _server = tokio::spawn(async move { TestServer::new().serve(server_transport).await });
341+
let mut client = IntoTransport::<rmcp::RoleClient, _, _>::into_transport(client_transport);
342+
343+
do_initialize(&mut client).await;
344+
client.send(initialized_notification()).await.unwrap();
345+
client
346+
.send(duplicate_init_request(2, "2025-11-25"))
347+
.await
348+
.unwrap();
349+
let _renegotiated = client.receive().await.unwrap();
350+
351+
client.send(ping_request(3)).await.unwrap();
352+
let pong = client.receive().await.unwrap();
353+
assert!(
354+
matches!(
355+
pong,
356+
ServerJsonRpcMessage::Response(ref r)
357+
if matches!(r.result, ServerResult::EmptyResult(_))
358+
),
359+
"expected EmptyResult ping after renegotiation, got: {pong:?}"
360+
);
361+
}
362+
302363
// Server buffers multiple requests before initialized and processes them in order.
303364
#[tokio::test]
304365
async fn server_init_buffers_multiple_requests_before_initialized() {

0 commit comments

Comments
 (0)