1use std::{
2 borrow::Cow,
3 collections::{HashMap, HashSet},
4 sync::Arc,
5 time::Duration,
6};
7
8use futures::{Stream, StreamExt, future::BoxFuture, stream::BoxStream};
9use http::{HeaderName, HeaderValue};
10pub use sse_stream::Error as SseError;
11use sse_stream::Sse;
12use thiserror::Error;
13use tokio_util::sync::CancellationToken;
14use tracing::debug;
15
16use super::common::client_side_sse::{
17 DEFAULT_MAX_SSE_EVENT_SIZE, ExponentialBackoff, SseRetryPolicy, SseStreamReconnect,
18};
19use crate::{
20 RoleClient,
21 model::{
22 ClientJsonRpcMessage, ClientNotification, ClientRequest, ErrorData, GetMeta,
23 InitializedNotification, JsonObject, ProtocolVersion, RequestId, ServerJsonRpcMessage,
24 ServerResult,
25 },
26 transport::{
27 common::{client_side_sse::SseAutoReconnectStream, mcp_headers},
28 worker::{Worker, WorkerQuitReason, WorkerSendRequest, WorkerTransport},
29 },
30};
31
32type BoxedSseStream = BoxStream<'static, Result<Sse, SseError>>;
33const SESSION_CLEANUP_TIMEOUT: Duration = Duration::from_secs(5);
34
35fn build_request_headers(
36 base: &HashMap<HeaderName, HeaderValue>,
37 message: &ClientJsonRpcMessage,
38 tool_cache: &HashMap<String, Arc<JsonObject>>,
39 version: &ProtocolVersion,
40) -> HashMap<HeaderName, HeaderValue> {
41 use serde_json::Value;
42
43 let mut headers = base.clone();
44 if *version >= ProtocolVersion::STANDARD_HEADERS {
45 if let Ok(value) = serde_json::to_value(message) {
46 let schema = value
47 .get("method")
48 .and_then(Value::as_str)
49 .filter(|method| *method == "tools/call")
50 .and_then(|_| value.get("params"))
51 .and_then(|params| params.get("name"))
52 .and_then(Value::as_str)
53 .and_then(|name| tool_cache.get(name))
54 .map(Arc::as_ref);
55 for (name, val) in mcp_headers::standard_request_headers(&value, schema) {
56 headers.insert(name, val);
57 }
58 }
59 }
60 headers
61}
62
63fn request_version_headers(
64 base: &HashMap<HeaderName, HeaderValue>,
65 message: &ClientJsonRpcMessage,
66 fallback: &ProtocolVersion,
67 tool_cache: &HashMap<String, Arc<JsonObject>>,
68) -> (ProtocolVersion, HashMap<HeaderName, HeaderValue>) {
69 let version = match message {
70 ClientJsonRpcMessage::Request(request) => request
71 .request
72 .get_meta()
73 .protocol_version()
74 .unwrap_or_else(|| fallback.clone()),
75 _ => fallback.clone(),
76 };
77 let mut headers = build_request_headers(base, message, tool_cache, &version);
78 if let Ok(value) = HeaderValue::from_str(version.as_str()) {
79 headers.insert(HeaderName::from_static("mcp-protocol-version"), value);
80 }
81 (version, headers)
82}
83
84fn cache_tools_from_response(
85 cache: &mut HashMap<String, Arc<JsonObject>>,
86 message: &ServerJsonRpcMessage,
87) {
88 if let ServerJsonRpcMessage::Response(response) = message {
89 if let ServerResult::ListToolsResult(list) = &response.result {
90 for tool in &list.tools {
91 if let Err(reason) =
92 mcp_headers::validate_param_header_annotations(&tool.input_schema)
93 {
94 tracing::warn!(tool = %tool.name, "ignoring x-mcp-header annotations: {reason}");
95 continue;
96 }
97 cache.insert(tool.name.to_string(), tool.input_schema.clone());
98 }
99 }
100 }
101}
102
103fn negotiate_version_headers(
104 init_response: &ServerJsonRpcMessage,
105 base: HashMap<HeaderName, HeaderValue>,
106) -> (ProtocolVersion, HashMap<HeaderName, HeaderValue>) {
107 let mut version = ProtocolVersion::default();
108 let mut headers = base;
109 if let ServerJsonRpcMessage::Response(response) = init_response {
110 if let ServerResult::InitializeResult(init_result) = &response.result {
111 version = init_result.protocol_version.clone();
112 if let Ok(hv) = HeaderValue::from_str(init_result.protocol_version.as_str()) {
114 headers.insert(HeaderName::from_static("mcp-protocol-version"), hv);
115 }
116 }
117 }
118 (version, headers)
119}
120
121#[derive(Debug)]
122#[non_exhaustive]
123pub struct AuthRequiredError {
124 pub www_authenticate_header: String,
125}
126
127impl AuthRequiredError {
128 pub fn new(www_authenticate_header: String) -> Self {
130 Self {
131 www_authenticate_header,
132 }
133 }
134}
135
136#[derive(Debug)]
137#[non_exhaustive]
138pub struct InsufficientScopeError {
139 pub www_authenticate_header: String,
140 pub required_scope: Option<String>,
141}
142
143impl InsufficientScopeError {
144 pub fn new(www_authenticate_header: String, required_scope: Option<String>) -> Self {
146 Self {
147 www_authenticate_header,
148 required_scope,
149 }
150 }
151
152 pub fn can_upgrade(&self) -> bool {
154 self.required_scope.is_some()
155 }
156
157 pub fn get_required_scope(&self) -> Option<&str> {
159 self.required_scope.as_deref()
160 }
161}
162
163#[derive(Error, Debug)]
164#[non_exhaustive]
165pub enum StreamableHttpError<E: std::error::Error + Send + Sync + 'static> {
166 #[error("SSE error: {0}")]
167 Sse(#[from] SseError),
168 #[error("Io error: {0}")]
169 Io(#[from] std::io::Error),
170 #[error("Client error: {0}")]
171 Client(E),
172 #[error("unexpected end of stream")]
173 UnexpectedEndOfStream,
174 #[error("unexpected server response: {0}")]
175 UnexpectedServerResponse(Cow<'static, str>),
176 #[error("Unexpected content type: {0:?}")]
177 UnexpectedContentType(Option<String>),
178 #[error("Server does not support SSE")]
179 ServerDoesNotSupportSse,
180 #[error("Server does not support delete session")]
181 ServerDoesNotSupportDeleteSession,
182 #[error("Tokio join error: {0}")]
183 TokioJoinError(#[from] tokio::task::JoinError),
184 #[error("Deserialize error: {0}")]
185 Deserialize(#[from] serde_json::Error),
186 #[error("Transport channel closed")]
187 TransportChannelClosed,
188 #[error("Missing session id in HTTP response")]
189 MissingSessionIdInResponse,
190 #[cfg(feature = "auth")]
191 #[error("Auth error: {0}")]
192 Auth(#[from] crate::transport::auth::AuthError),
193 #[error("Auth required")]
194 AuthRequired(AuthRequiredError),
195 #[error("Insufficient scope")]
196 InsufficientScope(InsufficientScopeError),
197 #[error("Header name '{0}' is reserved and conflicts with default headers")]
198 ReservedHeaderConflict(String),
199 #[error("Session expired (HTTP 404)")]
200 SessionExpired,
201}
202
203#[derive(Debug, Clone, Error)]
204#[non_exhaustive]
205pub enum StreamableHttpProtocolError {
206 #[error("Missing session id in response")]
207 MissingSessionIdInResponse,
208}
209
210#[expect(
211 clippy::large_enum_variant,
212 reason = "boxing the streaming response would add an allocation to the common response path"
213)]
214#[non_exhaustive]
215pub enum StreamableHttpPostResponse {
216 Accepted,
217 Json(ServerJsonRpcMessage, Option<String>),
218 Sse(BoxedSseStream, Option<String>),
219}
220
221impl std::fmt::Debug for StreamableHttpPostResponse {
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 match self {
224 Self::Accepted => write!(f, "Accepted"),
225 Self::Json(arg0, arg1) => f.debug_tuple("Json").field(arg0).field(arg1).finish(),
226 Self::Sse(_, arg1) => f.debug_tuple("Sse").field(arg1).finish(),
227 }
228 }
229}
230
231impl StreamableHttpPostResponse {
232 pub async fn expect_initialized<E>(
233 self,
234 ) -> Result<(ServerJsonRpcMessage, Option<String>), StreamableHttpError<E>>
235 where
236 E: std::error::Error + Send + Sync + 'static,
237 {
238 match self {
239 Self::Json(message, session_id) => Ok((message, session_id)),
240 Self::Sse(mut stream, session_id) => {
241 while let Some(event) = stream.next().await {
242 let event = event?;
243 let payload = event.data.unwrap_or_default();
244 if payload.trim().is_empty() {
245 continue;
246 }
247
248 let message: ServerJsonRpcMessage = serde_json::from_str(&payload)?;
249
250 if matches!(message, ServerJsonRpcMessage::Response(_)) {
251 return Ok((message, session_id));
252 }
253
254 debug!(
255 ?message,
256 "received message before initialize response; continuing to drain stream"
257 );
258 }
259
260 Err(StreamableHttpError::UnexpectedServerResponse(
261 "empty sse stream".into(),
262 ))
263 }
264 _ => Err(StreamableHttpError::UnexpectedServerResponse(
265 "expect initialized, accepted".into(),
266 )),
267 }
268 }
269
270 pub fn expect_json<E>(self) -> Result<ServerJsonRpcMessage, StreamableHttpError<E>>
271 where
272 E: std::error::Error + Send + Sync + 'static,
273 {
274 match self {
275 Self::Json(message, ..) => Ok(message),
276 got => Err(StreamableHttpError::UnexpectedServerResponse(
277 format!("expect json, got {got:?}").into(),
278 )),
279 }
280 }
281
282 pub fn expect_accepted_or_json<E>(self) -> Result<(), StreamableHttpError<E>>
283 where
284 E: std::error::Error + Send + Sync + 'static,
285 {
286 match self {
287 Self::Accepted => Ok(()),
288 Self::Json(..) => Ok(()),
290 got => Err(StreamableHttpError::UnexpectedServerResponse(
291 format!("expect accepted or json, got {got:?}").into(),
292 )),
293 }
294 }
295}
296
297pub trait StreamableHttpClient: Clone + Send + 'static {
304 type Error: std::error::Error + Send + Sync + 'static;
305 fn post_message(
306 &self,
307 uri: Arc<str>,
308 message: ClientJsonRpcMessage,
309 session_id: Option<Arc<str>>,
310 auth_header: Option<String>,
311 custom_headers: HashMap<HeaderName, HeaderValue>,
312 ) -> impl Future<Output = Result<StreamableHttpPostResponse, StreamableHttpError<Self::Error>>>
313 + Send
314 + '_;
315 fn post_message_with_max_sse_event_size(
326 &self,
327 uri: Arc<str>,
328 message: ClientJsonRpcMessage,
329 session_id: Option<Arc<str>>,
330 auth_header: Option<String>,
331 custom_headers: HashMap<HeaderName, HeaderValue>,
332 _max_sse_event_size: usize,
333 ) -> impl Future<Output = Result<StreamableHttpPostResponse, StreamableHttpError<Self::Error>>>
334 + Send
335 + '_ {
336 self.post_message(uri, message, session_id, auth_header, custom_headers)
337 }
338 fn delete_session(
339 &self,
340 uri: Arc<str>,
341 session_id: Arc<str>,
342 auth_header: Option<String>,
343 custom_headers: HashMap<HeaderName, HeaderValue>,
344 ) -> impl Future<Output = Result<(), StreamableHttpError<Self::Error>>> + Send + '_;
345 fn get_stream(
346 &self,
347 uri: Arc<str>,
348 session_id: Arc<str>,
349 last_event_id: Option<String>,
350 auth_header: Option<String>,
351 custom_headers: HashMap<HeaderName, HeaderValue>,
352 ) -> impl Future<
353 Output = Result<
354 BoxStream<'static, Result<Sse, SseError>>,
355 StreamableHttpError<Self::Error>,
356 >,
357 > + Send
358 + '_;
359 fn get_stream_with_max_sse_event_size(
370 &self,
371 uri: Arc<str>,
372 session_id: Arc<str>,
373 last_event_id: Option<String>,
374 auth_header: Option<String>,
375 custom_headers: HashMap<HeaderName, HeaderValue>,
376 _max_sse_event_size: usize,
377 ) -> impl Future<
378 Output = Result<
379 BoxStream<'static, Result<Sse, SseError>>,
380 StreamableHttpError<Self::Error>,
381 >,
382 > + Send
383 + '_ {
384 self.get_stream(uri, session_id, last_event_id, auth_header, custom_headers)
385 }
386}
387
388#[non_exhaustive]
389pub struct RetryConfig {
390 pub max_times: Option<usize>,
391 pub min_duration: Duration,
392}
393
394struct StreamableHttpClientReconnect<C> {
395 pub client: C,
396 pub session_id: Arc<str>,
397 pub uri: Arc<str>,
398 pub auth_header: Option<String>,
399 pub custom_headers: HashMap<HeaderName, HeaderValue>,
400 pub max_sse_event_size: usize,
401}
402
403impl<C: StreamableHttpClient> SseStreamReconnect for StreamableHttpClientReconnect<C> {
404 type Error = StreamableHttpError<C::Error>;
405 type Future = BoxFuture<'static, Result<BoxedSseStream, Self::Error>>;
406 fn retry_connection(&mut self, last_event_id: Option<&str>) -> Self::Future {
407 let client = self.client.clone();
408 let uri = self.uri.clone();
409 let session_id = self.session_id.clone();
410 let auth_header = self.auth_header.clone();
411 let custom_headers = self.custom_headers.clone();
412 let max_sse_event_size = self.max_sse_event_size;
413 let last_event_id = last_event_id.map(|s| s.to_owned());
414 Box::pin(async move {
415 client
416 .get_stream_with_max_sse_event_size(
417 uri,
418 session_id,
419 last_event_id,
420 auth_header,
421 custom_headers,
422 max_sse_event_size,
423 )
424 .await
425 })
426 }
427
428 fn map_fatal_stream_error(&mut self, error: SseError) -> Option<Self::Error> {
429 Some(StreamableHttpError::Sse(error))
430 }
431}
432
433struct SessionCleanupInfo<C> {
435 client: C,
436 uri: Arc<str>,
437 session_id: Arc<str>,
438 auth_header: Option<String>,
439 protocol_headers: HashMap<HeaderName, HeaderValue>,
440}
441
442#[derive(Debug, Clone, Default)]
443#[non_exhaustive]
444pub struct StreamableHttpClientWorker<C: StreamableHttpClient> {
445 pub client: C,
446 pub config: StreamableHttpClientTransportConfig,
447}
448
449impl<C: StreamableHttpClient + Default> StreamableHttpClientWorker<C> {
450 pub fn new_simple(url: impl Into<Arc<str>>) -> Self {
451 Self {
452 client: C::default(),
453 config: StreamableHttpClientTransportConfig {
454 uri: url.into(),
455 ..Default::default()
456 },
457 }
458 }
459}
460
461impl<C: StreamableHttpClient> StreamableHttpClientWorker<C> {
462 pub fn new(client: C, config: StreamableHttpClientTransportConfig) -> Self {
463 Self { client, config }
464 }
465}
466
467impl<C: StreamableHttpClient> StreamableHttpClientWorker<C> {
468 fn client_request_id(message: &ClientJsonRpcMessage) -> Option<RequestId> {
469 match message {
470 ClientJsonRpcMessage::Request(request) => Some(request.id.clone()),
471 _ => None,
472 }
473 }
474
475 fn server_response_id(message: &ServerJsonRpcMessage) -> Option<&RequestId> {
476 match message {
477 ServerJsonRpcMessage::Response(response) => Some(&response.id),
478 ServerJsonRpcMessage::Error(error) => error.id.as_ref(),
479 _ => None,
480 }
481 }
482
483 fn mark_stream_response_pending(
484 pending_stream_response_ids: &mut HashSet<RequestId>,
485 request_id: Option<RequestId>,
486 ) {
487 if let Some(request_id) = request_id {
488 pending_stream_response_ids.insert(request_id);
489 }
490 }
491
492 fn clear_stream_response_pending(
493 pending_stream_response_ids: &mut HashSet<RequestId>,
494 message: &ServerJsonRpcMessage,
495 ) {
496 if let Some(id) = Self::server_response_id(message) {
497 pending_stream_response_ids.remove(id);
498 }
499 }
500
501 async fn drain_queued_stream_messages(
502 sse_worker_rx: &mut tokio::sync::mpsc::Receiver<ServerJsonRpcMessage>,
503 context: &mut super::worker::WorkerContext<Self>,
504 pending_stream_response_ids: &mut HashSet<RequestId>,
505 ) -> Result<(), WorkerQuitReason<StreamableHttpError<C::Error>>> {
506 loop {
507 match sse_worker_rx.try_recv() {
508 Ok(message) => {
509 Self::clear_stream_response_pending(pending_stream_response_ids, &message);
510 context.send_to_handler(message).await?;
511 }
512 Err(tokio::sync::mpsc::error::TryRecvError::Empty) => return Ok(()),
513 Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => return Ok(()),
514 }
515 }
516 }
517
518 async fn fail_pending_stream_responses(
519 context: &mut super::worker::WorkerContext<Self>,
520 pending_stream_response_ids: &mut HashSet<RequestId>,
521 ) -> Result<(), WorkerQuitReason<StreamableHttpError<C::Error>>> {
522 if pending_stream_response_ids.is_empty() {
523 return Ok(());
524 }
525
526 let pending_ids = std::mem::take(pending_stream_response_ids);
527 for id in pending_ids {
528 context
529 .send_to_handler(ServerJsonRpcMessage::error(
530 ErrorData::internal_error(
531 "streamable HTTP session was re-initialized before the response arrived",
532 None,
533 ),
534 Some(id),
535 ))
536 .await?;
537 }
538 Ok(())
539 }
540
541 fn raw_sse_to_jsonrpc(
544 stream: BoxedSseStream,
545 ) -> impl Stream<Item = Result<ServerJsonRpcMessage, StreamableHttpError<C::Error>>> + Send + 'static
546 {
547 stream.filter_map(|event| async {
548 match event {
549 Err(e) => Some(Err(StreamableHttpError::Sse(e))),
550 Ok(sse) => {
551 let is_message =
552 matches!(sse.event.as_deref(), None | Some("") | Some("message"));
553 if !is_message {
554 return None;
555 }
556 let data = sse.data?;
557 if data.trim().is_empty() {
558 return None;
559 }
560 match serde_json::from_str::<ServerJsonRpcMessage>(&data) {
561 Ok(msg) => Some(Ok(msg)),
562 Err(e) => {
563 tracing::debug!("failed to deserialize server message: {e}");
564 None
565 }
566 }
567 }
568 }
569 })
570 }
571
572 fn reconnecting_sse_to_jsonrpc(
579 stream: BoxedSseStream,
580 client: C,
581 session_id: Arc<str>,
582 uri: Arc<str>,
583 auth_header: Option<String>,
584 custom_headers: HashMap<HeaderName, HeaderValue>,
585 max_sse_event_size: usize,
586 retry_config: Arc<dyn SseRetryPolicy>,
587 ) -> impl Stream<Item = Result<ServerJsonRpcMessage, StreamableHttpError<C::Error>>> + Send + 'static
588 {
589 SseAutoReconnectStream::new(
590 stream,
591 StreamableHttpClientReconnect {
592 client,
593 session_id,
594 uri,
595 auth_header,
596 custom_headers,
597 max_sse_event_size,
598 },
599 retry_config,
600 )
601 }
602
603 fn response_sse_to_jsonrpc(
610 stream: BoxedSseStream,
611 session_id: Option<Arc<str>>,
612 client: C,
613 uri: Arc<str>,
614 auth_header: Option<String>,
615 custom_headers: HashMap<HeaderName, HeaderValue>,
616 max_sse_event_size: usize,
617 retry_config: Arc<dyn SseRetryPolicy>,
618 ) -> BoxStream<'static, Result<ServerJsonRpcMessage, StreamableHttpError<C::Error>>> {
619 match session_id {
620 Some(session_id) => Self::reconnecting_sse_to_jsonrpc(
621 stream,
622 client,
623 session_id,
624 uri,
625 auth_header,
626 custom_headers,
627 max_sse_event_size,
628 retry_config,
629 )
630 .boxed(),
631 None => Self::raw_sse_to_jsonrpc(stream).boxed(),
632 }
633 }
634
635 async fn execute_sse_stream(
636 sse_stream: impl Stream<Item = Result<ServerJsonRpcMessage, StreamableHttpError<C::Error>>>
637 + Send
638 + 'static,
639 sse_worker_tx: tokio::sync::mpsc::Sender<ServerJsonRpcMessage>,
640 close_on_response: bool,
641 ct: CancellationToken,
642 ) -> Result<(), StreamableHttpError<C::Error>> {
643 let mut sse_stream = std::pin::pin!(sse_stream);
644 loop {
645 let message = tokio::select! {
646 event = sse_stream.next() => {
647 event
648 }
649 _ = ct.cancelled() => {
650 tracing::debug!("cancelled");
651 break;
652 }
653 };
654 let Some(message) = message.transpose()? else {
655 break;
656 };
657 let is_response = matches!(
658 message,
659 ServerJsonRpcMessage::Response(_) | ServerJsonRpcMessage::Error(_)
660 );
661 let yield_result = sse_worker_tx.send(message).await;
662 if yield_result.is_err() {
663 tracing::trace!("streamable http transport worker dropped, exiting");
664 break;
665 }
666 if close_on_response && is_response {
667 tracing::debug!("got response, draining sse stream for connection reuse");
668 let _ = tokio::time::timeout(std::time::Duration::from_millis(50), async {
671 while sse_stream.next().await.is_some() {}
672 })
673 .await;
674 break;
675 }
676 }
677 Ok(())
678 }
679
680 fn spawn_common_stream(
681 streams: &mut tokio::task::JoinSet<Result<(), StreamableHttpError<C::Error>>>,
682 client: C,
683 session_id: Arc<str>,
684 config: &StreamableHttpClientTransportConfig,
685 protocol_headers: HashMap<HeaderName, HeaderValue>,
686 sse_worker_tx: tokio::sync::mpsc::Sender<ServerJsonRpcMessage>,
687 transport_task_ct: CancellationToken,
688 ) {
689 let uri = config.uri.clone();
690 let auth_header = config.auth_header.clone();
691 let retry_config = config.retry_config.clone();
692 let reconnect_uri = config.uri.clone();
693 let reconnect_auth_header = config.auth_header.clone();
694 let max_sse_event_size = config.max_sse_event_size;
695
696 streams.spawn(async move {
697 match client
698 .get_stream_with_max_sse_event_size(
699 uri,
700 session_id.clone(),
701 None,
702 auth_header,
703 protocol_headers.clone(),
704 max_sse_event_size,
705 )
706 .await
707 {
708 Ok(stream) => {
709 let sse_stream = SseAutoReconnectStream::new(
710 stream,
711 StreamableHttpClientReconnect {
712 client,
713 session_id,
714 uri: reconnect_uri,
715 auth_header: reconnect_auth_header,
716 custom_headers: protocol_headers,
717 max_sse_event_size,
718 },
719 retry_config,
720 );
721 Self::execute_sse_stream(
722 sse_stream,
723 sse_worker_tx,
724 false,
725 transport_task_ct.child_token(),
726 )
727 .await
728 }
729 Err(StreamableHttpError::ServerDoesNotSupportSse) => {
730 tracing::debug!("server doesn't support sse, skip common stream");
731 Ok(())
732 }
733 Err(error) => {
734 tracing::error!("fail to get common stream: {error}");
735 Err(error)
736 }
737 }
738 });
739 }
740
741 async fn perform_reinitialization(
751 client: C,
752 saved_init_request: ClientJsonRpcMessage,
753 uri: Arc<str>,
754 auth_header: Option<String>,
755 custom_headers: HashMap<HeaderName, HeaderValue>,
756 max_sse_event_size: usize,
757 ) -> Result<
758 (
759 Option<Arc<str>>,
760 ProtocolVersion,
761 HashMap<HeaderName, HeaderValue>,
762 ),
763 StreamableHttpError<C::Error>,
764 > {
765 let (init_msg, new_session_id_str) = client
766 .post_message_with_max_sse_event_size(
767 uri.clone(),
768 saved_init_request,
769 None,
770 auth_header.clone(),
771 custom_headers.clone(),
772 max_sse_event_size,
773 )
774 .await?
775 .expect_initialized::<C::Error>()
776 .await?;
777
778 let new_session_id: Option<Arc<str>> = new_session_id_str.map(|s| Arc::from(s.as_str()));
779
780 let (negotiated_version, new_protocol_headers) =
781 negotiate_version_headers(&init_msg, custom_headers);
782
783 let initialized_notification = ClientJsonRpcMessage::notification(
784 ClientNotification::InitializedNotification(InitializedNotification {
785 method: Default::default(),
786 extensions: Default::default(),
787 }),
788 );
789 let initialized_headers = build_request_headers(
791 &new_protocol_headers,
792 &initialized_notification,
793 &HashMap::new(),
794 &negotiated_version,
795 );
796 client
797 .post_message_with_max_sse_event_size(
798 uri,
799 initialized_notification,
800 new_session_id.clone(),
801 auth_header,
802 initialized_headers,
803 max_sse_event_size,
804 )
805 .await?
806 .expect_accepted_or_json::<C::Error>()?;
807
808 Ok((new_session_id, negotiated_version, new_protocol_headers))
809 }
810}
811
812impl<C: StreamableHttpClient> Worker for StreamableHttpClientWorker<C> {
813 type Role = RoleClient;
814 type Error = StreamableHttpError<C::Error>;
815 fn err_closed() -> Self::Error {
816 StreamableHttpError::TransportChannelClosed
817 }
818 fn err_join(e: tokio::task::JoinError) -> Self::Error {
819 StreamableHttpError::TokioJoinError(e)
820 }
821 fn config(&self) -> super::worker::WorkerConfig {
822 super::worker::WorkerConfig {
823 name: Some("StreamableHttpClientWorker".into()),
824 channel_buffer_capacity: self.config.channel_buffer_capacity,
825 }
826 }
827 async fn run(
828 self,
829 mut context: super::worker::WorkerContext<Self>,
830 ) -> Result<(), WorkerQuitReason<Self::Error>> {
831 let channel_buffer_capacity = self.config.channel_buffer_capacity;
832 let (sse_worker_tx, mut sse_worker_rx) =
833 tokio::sync::mpsc::channel::<ServerJsonRpcMessage>(channel_buffer_capacity);
834 let config = self.config.clone();
835 let transport_task_ct = context.cancellation_token.clone();
836 let _drop_guard = transport_task_ct.clone().drop_guard();
837 let WorkerSendRequest {
838 responder,
839 message: startup_request,
840 } = context.recv_from_handler().await?;
841 let is_legacy_startup = matches!(
842 &startup_request,
843 ClientJsonRpcMessage::Request(request)
844 if matches!(&request.request, ClientRequest::InitializeRequest(_))
845 );
846 let mut saved_init_request = is_legacy_startup.then(|| startup_request.clone());
847 let empty_tool_cache = HashMap::new();
848 let (bootstrap_version, bootstrap_headers) = if is_legacy_startup {
849 (ProtocolVersion::default(), config.custom_headers.clone())
850 } else {
851 request_version_headers(
852 &config.custom_headers,
853 &startup_request,
854 &ProtocolVersion::default(),
855 &empty_tool_cache,
856 )
857 };
858 let (message, session_id) = match self
859 .client
860 .post_message_with_max_sse_event_size(
861 config.uri.clone(),
862 startup_request,
863 None,
864 config.auth_header.clone(),
865 bootstrap_headers.clone(),
866 config.max_sse_event_size,
867 )
868 .await
869 {
870 Ok(res) => {
871 let _ = responder.send(Ok(()));
872 res.expect_initialized::<C::Error>().await.map_err(
873 WorkerQuitReason::fatal_context("process initialize response"),
874 )?
875 }
876 Err(err) => {
877 let msg = format!("{:?}", err);
878 let _ = responder.send(Err(err));
879 return Err(WorkerQuitReason::fatal(
880 StreamableHttpError::TransportChannelClosed,
881 msg,
882 ));
883 }
884 };
885 let mut session_id: Option<Arc<str>> = if let Some(session_id) = session_id {
886 Some(session_id.into())
887 } else {
888 if !self.config.allow_stateless {
889 return Err(WorkerQuitReason::fatal(
890 StreamableHttpError::<C::Error>::MissingSessionIdInResponse,
891 "process initialize response",
892 ));
893 }
894 None
895 };
896 let (mut negotiated_version, mut protocol_headers) = if is_legacy_startup {
897 negotiate_version_headers(&message, config.custom_headers.clone())
898 } else {
899 (bootstrap_version, bootstrap_headers)
900 };
901 let mut tool_header_cache: HashMap<String, Arc<JsonObject>> = HashMap::new();
904
905 let mut session_cleanup_info = session_id.as_ref().map(|sid| SessionCleanupInfo {
907 client: self.client.clone(),
908 uri: config.uri.clone(),
909 session_id: sid.clone(),
910 auth_header: config.auth_header.clone(),
911 protocol_headers: protocol_headers.clone(),
912 });
913
914 context.send_to_handler(message).await?;
915 if is_legacy_startup {
916 let initialized_notification = context.recv_from_handler().await?;
917 let initialized_headers = build_request_headers(
918 &protocol_headers,
919 &initialized_notification.message,
920 &tool_header_cache,
921 &negotiated_version,
922 );
923 self.client
924 .post_message_with_max_sse_event_size(
925 config.uri.clone(),
926 initialized_notification.message,
927 session_id.clone(),
928 config.auth_header.clone(),
929 initialized_headers,
930 config.max_sse_event_size,
931 )
932 .await
933 .map_err(WorkerQuitReason::fatal_context(
934 "send initialized notification",
935 ))?
936 .expect_accepted_or_json::<C::Error>()
937 .map_err(WorkerQuitReason::fatal_context(
938 "process initialized notification response",
939 ))?;
940 let _ = initialized_notification.responder.send(Ok(()));
941 }
942 #[expect(
943 clippy::large_enum_variant,
944 reason = "the event is short-lived and boxing would add allocation in the event loop"
945 )]
946 enum Event<W: Worker, E: std::error::Error + Send + Sync + 'static> {
947 ClientMessage(WorkerSendRequest<W>),
948 ServerMessage(ServerJsonRpcMessage),
949 StreamResult(Result<(), StreamableHttpError<E>>),
950 }
951 let mut streams = tokio::task::JoinSet::new();
952 let mut pending_stream_response_ids = HashSet::new();
953 let mut awaiting_fallback_initialized = false;
954 if let Some(session_id) = &session_id {
955 Self::spawn_common_stream(
956 &mut streams,
957 self.client.clone(),
958 session_id.clone(),
959 &config,
960 protocol_headers.clone(),
961 sse_worker_tx.clone(),
962 transport_task_ct.clone(),
963 );
964 }
965 let loop_result: Result<(), WorkerQuitReason<Self::Error>> = 'main_loop: loop {
967 let event = tokio::select! {
968 _ = transport_task_ct.cancelled() => {
969 tracing::debug!("cancelled");
970 break 'main_loop Err(WorkerQuitReason::Cancelled);
971 }
972 message = context.recv_from_handler() => {
973 match message {
974 Ok(msg) => Event::ClientMessage(msg),
975 Err(e) => break 'main_loop Err(e),
976 }
977 },
978 message = sse_worker_rx.recv() => {
979 let Some(message) = message else {
980 tracing::trace!("transport dropped, exiting");
981 break 'main_loop Err(WorkerQuitReason::HandlerTerminated);
982 };
983 Event::ServerMessage(message)
984 },
985 terminated_stream = streams.join_next(), if !streams.is_empty() => {
986 match terminated_stream {
987 Some(result) => {
988 Event::StreamResult(result.map_err(StreamableHttpError::TokioJoinError).and_then(std::convert::identity))
989 }
990 None => {
991 continue
992 }
993 }
994 }
995 };
996 match event {
997 Event::ClientMessage(send_request) => {
998 let WorkerSendRequest { message, responder } = send_request;
999 let is_fallback_initialize = saved_init_request.is_none()
1000 && matches!(
1001 &message,
1002 ClientJsonRpcMessage::Request(request)
1003 if matches!(
1004 &request.request,
1005 ClientRequest::InitializeRequest(_)
1006 )
1007 );
1008 if is_fallback_initialize {
1009 saved_init_request = Some(message.clone());
1010 debug_assert!(
1014 session_id.is_none()
1015 && session_cleanup_info.is_none()
1016 && streams.is_empty(),
1017 "discover bootstrap must not create session state"
1018 );
1019
1020 let response = self
1021 .client
1022 .post_message_with_max_sse_event_size(
1023 config.uri.clone(),
1024 message,
1025 None,
1026 config.auth_header.clone(),
1027 config.custom_headers.clone(),
1028 config.max_sse_event_size,
1029 )
1030 .await;
1031 let response = match response {
1032 Ok(response) => {
1033 let _ = responder.send(Ok(()));
1034 response
1035 }
1036 Err(error) => {
1037 let _ = responder.send(Err(error));
1038 continue;
1039 }
1040 };
1041 let (initialize_response, new_session_id) = response
1042 .expect_initialized::<C::Error>()
1043 .await
1044 .map_err(WorkerQuitReason::fatal_context(
1045 "process fallback initialize response",
1046 ))?;
1047 session_id = new_session_id.map(Arc::from);
1048 if session_id.is_none() && !config.allow_stateless {
1049 return Err(WorkerQuitReason::fatal(
1050 StreamableHttpError::<C::Error>::MissingSessionIdInResponse,
1051 "process fallback initialize response",
1052 ));
1053 }
1054 (negotiated_version, protocol_headers) = negotiate_version_headers(
1055 &initialize_response,
1056 config.custom_headers.clone(),
1057 );
1058 session_cleanup_info =
1059 session_id.as_ref().map(|session_id| SessionCleanupInfo {
1060 client: self.client.clone(),
1061 uri: config.uri.clone(),
1062 session_id: session_id.clone(),
1063 auth_header: config.auth_header.clone(),
1064 protocol_headers: protocol_headers.clone(),
1065 });
1066 context.send_to_handler(initialize_response).await?;
1067 awaiting_fallback_initialized = true;
1068 continue;
1069 }
1070
1071 let request_id = Self::client_request_id(&message);
1072 let inline_version = match &message {
1073 ClientJsonRpcMessage::Request(request) => {
1074 request.request.get_meta().protocol_version()
1075 }
1076 _ => None,
1077 };
1078 let is_initialized_notification = matches!(
1079 &message,
1080 ClientJsonRpcMessage::Notification(notification)
1081 if matches!(
1082 ¬ification.notification,
1083 ClientNotification::InitializedNotification(_)
1084 )
1085 );
1086 let (request_version, request_headers) = request_version_headers(
1090 &protocol_headers,
1091 &message,
1092 &negotiated_version,
1093 &tool_header_cache,
1094 );
1095 if inline_version.is_some() {
1096 negotiated_version = request_version.clone();
1097 if let Ok(value) = HeaderValue::from_str(request_version.as_str()) {
1098 protocol_headers
1099 .insert(HeaderName::from_static("mcp-protocol-version"), value);
1100 }
1101 if let Some(cleanup) = &mut session_cleanup_info {
1102 cleanup.protocol_headers = protocol_headers.clone();
1103 }
1104 }
1105 let response = self
1106 .client
1107 .post_message_with_max_sse_event_size(
1108 config.uri.clone(),
1109 message.clone(),
1110 session_id.clone(),
1111 config.auth_header.clone(),
1112 request_headers,
1113 config.max_sse_event_size,
1114 )
1115 .await;
1116 let send_result = match response {
1117 Err(StreamableHttpError::SessionExpired) => {
1118 if let Some(saved_init_request) = saved_init_request
1119 .as_ref()
1120 .filter(|_| config.reinit_on_expired_session)
1121 {
1122 tracing::info!(
1125 "session expired (HTTP 404), attempting transparent re-initialization"
1126 );
1127 match Self::perform_reinitialization(
1128 self.client.clone(),
1129 saved_init_request.clone(),
1130 config.uri.clone(),
1131 config.auth_header.clone(),
1132 config.custom_headers.clone(),
1133 config.max_sse_event_size,
1134 )
1135 .await
1136 {
1137 Ok((
1138 new_session_id,
1139 new_negotiated_version,
1140 new_protocol_headers,
1141 )) => {
1142 streams.abort_all();
1146 while streams.join_next().await.is_some() {}
1147
1148 Self::drain_queued_stream_messages(
1152 &mut sse_worker_rx,
1153 &mut context,
1154 &mut pending_stream_response_ids,
1155 )
1156 .await?;
1157 Self::fail_pending_stream_responses(
1158 &mut context,
1159 &mut pending_stream_response_ids,
1160 )
1161 .await?;
1162
1163 session_id = new_session_id;
1164 negotiated_version = new_negotiated_version;
1165 protocol_headers = new_protocol_headers;
1166 session_cleanup_info =
1167 session_id.as_ref().map(|sid| SessionCleanupInfo {
1168 client: self.client.clone(),
1169 uri: config.uri.clone(),
1170 session_id: sid.clone(),
1171 auth_header: config.auth_header.clone(),
1172 protocol_headers: protocol_headers.clone(),
1173 });
1174
1175 if let Some(new_sid) = &session_id {
1176 Self::spawn_common_stream(
1177 &mut streams,
1178 self.client.clone(),
1179 new_sid.clone(),
1180 &config,
1181 protocol_headers.clone(),
1182 sse_worker_tx.clone(),
1183 transport_task_ct.clone(),
1184 );
1185 }
1186
1187 let (_, retry_headers) = request_version_headers(
1188 &protocol_headers,
1189 &message,
1190 &negotiated_version,
1191 &tool_header_cache,
1192 );
1193 let retry_response = self
1194 .client
1195 .post_message_with_max_sse_event_size(
1196 config.uri.clone(),
1197 message,
1198 session_id.clone(),
1199 config.auth_header.clone(),
1200 retry_headers,
1201 config.max_sse_event_size,
1202 )
1203 .await;
1204 match retry_response {
1205 Err(e) => Err(e),
1206 Ok(StreamableHttpPostResponse::Accepted) => {
1207 Self::mark_stream_response_pending(
1208 &mut pending_stream_response_ids,
1209 request_id,
1210 );
1211 tracing::trace!(
1212 "client message accepted after re-init"
1213 );
1214 Ok(())
1215 }
1216 Ok(StreamableHttpPostResponse::Json(msg, ..)) => {
1217 cache_tools_from_response(
1218 &mut tool_header_cache,
1219 &msg,
1220 );
1221 context.send_to_handler(msg).await?;
1222 Ok(())
1223 }
1224 Ok(StreamableHttpPostResponse::Sse(stream, ..)) => {
1225 Self::mark_stream_response_pending(
1226 &mut pending_stream_response_ids,
1227 request_id,
1228 );
1229 let sse_stream = Self::response_sse_to_jsonrpc(
1230 stream,
1231 session_id.clone(),
1232 self.client.clone(),
1233 config.uri.clone(),
1234 config.auth_header.clone(),
1235 protocol_headers.clone(),
1236 config.max_sse_event_size,
1237 self.config.retry_config.clone(),
1238 );
1239 streams.spawn(Self::execute_sse_stream(
1240 sse_stream,
1241 sse_worker_tx.clone(),
1242 true,
1243 transport_task_ct.child_token(),
1244 ));
1245 tracing::trace!("got new sse stream after re-init");
1246 Ok(())
1247 }
1248 }
1249 }
1250 Err(reinit_err) => Err(reinit_err),
1251 }
1252 } else {
1253 Err(StreamableHttpError::SessionExpired)
1254 }
1255 }
1256 Err(e) => Err(e),
1257 Ok(StreamableHttpPostResponse::Accepted) => {
1258 Self::mark_stream_response_pending(
1259 &mut pending_stream_response_ids,
1260 request_id,
1261 );
1262 tracing::trace!("client message accepted");
1263 Ok(())
1264 }
1265 Ok(StreamableHttpPostResponse::Json(message, ..)) => {
1266 cache_tools_from_response(&mut tool_header_cache, &message);
1267 context.send_to_handler(message).await?;
1268 Ok(())
1269 }
1270 Ok(StreamableHttpPostResponse::Sse(stream, ..)) => {
1271 Self::mark_stream_response_pending(
1272 &mut pending_stream_response_ids,
1273 request_id,
1274 );
1275 let sse_stream = Self::response_sse_to_jsonrpc(
1276 stream,
1277 session_id.clone(),
1278 self.client.clone(),
1279 config.uri.clone(),
1280 config.auth_header.clone(),
1281 protocol_headers.clone(),
1282 config.max_sse_event_size,
1283 self.config.retry_config.clone(),
1284 );
1285 streams.spawn(Self::execute_sse_stream(
1286 sse_stream,
1287 sse_worker_tx.clone(),
1288 true,
1289 transport_task_ct.child_token(),
1290 ));
1291 tracing::trace!("got new sse stream");
1292 Ok(())
1293 }
1294 };
1295 if send_result.is_ok()
1296 && awaiting_fallback_initialized
1297 && is_initialized_notification
1298 {
1299 if let Some(session_id) = &session_id {
1300 Self::spawn_common_stream(
1301 &mut streams,
1302 self.client.clone(),
1303 session_id.clone(),
1304 &config,
1305 protocol_headers.clone(),
1306 sse_worker_tx.clone(),
1307 transport_task_ct.clone(),
1308 );
1309 }
1310 awaiting_fallback_initialized = false;
1311 }
1312 let _ = responder.send(send_result);
1313 }
1314 Event::ServerMessage(json_rpc_message) => {
1315 Self::clear_stream_response_pending(
1316 &mut pending_stream_response_ids,
1317 &json_rpc_message,
1318 );
1319 cache_tools_from_response(&mut tool_header_cache, &json_rpc_message);
1320 if let Err(e) = context.send_to_handler(json_rpc_message).await {
1322 break 'main_loop Err(e);
1323 }
1324 }
1325 Event::StreamResult(result) => {
1326 if result.is_err() {
1327 tracing::warn!(
1328 "sse client event stream terminated with error: {:?}",
1329 result
1330 );
1331 }
1332 }
1333 }
1334 };
1335
1336 if let Some(cleanup) = session_cleanup_info {
1339 let cleanup_session_id = cleanup.session_id.clone();
1340 match tokio::time::timeout(
1341 SESSION_CLEANUP_TIMEOUT,
1342 cleanup.client.delete_session(
1343 cleanup.uri,
1344 cleanup.session_id,
1345 cleanup.auth_header,
1346 cleanup.protocol_headers,
1347 ),
1348 )
1349 .await
1350 {
1351 Ok(Ok(_)) => {
1352 tracing::info!(
1353 session_id = cleanup_session_id.as_ref(),
1354 "delete session success"
1355 )
1356 }
1357 Ok(Err(StreamableHttpError::ServerDoesNotSupportDeleteSession)) => {
1358 tracing::info!(
1359 session_id = cleanup_session_id.as_ref(),
1360 "server doesn't support delete session"
1361 )
1362 }
1363 Ok(Err(e)) => {
1364 tracing::error!(
1365 session_id = cleanup_session_id.as_ref(),
1366 "fail to delete session: {e}"
1367 );
1368 }
1369 Err(_elapsed) => {
1370 tracing::warn!(
1371 session_id = cleanup_session_id.as_ref(),
1372 "session cleanup timed out after {:?}",
1373 SESSION_CLEANUP_TIMEOUT
1374 );
1375 }
1376 }
1377 }
1378
1379 loop_result
1380 }
1381}
1382
1383pub type StreamableHttpClientTransport<C> = WorkerTransport<StreamableHttpClientWorker<C>>;
1476
1477impl<C: StreamableHttpClient> StreamableHttpClientTransport<C> {
1478 pub fn with_client(client: C, config: StreamableHttpClientTransportConfig) -> Self {
1558 let worker = StreamableHttpClientWorker::new(client, config);
1559 WorkerTransport::spawn(worker)
1560 }
1561}
1562#[derive(Debug, Clone)]
1563#[non_exhaustive]
1564pub struct StreamableHttpClientTransportConfig {
1565 pub uri: Arc<str>,
1566 pub retry_config: Arc<dyn SseRetryPolicy>,
1567 pub channel_buffer_capacity: usize,
1568 pub allow_stateless: bool,
1570 pub auth_header: Option<String>,
1572 pub custom_headers: HashMap<HeaderName, HeaderValue>,
1574 pub max_sse_event_size: usize,
1580 pub reinit_on_expired_session: bool,
1590}
1591
1592impl StreamableHttpClientTransportConfig {
1593 pub fn with_uri(uri: impl Into<Arc<str>>) -> Self {
1594 Self {
1595 uri: uri.into(),
1596 ..Default::default()
1597 }
1598 }
1599
1600 pub fn auth_header<T: Into<String>>(mut self, value: T) -> Self {
1606 self.auth_header = Some(value.into());
1608 self
1609 }
1610
1611 pub fn custom_headers(mut self, custom_headers: HashMap<HeaderName, HeaderValue>) -> Self {
1634 self.custom_headers = custom_headers;
1635 self
1636 }
1637
1638 pub fn max_sse_event_size(mut self, bytes: usize) -> Self {
1640 self.max_sse_event_size = bytes;
1641 self
1642 }
1643
1644 pub fn reinit_on_expired_session(mut self, enable: bool) -> Self {
1653 self.reinit_on_expired_session = enable;
1654 self
1655 }
1656}
1657
1658impl Default for StreamableHttpClientTransportConfig {
1659 fn default() -> Self {
1660 Self {
1661 uri: "localhost".into(),
1662 retry_config: Arc::new(ExponentialBackoff::default()),
1663 channel_buffer_capacity: 16,
1664 allow_stateless: true,
1665 auth_header: None,
1666 custom_headers: HashMap::new(),
1667 max_sse_event_size: DEFAULT_MAX_SSE_EVENT_SIZE,
1668 reinit_on_expired_session: true,
1669 }
1670 }
1671}