use std::{convert::Infallible, fmt::Display, sync::Arc, time::Duration}; use bytes::Bytes; use futures::{StreamExt, future::BoxFuture}; use http::{Method, Request, Response, header::ALLOW}; use http_body::Body; use http_body_util::{BodyExt, Full, combinators::BoxBody}; use tokio_stream::wrappers::ReceiverStream; use tokio_util::sync::CancellationToken; use super::session::SessionManager; use crate::{ RoleServer, model::{ClientJsonRpcMessage, ClientRequest, GetExtensions}, serve_server, service::serve_directly, transport::{ OneshotTransport, TransportAdapterIdentity, common::{ http_header::{ EVENT_STREAM_MIME_TYPE, HEADER_LAST_EVENT_ID, HEADER_SESSION_ID, JSON_MIME_TYPE, }, server_side_http::{ BoxResponse, ServerSseMessage, accepted_response, expect_json, internal_error_response, sse_stream_response, unexpected_message_response, }, }, }, }; #[derive(Debug, Clone)] pub struct StreamableHttpServerConfig { /// The ping message duration for SSE connections. pub sse_keep_alive: Option, /// The retry interval for SSE priming events. pub sse_retry: Option, /// If true, the server will create a session for each request and keep it alive. /// When enabled, SSE priming events are sent to enable client reconnection. pub stateful_mode: bool, /// Cancellation token for the Streamable HTTP server. /// /// When this token is cancelled, all active sessions are terminated and /// the server stops accepting new requests. pub cancellation_token: CancellationToken, } impl Default for StreamableHttpServerConfig { fn default() -> Self { Self { sse_keep_alive: Some(Duration::from_secs(15)), sse_retry: Some(Duration::from_secs(3)), stateful_mode: true, cancellation_token: CancellationToken::new(), } } } /// # Streamable Http Server /// /// ## Extract information from raw http request /// /// The http service will consume the request body, however the rest part will be remain and injected into [`crate::model::Extensions`], /// which you can get from [`crate::service::RequestContext`]. /// ```rust /// use rmcp::handler::server::tool::Extension; /// use http::request::Parts; /// async fn my_tool(Extension(parts): Extension) { /// tracing::info!("http parts:{parts:?}") /// } /// ``` pub struct StreamableHttpService { pub config: StreamableHttpServerConfig, session_manager: Arc, service_factory: Arc Result + Send + Sync>, } impl Clone for StreamableHttpService { fn clone(&self) -> Self { Self { config: self.config.clone(), session_manager: self.session_manager.clone(), service_factory: self.service_factory.clone(), } } } impl tower_service::Service> for StreamableHttpService where RequestBody: Body + Send + 'static, S: crate::Service, M: SessionManager, RequestBody::Error: Display, RequestBody::Data: Send + 'static, { type Response = BoxResponse; type Error = Infallible; type Future = BoxFuture<'static, Result>; fn call(&mut self, req: http::Request) -> Self::Future { let service = self.clone(); Box::pin(async move { let response = service.handle(req).await; Ok(response) }) } fn poll_ready( &mut self, _cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { std::task::Poll::Ready(Ok(())) } } impl StreamableHttpService where S: crate::Service + Send + 'static, M: SessionManager, { pub fn new( service_factory: impl Fn() -> Result + Send + Sync + 'static, session_manager: Arc, config: StreamableHttpServerConfig, ) -> Self { Self { config, session_manager, service_factory: Arc::new(service_factory), } } fn get_service(&self) -> Result { (self.service_factory)() } pub async fn handle(&self, request: Request) -> Response> where B: Body + Send + 'static, B::Error: Display, { let method = request.method().clone(); let allowed_methods = match self.config.stateful_mode { true => "GET, POST, DELETE", false => "POST", }; let result = match (method, self.config.stateful_mode) { (Method::POST, _) => self.handle_post(request).await, // if we're not in stateful mode, we don't support GET or DELETE because there is no session (Method::GET, true) => self.handle_get(request).await, (Method::DELETE, true) => self.handle_delete(request).await, _ => { // Handle other methods or return an error let response = Response::builder() .status(http::StatusCode::METHOD_NOT_ALLOWED) .header(ALLOW, allowed_methods) .body(Full::new(Bytes::from("Method Not Allowed")).boxed()) .expect("valid response"); return response; } }; match result { Ok(response) => response, Err(response) => response, } } async fn handle_get(&self, request: Request) -> Result where B: Body + Send + 'static, B::Error: Display, { // check accept header if !request .headers() .get(http::header::ACCEPT) .and_then(|header| header.to_str().ok()) .is_some_and(|header| header.contains(EVENT_STREAM_MIME_TYPE)) { return Ok(Response::builder() .status(http::StatusCode::NOT_ACCEPTABLE) .body( Full::new(Bytes::from( "Not Acceptable: Client must accept text/event-stream", )) .boxed(), ) .expect("valid response")); } // check session id let session_id = request .headers() .get(HEADER_SESSION_ID) .and_then(|v| v.to_str().ok()) .map(|s| s.to_owned().into()); let Some(session_id) = session_id else { // unauthorized return Ok(Response::builder() .status(http::StatusCode::UNAUTHORIZED) .body(Full::new(Bytes::from("Unauthorized: Session ID is required")).boxed()) .expect("valid response")); }; // check if session exists let has_session = self .session_manager .has_session(&session_id) .await .map_err(internal_error_response("check session"))?; if !has_session { // unauthorized return Ok(Response::builder() .status(http::StatusCode::UNAUTHORIZED) .body(Full::new(Bytes::from("Unauthorized: Session not found")).boxed()) .expect("valid response")); } // check if last event id is provided let last_event_id = request .headers() .get(HEADER_LAST_EVENT_ID) .and_then(|v| v.to_str().ok()) .map(|s| s.to_owned()); if let Some(last_event_id) = last_event_id { // check if session has this event id let stream = self .session_manager .resume(&session_id, last_event_id) .await .map_err(internal_error_response("resume session"))?; // Resume doesn't need priming - client already has the event ID Ok(sse_stream_response( stream, self.config.sse_keep_alive, self.config.cancellation_token.child_token(), )) } else { // create standalone stream let stream = self .session_manager .create_standalone_stream(&session_id) .await .map_err(internal_error_response("create standalone stream"))?; // Prepend priming event if sse_retry configured let stream = if let Some(retry) = self.config.sse_retry { let priming = ServerSseMessage { event_id: Some("0".into()), message: None, retry: Some(retry), }; futures::stream::once(async move { priming }) .chain(stream) .left_stream() } else { stream.right_stream() }; Ok(sse_stream_response( stream, self.config.sse_keep_alive, self.config.cancellation_token.child_token(), )) } } async fn handle_post(&self, request: Request) -> Result where B: Body + Send + 'static, B::Error: Display, { // check accept header if !request .headers() .get(http::header::ACCEPT) .and_then(|header| header.to_str().ok()) .is_some_and(|header| { header.contains(JSON_MIME_TYPE) && header.contains(EVENT_STREAM_MIME_TYPE) }) { return Ok(Response::builder() .status(http::StatusCode::NOT_ACCEPTABLE) .body(Full::new(Bytes::from("Not Acceptable: Client must accept both application/json and text/event-stream")).boxed()) .expect("valid response")); } // check content type if !request .headers() .get(http::header::CONTENT_TYPE) .and_then(|header| header.to_str().ok()) .is_some_and(|header| header.starts_with(JSON_MIME_TYPE)) { return Ok(Response::builder() .status(http::StatusCode::UNSUPPORTED_MEDIA_TYPE) .body( Full::new(Bytes::from( "Unsupported Media Type: Content-Type must be application/json", )) .boxed(), ) .expect("valid response")); } // json deserialize request body let (part, body) = request.into_parts(); let mut message = match expect_json(body).await { Ok(message) => message, Err(response) => return Ok(response), }; if self.config.stateful_mode { // do we have a session id? let session_id = part .headers .get(HEADER_SESSION_ID) .and_then(|v| v.to_str().ok()); if let Some(session_id) = session_id { let session_id = session_id.to_owned().into(); let has_session = self .session_manager .has_session(&session_id) .await .map_err(internal_error_response("check session"))?; if !has_session { // unauthorized return Ok(Response::builder() .status(http::StatusCode::UNAUTHORIZED) .body(Full::new(Bytes::from("Unauthorized: Session not found")).boxed()) .expect("valid response")); } // inject request part to extensions match &mut message { ClientJsonRpcMessage::Request(req) => { req.request.extensions_mut().insert(part); } ClientJsonRpcMessage::Notification(not) => { not.notification.extensions_mut().insert(part); } _ => { // skip } } match message { ClientJsonRpcMessage::Request(_) => { let stream = self .session_manager .create_stream(&session_id, message) .await .map_err(internal_error_response("get session"))?; // Prepend priming event if sse_retry configured let stream = if let Some(retry) = self.config.sse_retry { let priming = ServerSseMessage { event_id: Some("0".into()), message: None, retry: Some(retry), }; futures::stream::once(async move { priming }) .chain(stream) .left_stream() } else { stream.right_stream() }; Ok(sse_stream_response( stream, self.config.sse_keep_alive, self.config.cancellation_token.child_token(), )) } ClientJsonRpcMessage::Notification(_) | ClientJsonRpcMessage::Response(_) | ClientJsonRpcMessage::Error(_) => { // handle notification self.session_manager .accept_message(&session_id, message) .await .map_err(internal_error_response("accept message"))?; Ok(accepted_response()) } } } else { let (session_id, transport) = self .session_manager .create_session() .await .map_err(internal_error_response("create session"))?; if let ClientJsonRpcMessage::Request(req) = &mut message { if !matches!(req.request, ClientRequest::InitializeRequest(_)) { return Err(unexpected_message_response("initialize request")); } // inject request part to extensions req.request.extensions_mut().insert(part); } else { return Err(unexpected_message_response("initialize request")); } let service = self .get_service() .map_err(internal_error_response("get service"))?; // spawn a task to serve the session tokio::spawn({ let session_manager = self.session_manager.clone(); let session_id = session_id.clone(); async move { let service = serve_server::( service, transport, ) .await; match service { Ok(service) => { // on service created let _ = service.waiting().await; } Err(e) => { tracing::error!("Failed to create service: {e}"); } } let _ = session_manager .close_session(&session_id) .await .inspect_err(|e| { tracing::error!("Failed to close session {session_id}: {e}"); }); } }); // get initialize response let response = self .session_manager .initialize_session(&session_id, message) .await .map_err(internal_error_response("create stream"))?; let stream = futures::stream::once(async move { ServerSseMessage { event_id: None, message: Some(Arc::new(response)), retry: None, } }); // Prepend priming event if sse_retry configured let stream = if let Some(retry) = self.config.sse_retry { let priming = ServerSseMessage { event_id: Some("0".into()), message: None, retry: Some(retry), }; futures::stream::once(async move { priming }) .chain(stream) .left_stream() } else { stream.right_stream() }; let mut response = sse_stream_response( stream, self.config.sse_keep_alive, self.config.cancellation_token.child_token(), ); response.headers_mut().insert( HEADER_SESSION_ID, session_id .parse() .map_err(internal_error_response("create session id header"))?, ); Ok(response) } } else { let service = self .get_service() .map_err(internal_error_response("get service"))?; match message { ClientJsonRpcMessage::Request(mut request) => { request.request.extensions_mut().insert(part); let (transport, receiver) = OneshotTransport::::new(ClientJsonRpcMessage::Request(request)); let service = serve_directly(service, transport, None); tokio::spawn(async move { // on service created let _ = service.waiting().await; }); // Stateless mode: no priming (no session to resume) let stream = ReceiverStream::new(receiver).map(|message| { tracing::info!(?message); ServerSseMessage { event_id: None, message: Some(Arc::new(message)), retry: None, } }); Ok(sse_stream_response( stream, self.config.sse_keep_alive, self.config.cancellation_token.child_token(), )) } ClientJsonRpcMessage::Notification(_notification) => { // ignore Ok(accepted_response()) } ClientJsonRpcMessage::Response(_json_rpc_response) => Ok(accepted_response()), ClientJsonRpcMessage::Error(_json_rpc_error) => Ok(accepted_response()), } } } async fn handle_delete(&self, request: Request) -> Result where B: Body + Send + 'static, B::Error: Display, { // check session id let session_id = request .headers() .get(HEADER_SESSION_ID) .and_then(|v| v.to_str().ok()) .map(|s| s.to_owned().into()); let Some(session_id) = session_id else { // unauthorized return Ok(Response::builder() .status(http::StatusCode::UNAUTHORIZED) .body(Full::new(Bytes::from("Unauthorized: Session ID is required")).boxed()) .expect("valid response")); }; // close session self.session_manager .close_session(&session_id) .await .map_err(internal_error_response("close session"))?; Ok(accepted_response()) } }