use std::sync::Arc;
use futures_core::Stream;
use rmcp::model::{ClientJsonRpcMessage, GetExtensions as _, ServerJsonRpcMessage};
use rmcp::transport::streamable_http_server::session::{
local::{LocalSessionManager, LocalSessionManagerError},
EventStore, RestoreOutcome, ServerSseMessage, SessionId, SessionManager,
};
#[derive(Debug, Clone)]
pub struct TransportSessionId(pub SessionId);
#[derive(Debug, Default)]
pub struct AuditedSessionManager {
inner: LocalSessionManager,
}
fn stamp(message: &mut ClientJsonRpcMessage, id: &SessionId) {
match message {
ClientJsonRpcMessage::Request(req) => {
req.request
.extensions_mut()
.insert(TransportSessionId(id.clone()));
}
ClientJsonRpcMessage::Notification(notification) => {
notification
.notification
.extensions_mut()
.insert(TransportSessionId(id.clone()));
}
ClientJsonRpcMessage::Response(_) | ClientJsonRpcMessage::Error(_) => {}
}
}
impl SessionManager for AuditedSessionManager {
type Error = LocalSessionManagerError;
type Transport = <LocalSessionManager as SessionManager>::Transport;
async fn create_session(&self) -> Result<(SessionId, Self::Transport), Self::Error> {
self.inner.create_session().await
}
async fn initialize_session(
&self,
id: &SessionId,
mut message: ClientJsonRpcMessage,
) -> Result<ServerJsonRpcMessage, Self::Error> {
stamp(&mut message, id);
self.inner.initialize_session(id, message).await
}
async fn has_session(&self, id: &SessionId) -> Result<bool, Self::Error> {
self.inner.has_session(id).await
}
async fn close_session(&self, id: &SessionId) -> Result<(), Self::Error> {
self.inner.close_session(id).await
}
async fn create_stream(
&self,
id: &SessionId,
mut message: ClientJsonRpcMessage,
) -> Result<impl Stream<Item = ServerSseMessage> + Send + Sync + 'static, Self::Error> {
stamp(&mut message, id);
self.inner.create_stream(id, message).await
}
async fn accept_message(
&self,
id: &SessionId,
mut message: ClientJsonRpcMessage,
) -> Result<(), Self::Error> {
stamp(&mut message, id);
self.inner.accept_message(id, message).await
}
async fn create_standalone_stream(
&self,
id: &SessionId,
) -> Result<impl Stream<Item = ServerSseMessage> + Send + Sync + 'static, Self::Error> {
self.inner.create_standalone_stream(id).await
}
async fn resume(
&self,
id: &SessionId,
last_event_id: String,
) -> Result<impl Stream<Item = ServerSseMessage> + Send + Sync + 'static, Self::Error> {
self.inner.resume(id, last_event_id).await
}
async fn restore_session(
&self,
id: SessionId,
) -> Result<RestoreOutcome<Self::Transport>, Self::Error> {
self.inner.restore_session(id).await
}
fn event_store(&self) -> Option<Arc<dyn EventStore>> {
self.inner.event_store()
}
}
#[cfg(test)]
mod tests {
use super::*;
use rmcp::model::{
ClientNotification, ClientRequest, ClientResult, ErrorData, InitializedNotification,
JsonRpcMessage, PingRequest, RequestId,
};
fn session() -> SessionId {
SessionId::from("sess-180")
}
fn stamped(message: &ClientJsonRpcMessage) -> Option<&str> {
let extensions = match message {
ClientJsonRpcMessage::Request(req) => req.request.extensions(),
ClientJsonRpcMessage::Notification(notification) => {
notification.notification.extensions()
}
ClientJsonRpcMessage::Response(_) | ClientJsonRpcMessage::Error(_) => return None,
};
extensions.get::<TransportSessionId>().map(|id| &*id.0)
}
#[test]
fn stamp_reaches_a_request() {
let mut message = ClientJsonRpcMessage::request(
ClientRequest::PingRequest(PingRequest::default()),
RequestId::Number(1),
);
stamp(&mut message, &session());
assert_eq!(stamped(&message), Some("sess-180"));
}
#[test]
fn stamp_reaches_a_notification() {
let mut message = ClientJsonRpcMessage::notification(
ClientNotification::InitializedNotification(InitializedNotification::default()),
);
stamp(&mut message, &session());
assert_eq!(stamped(&message), Some("sess-180"));
}
#[test]
fn stamp_leaves_responses_and_errors_alone() {
let json =
|m: &ClientJsonRpcMessage| serde_json::to_value(m).expect("a message serializes");
let mut response =
ClientJsonRpcMessage::response(ClientResult::empty(()), RequestId::Number(7));
let before = json(&response);
stamp(&mut response, &session());
assert!(matches!(response, ClientJsonRpcMessage::Response(_)));
assert_eq!(json(&response), before);
let mut error = JsonRpcMessage::error(
ErrorData::internal_error("boom", None),
Some(RequestId::Number(7)),
);
let before = json(&error);
stamp(&mut error, &session());
assert!(matches!(error, ClientJsonRpcMessage::Error(_)));
assert_eq!(json(&error), before);
}
}