1use std::collections::HashMap;
246use std::convert::Infallible;
247use std::future::Future;
248use std::pin::Pin;
249use std::sync::Arc;
250use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
251use std::time::{Duration, Instant};
252
253use axum::{
254 Router,
255 extract::State,
256 http::{HeaderMap, HeaderValue, StatusCode, header},
257 response::{IntoResponse, Response, Sse, sse::Event},
258 routing::{delete, get, post},
259};
260#[cfg(feature = "stateless")]
261use tokio::sync::mpsc;
262use tokio::sync::{Mutex, RwLock, broadcast, oneshot};
263use tokio_stream::StreamExt;
264use tokio_stream::wrappers::BroadcastStream;
265
266#[cfg(feature = "stateless")]
267use crate::context::ServerNotification;
268use crate::context::{
269 ChannelClientRequester, ClientRequesterHandle, NotificationReceiver, OutgoingRequest,
270 OutgoingRequestReceiver, notification_channel, outgoing_request_channel,
271};
272use crate::error::{Error, JsonRpcError, Result};
273#[cfg(feature = "stateless")]
274use crate::error::{ErrorCode, McpErrorCode};
275use crate::inspection::{McpDirection, McpProtocolRevision};
276use crate::jsonrpc::{JsonRpcService, apply_protocol_result_fields, inspect_runtime_value};
277use crate::protocol::{
278 ClientCapabilities, Implementation, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest,
279 JsonRpcResponse, LATEST_PROTOCOL_VERSION, McpNotification, PROTOCOL_VERSION_2026_07_28,
280 RequestId,
281};
282#[cfg(feature = "stateless")]
283use crate::protocol::{SubscriptionFilter, SubscriptionsListenParams};
284use crate::router::{McpRouter, RouterRequest, RouterResponse};
285use crate::transport::service::{
286 CatchError, InjectAnnotations, McpBoxService, ServiceFactory, identity_factory,
287};
288#[cfg(feature = "stateless")]
289use crate::transport::subscriptions::{
290 accepted_subscription_filter, subscription_complete_response, subscription_matches,
291 tagged_subscription_notification,
292};
293use crate::{ProtocolSupport, ProtocolSupportError};
294use tower::util::BoxCloneService;
295
296#[cfg(feature = "stateless")]
301fn stash_per_request_meta(req: &JsonRpcRequest, ext: &mut crate::router::Extensions) {
302 if let Some(params) = req.params.as_ref()
303 && let Some(meta) = crate::stateless::StatelessRequestMeta::from_params(params)
304 {
305 ext.insert(meta);
306 }
307}
308
309pub const MCP_SESSION_ID_HEADER: &str = "mcp-session-id";
311
312pub const MCP_PROTOCOL_VERSION_HEADER: &str = "mcp-protocol-version";
314
315pub const MCP_METHOD_HEADER: &str = "mcp-method";
320
321pub const MCP_NAME_HEADER: &str = "mcp-name";
326
327pub const MCP_PARAM_HEADER_PREFIX: &str = "mcp-param-";
331
332pub const DEFAULT_MAX_BODY_SIZE: usize = 4 * 1024 * 1024;
336
337const SSE_MESSAGE_EVENT: &str = "message";
339
340const LAST_EVENT_ID_HEADER: &str = "last-event-id";
342
343struct PendingRequest {
345 response_tx: oneshot::Sender<Result<serde_json::Value>>,
346}
347
348type AssociatedCall = Pin<Box<dyn Future<Output = Result<JsonRpcResponse>> + Send + 'static>>;
349
350enum SessionServiceSource {
353 Router {
355 router: McpRouter,
356 factory: ServiceFactory,
357 },
358 Boxed(std::sync::Mutex<McpBoxService>),
362}
363
364struct Session {
365 id: String,
367 service_source: SessionServiceSource,
369 notifications_tx: broadcast::Sender<String>,
371 created_at: Instant,
373 last_accessed: RwLock<Instant>,
375 pending_requests: Mutex<HashMap<RequestId, PendingRequest>>,
377 request_id_allocator: Option<Arc<AtomicI64>>,
382 protocol_version: RwLock<String>,
384 client_info: RwLock<Option<Implementation>>,
391 client_capabilities: RwLock<Option<ClientCapabilities>>,
398 event_counter: AtomicU64,
400 event_store: Arc<dyn crate::event_store::EventStore>,
402 initialized_notification_received: std::sync::atomic::AtomicBool,
410}
411
412impl Session {
413 fn new(
414 router: McpRouter,
415 sampling_enabled: bool,
416 service_factory: ServiceFactory,
417 event_store: Arc<dyn crate::event_store::EventStore>,
418 ) -> Self {
419 let (notifications_tx, _) = broadcast::channel(100);
420
421 let (notif_sender, mut notif_receiver) = notification_channel(256);
426 let router = router.with_notification_sender(notif_sender);
427
428 let broadcast_tx = notifications_tx.clone();
429 tokio::spawn(async move {
430 while let Some(notification) = notif_receiver.recv().await {
431 if let Some(json) = crate::transport::stdio::serialize_notification(¬ification) {
432 let _ = broadcast_tx.send(json);
434 }
435 }
436 });
437
438 let request_id_allocator = if sampling_enabled {
439 Some(Arc::new(AtomicI64::new(1)))
440 } else {
441 None
442 };
443
444 let now = Instant::now();
445 Self {
446 id: uuid::Uuid::new_v4().to_string(),
447 service_source: SessionServiceSource::Router {
448 router,
449 factory: service_factory,
450 },
451 notifications_tx,
452 created_at: now,
453 last_accessed: RwLock::new(now),
454 pending_requests: Mutex::new(HashMap::new()),
455 request_id_allocator,
456 protocol_version: RwLock::new(LATEST_PROTOCOL_VERSION.to_string()),
457 client_info: RwLock::new(None),
458 client_capabilities: RwLock::new(None),
459 event_counter: AtomicU64::new(0),
460 event_store,
461 initialized_notification_received: std::sync::atomic::AtomicBool::new(false),
462 }
463 }
464
465 fn from_service(
471 service: McpBoxService,
472 event_store: Arc<dyn crate::event_store::EventStore>,
473 ) -> Self {
474 let (notifications_tx, _) = broadcast::channel(100);
475
476 let now = Instant::now();
477 Self {
478 id: uuid::Uuid::new_v4().to_string(),
479 service_source: SessionServiceSource::Boxed(std::sync::Mutex::new(service)),
480 notifications_tx,
481 created_at: now,
482 last_accessed: RwLock::new(now),
483 pending_requests: Mutex::new(HashMap::new()),
484 request_id_allocator: None,
485 protocol_version: RwLock::new(LATEST_PROTOCOL_VERSION.to_string()),
486 client_info: RwLock::new(None),
487 client_capabilities: RwLock::new(None),
488 event_counter: AtomicU64::new(0),
489 event_store,
490 initialized_notification_received: std::sync::atomic::AtomicBool::new(false),
491 }
492 }
493
494 fn restored(
504 record: &crate::session_store::SessionRecord,
505 router: McpRouter,
506 sampling_enabled: bool,
507 service_factory: ServiceFactory,
508 event_store: Arc<dyn crate::event_store::EventStore>,
509 ) -> Self {
510 router.session().mark_initialized();
513
514 let (notifications_tx, _) = broadcast::channel(100);
515 let (notif_sender, mut notif_receiver) = notification_channel(256);
516 let router = router.with_notification_sender(notif_sender);
517
518 let broadcast_tx = notifications_tx.clone();
519 tokio::spawn(async move {
520 while let Some(notification) = notif_receiver.recv().await {
521 if let Some(json) = crate::transport::stdio::serialize_notification(¬ification) {
522 let _ = broadcast_tx.send(json);
523 }
524 }
525 });
526
527 let request_id_allocator = if sampling_enabled {
528 Some(Arc::new(AtomicI64::new(1)))
529 } else {
530 None
531 };
532
533 let now = Instant::now();
534 Self {
535 id: record.id.clone(),
536 service_source: SessionServiceSource::Router {
537 router,
538 factory: service_factory,
539 },
540 notifications_tx,
541 created_at: now,
542 last_accessed: RwLock::new(now),
543 pending_requests: Mutex::new(HashMap::new()),
544 request_id_allocator,
545 protocol_version: RwLock::new(record.protocol_version.clone()),
546 client_info: RwLock::new(record.client_info.clone()),
547 client_capabilities: RwLock::new(record.client_capabilities.clone()),
548 event_counter: AtomicU64::new(0),
549 event_store,
550 initialized_notification_received: std::sync::atomic::AtomicBool::new(true),
553 }
554 }
555
556 fn from_service_restored(
561 service: McpBoxService,
562 record: &crate::session_store::SessionRecord,
563 event_store: Arc<dyn crate::event_store::EventStore>,
564 ) -> Self {
565 let (notifications_tx, _) = broadcast::channel(100);
566 let now = Instant::now();
567 Self {
568 id: record.id.clone(),
569 service_source: SessionServiceSource::Boxed(std::sync::Mutex::new(service)),
570 notifications_tx,
571 created_at: now,
572 last_accessed: RwLock::new(now),
573 pending_requests: Mutex::new(HashMap::new()),
574 request_id_allocator: None,
575 protocol_version: RwLock::new(record.protocol_version.clone()),
576 client_info: RwLock::new(record.client_info.clone()),
577 client_capabilities: RwLock::new(record.client_capabilities.clone()),
578 event_counter: AtomicU64::new(0),
579 event_store,
580 initialized_notification_received: std::sync::atomic::AtomicBool::new(true),
583 }
584 }
585
586 fn make_service(&self) -> McpBoxService {
588 match &self.service_source {
589 SessionServiceSource::Router { router, factory } => (factory)(router.clone()),
590 SessionServiceSource::Boxed(mutex) => mutex.lock().unwrap().clone(),
591 }
592 }
593
594 fn handle_notification(&self, notification: McpNotification) {
600 match &self.service_source {
601 SessionServiceSource::Router { router, .. } => {
602 router.handle_notification(notification);
603 }
604 SessionServiceSource::Boxed(_) => {
605 tracing::debug!(
606 notification = ?notification,
607 "Notification received on service-based session (not forwarded)"
608 );
609 }
610 }
611 }
612
613 fn next_event_id(&self) -> u64 {
618 self.event_counter.fetch_add(1, Ordering::SeqCst)
619 }
620
621 async fn buffer_event(&self, id: u64, data: String) {
628 let record = crate::event_store::EventRecord::new(id, data);
629 if let Err(e) = self.event_store.append(&self.id, record).await {
630 tracing::warn!(session_id = %self.id, event_id = id, error = %e, "Failed to append event to event store");
631 }
632 }
633
634 async fn get_events_after(&self, after_id: u64) -> Vec<crate::event_store::EventRecord> {
640 match self.event_store.replay_after(&self.id, after_id).await {
641 Ok(events) => events,
642 Err(e) => {
643 tracing::warn!(session_id = %self.id, error = %e, "Failed to replay events from event store");
644 Vec::new()
645 }
646 }
647 }
648
649 async fn touch(&self) {
651 *self.last_accessed.write().await = Instant::now();
652 }
653
654 async fn is_expired(&self, ttl: Duration) -> bool {
656 self.last_accessed.read().await.elapsed() > ttl
657 }
658
659 async fn add_pending_request(
661 &self,
662 id: RequestId,
663 response_tx: oneshot::Sender<Result<serde_json::Value>>,
664 ) {
665 let mut pending = self.pending_requests.lock().await;
666 pending.insert(id, PendingRequest { response_tx });
667 }
668
669 async fn complete_pending_request(
671 &self,
672 id: &RequestId,
673 result: Result<serde_json::Value>,
674 ) -> bool {
675 let pending = {
676 let mut pending_requests = self.pending_requests.lock().await;
677 pending_requests.remove(id)
678 };
679
680 match pending {
681 Some(pending) => {
682 let _ = pending.response_tx.send(result);
684 true
685 }
686 None => false,
687 }
688 }
689
690 async fn fail_pending_requests(&self, ids: &[RequestId], message: &str) {
692 let removed = {
693 let mut pending = self.pending_requests.lock().await;
694 ids.iter()
695 .filter_map(|id| pending.remove(id))
696 .collect::<Vec<_>>()
697 };
698
699 for pending in removed {
700 let _ = pending
701 .response_tx
702 .send(Err(Error::Transport(message.to_string())));
703 }
704 }
705}
706
707pub const DEFAULT_SESSION_TTL: Duration = Duration::from_secs(30 * 60);
709
710const DEFAULT_CLEANUP_INTERVAL: Duration = Duration::from_secs(60);
712
713#[derive(Debug, Clone)]
715pub struct SessionConfig {
716 pub ttl: Duration,
718 pub max_sessions: Option<usize>,
720 pub cleanup_interval: Duration,
722 pub strict_initialization: bool,
732}
733
734impl Default for SessionConfig {
735 fn default() -> Self {
736 Self {
737 ttl: DEFAULT_SESSION_TTL,
738 max_sessions: None,
739 cleanup_interval: DEFAULT_CLEANUP_INTERVAL,
740 strict_initialization: true,
741 }
742 }
743}
744
745impl SessionConfig {
746 pub fn with_ttl(ttl: Duration) -> Self {
748 Self {
749 ttl,
750 ..Default::default()
751 }
752 }
753
754 pub fn max_sessions(mut self, max: usize) -> Self {
756 self.max_sessions = Some(max);
757 self
758 }
759
760 pub fn cleanup_interval(mut self, interval: Duration) -> Self {
762 self.cleanup_interval = interval;
763 self
764 }
765
766 pub fn strict_initialization(mut self, enabled: bool) -> Self {
777 self.strict_initialization = enabled;
778 self
779 }
780}
781
782struct SessionRegistry {
792 sessions: RwLock<HashMap<String, Arc<Session>>>,
793 config: SessionConfig,
794 sampling_enabled: bool,
795 persistent: Arc<dyn crate::session_store::SessionStore>,
796 events: Arc<dyn crate::event_store::EventStore>,
797 service_source: ServiceSource,
799 auto_reinit: bool,
804}
805
806impl SessionRegistry {
807 fn new(
808 config: SessionConfig,
809 sampling_enabled: bool,
810 persistent: Arc<dyn crate::session_store::SessionStore>,
811 events: Arc<dyn crate::event_store::EventStore>,
812 service_source: ServiceSource,
813 auto_reinit: bool,
814 ) -> Self {
815 Self {
816 sessions: RwLock::new(HashMap::new()),
817 config,
818 sampling_enabled,
819 persistent,
820 events,
821 service_source,
822 auto_reinit,
823 }
824 }
825
826 async fn record_for(&self, session: &Session) -> crate::session_store::SessionRecord {
828 let protocol_version = session.protocol_version.read().await.clone();
829 let last_accessed = session.last_accessed.read().await;
830 let mut record = crate::session_store::SessionRecord::new(
831 session.id.clone(),
832 protocol_version,
833 self.config.ttl,
834 );
835 record.client_info = session.client_info.read().await.clone();
839 record.client_capabilities = session.client_capabilities.read().await.clone();
840 let now = std::time::SystemTime::now();
842 let created_ago = session.created_at.elapsed();
843 let last_accessed_ago = last_accessed.elapsed();
844 record.created_at = now.checked_sub(created_ago).unwrap_or(now);
845 record.last_accessed = now.checked_sub(last_accessed_ago).unwrap_or(now);
846 record.expires_at = record.last_accessed + self.config.ttl;
847 record
848 }
849
850 async fn persist_new(&self, session: &Session) {
856 let record = self.record_for(session).await;
857 if let Err(e) = self.persistent.create(&mut record.clone()).await {
858 tracing::warn!(session_id = %session.id, error = %e, "Failed to persist session record");
859 }
860 }
861
862 async fn save_record(&self, session: &Session) {
870 let record = self.record_for(session).await;
871 if let Err(e) = self.persistent.save(&record).await {
872 tracing::warn!(session_id = %session.id, error = %e, "Failed to save session record");
873 }
874 }
875
876 async fn create(
877 &self,
878 router: McpRouter,
879 service_factory: ServiceFactory,
880 ) -> Option<Arc<Session>> {
881 let session = {
882 let mut sessions = self.sessions.write().await;
883
884 if let Some(max) = self.config.max_sessions
886 && sessions.len() >= max
887 {
888 tracing::warn!(
889 max_sessions = max,
890 current = sessions.len(),
891 "Session limit reached, rejecting new session"
892 );
893 return None;
894 }
895
896 let session = Arc::new(Session::new(
897 router,
898 self.sampling_enabled,
899 service_factory,
900 self.events.clone(),
901 ));
902 sessions.insert(session.id.clone(), session.clone());
903 tracing::debug!(session_id = %session.id, sampling = self.sampling_enabled, "Created new session");
904 session
905 };
906 self.persist_new(&session).await;
907 Some(session)
908 }
909
910 async fn create_from_service(&self, service: McpBoxService) -> Option<Arc<Session>> {
911 let session = {
912 let mut sessions = self.sessions.write().await;
913
914 if let Some(max) = self.config.max_sessions
915 && sessions.len() >= max
916 {
917 tracing::warn!(
918 max_sessions = max,
919 current = sessions.len(),
920 "Session limit reached, rejecting new session"
921 );
922 return None;
923 }
924
925 let session = Arc::new(Session::from_service(service, self.events.clone()));
926 sessions.insert(session.id.clone(), session.clone());
927 tracing::debug!(session_id = %session.id, "Created new session from service");
928 session
929 };
930 self.persist_new(&session).await;
931 Some(session)
932 }
933
934 async fn create_initialized(
939 &self,
940 router: McpRouter,
941 service_factory: ServiceFactory,
942 ) -> Option<Arc<Session>> {
943 router.session().mark_initialized();
945
946 let session = {
947 let mut sessions = self.sessions.write().await;
948
949 if let Some(max) = self.config.max_sessions
950 && sessions.len() >= max
951 {
952 return None;
953 }
954
955 let session = Arc::new(Session::new(
956 router,
957 self.sampling_enabled,
958 service_factory,
959 self.events.clone(),
960 ));
961 session
966 .initialized_notification_received
967 .store(true, Ordering::Release);
968 sessions.insert(session.id.clone(), session.clone());
969 tracing::debug!(session_id = %session.id, "Created pre-initialized session (optional_sessions)");
970 session
971 };
972 self.persist_new(&session).await;
973 Some(session)
974 }
975
976 async fn create_initialized_from_service(
978 &self,
979 service: McpBoxService,
980 ) -> Option<Arc<Session>> {
981 let session = {
982 let mut sessions = self.sessions.write().await;
983
984 if let Some(max) = self.config.max_sessions
985 && sessions.len() >= max
986 {
987 return None;
988 }
989
990 let session = Arc::new(Session::from_service(service, self.events.clone()));
991 session
994 .initialized_notification_received
995 .store(true, Ordering::Release);
996 sessions.insert(session.id.clone(), session.clone());
997 tracing::debug!(session_id = %session.id, "Created pre-initialized session from service (optional_sessions)");
998 session
999 };
1000 self.persist_new(&session).await;
1001 Some(session)
1002 }
1003
1004 async fn get(&self, id: &str) -> Option<Arc<Session>> {
1005 {
1007 let sessions = self.sessions.read().await;
1008 if let Some(s) = sessions.get(id).cloned() {
1009 s.touch().await;
1010 return Some(s);
1011 }
1012 }
1013
1014 match self.persistent.load(id).await {
1017 Ok(Some(record)) => {
1018 tracing::info!(session_id = %id, "Restoring session from persistent store");
1019 if let Some(session) = self.restore_from_record(record).await {
1020 return Some(session);
1021 }
1022 }
1023 Ok(None) => {}
1024 Err(e) => {
1025 tracing::warn!(session_id = %id, error = %e, "Failed to load session record");
1026 }
1027 }
1028
1029 if self.auto_reinit {
1034 tracing::info!(session_id = %id, "Auto-reinitializing unknown session");
1035 return self.auto_reinitialize(id).await;
1036 }
1037
1038 None
1039 }
1040
1041 async fn restore_from_record(
1049 &self,
1050 record: crate::session_store::SessionRecord,
1051 ) -> Option<Arc<Session>> {
1052 let session = {
1053 let mut sessions = self.sessions.write().await;
1054
1055 if let Some(max) = self.config.max_sessions
1056 && sessions.len() >= max
1057 {
1058 tracing::warn!(
1059 max_sessions = max,
1060 "Session limit reached, cannot restore session"
1061 );
1062 return None;
1063 }
1064
1065 if let Some(existing) = sessions.get(&record.id).cloned() {
1067 existing.touch().await;
1068 return Some(existing);
1069 }
1070
1071 let session: Arc<Session> = match &self.service_source {
1072 ServiceSource::Router { router, factory } => Arc::new(Session::restored(
1073 &record,
1074 router.with_fresh_session(),
1075 self.sampling_enabled,
1076 factory.clone(),
1077 self.events.clone(),
1078 )),
1079 ServiceSource::Service(svc) => {
1080 let service = svc.lock().unwrap().clone();
1081 Arc::new(Session::from_service_restored(
1082 service,
1083 &record,
1084 self.events.clone(),
1085 ))
1086 }
1087 };
1088
1089 sessions.insert(record.id.clone(), session.clone());
1090 tracing::debug!(session_id = %session.id, "Restored session into local registry");
1091 session
1092 };
1093
1094 if let Ok(events) = self.events.replay_after(&record.id, 0).await
1097 && let Some(max_id) = events.iter().map(|e| e.id).max()
1098 {
1099 session
1100 .event_counter
1101 .store(max_id + 1, std::sync::atomic::Ordering::SeqCst);
1102 }
1103
1104 let mut refreshed = record;
1107 refreshed.touch(self.config.ttl);
1108 if let Err(e) = self.persistent.save(&refreshed).await {
1109 tracing::warn!(session_id = %refreshed.id, error = %e, "Failed to refresh restored session record");
1110 }
1111
1112 Some(session)
1113 }
1114
1115 async fn auto_reinitialize(&self, id: &str) -> Option<Arc<Session>> {
1122 let mut record = crate::session_store::SessionRecord::new(
1123 id.to_string(),
1124 LATEST_PROTOCOL_VERSION.to_string(),
1125 self.config.ttl,
1126 );
1127 record.client_info = Some(crate::protocol::Implementation {
1128 name: "auto-recovered".into(),
1129 version: "unknown".into(),
1130 title: None,
1131 description: None,
1132 icons: None,
1133 website_url: None,
1134 meta: None,
1135 });
1136 record.client_capabilities = Some(crate::protocol::ClientCapabilities::default());
1137
1138 if let Err(e) = self.persistent.create(&mut record).await {
1141 tracing::warn!(session_id = %id, error = %e, "Failed to persist auto-reinitialized session");
1142 }
1143
1144 self.restore_from_record(record).await
1145 }
1146
1147 async fn remove(&self, id: &str) -> bool {
1148 let removed = {
1149 let mut sessions = self.sessions.write().await;
1150 sessions.remove(id).is_some()
1151 };
1152 if removed {
1153 tracing::debug!(session_id = %id, "Removed session");
1154 if let Err(e) = self.persistent.delete(id).await {
1155 tracing::warn!(session_id = %id, error = %e, "Failed to delete session record");
1156 }
1157 if let Err(e) = self.events.purge_session(id).await {
1158 tracing::warn!(session_id = %id, error = %e, "Failed to purge session events");
1159 }
1160 }
1161 removed
1162 }
1163
1164 async fn broadcast_to_all(&self, json: &str) {
1171 let sessions = self.sessions.read().await;
1172 for session in sessions.values() {
1173 let _ = session.notifications_tx.send(json.to_string());
1174 }
1175 }
1176
1177 async fn cleanup_expired(&self) -> usize {
1179 let expired = {
1180 let mut sessions = self.sessions.write().await;
1181 let ttl = self.config.ttl;
1182
1183 let mut expired = Vec::new();
1184 for (id, session) in sessions.iter() {
1185 if session.is_expired(ttl).await {
1186 expired.push(id.clone());
1187 }
1188 }
1189
1190 for id in &expired {
1191 sessions.remove(id);
1192 tracing::debug!(session_id = %id, "Expired session removed");
1193 }
1194
1195 if !expired.is_empty() {
1196 tracing::info!(
1197 expired_count = expired.len(),
1198 remaining = sessions.len(),
1199 "Session cleanup completed"
1200 );
1201 }
1202 expired
1203 };
1204
1205 for id in &expired {
1206 if let Err(e) = self.persistent.delete(id).await {
1207 tracing::warn!(session_id = %id, error = %e, "Failed to delete expired session record");
1208 }
1209 if let Err(e) = self.events.purge_session(id).await {
1210 tracing::warn!(session_id = %id, error = %e, "Failed to purge expired session events");
1211 }
1212 }
1213
1214 expired.len()
1215 }
1216}
1217
1218#[derive(Debug, Clone)]
1222pub struct SessionInfo {
1223 pub id: String,
1225 pub created_at: Duration,
1227 pub last_activity: Duration,
1229}
1230
1231#[derive(Clone)]
1256pub struct SessionHandle {
1257 store: Arc<SessionRegistry>,
1258 #[cfg(feature = "stateless")]
1259 modern_subscriptions: Arc<ModernSubscriptionRegistry>,
1260}
1261
1262impl SessionHandle {
1263 pub async fn session_count(&self) -> usize {
1265 self.store.sessions.read().await.len()
1266 }
1267
1268 pub async fn list_sessions(&self) -> Vec<SessionInfo> {
1270 let sessions = self.store.sessions.read().await;
1271 let mut infos = Vec::with_capacity(sessions.len());
1272 for session in sessions.values() {
1273 let last_accessed = session.last_accessed.read().await;
1274 infos.push(SessionInfo {
1275 id: session.id.clone(),
1276 created_at: session.created_at.elapsed(),
1277 last_activity: last_accessed.elapsed(),
1278 });
1279 }
1280 infos
1281 }
1282
1283 pub async fn terminate_session(&self, id: &str) -> bool {
1285 self.store.remove(id).await
1286 }
1287
1288 #[cfg(feature = "stateless")]
1290 pub fn subscription_count(&self) -> usize {
1291 self.modern_subscriptions.len()
1292 }
1293
1294 #[cfg(feature = "stateless")]
1299 pub fn close_subscriptions(&self) -> usize {
1300 self.modern_subscriptions.close_all()
1301 }
1302}
1303
1304#[cfg(feature = "stateless")]
1305impl AppState {
1306 fn tasks_extension_enabled(&self) -> bool {
1312 match &self.service_source {
1313 ServiceSource::Router { router, .. } => router.final_tasks_enabled(),
1314 ServiceSource::Service(_) => false,
1315 }
1316 }
1317}
1318
1319#[derive(Clone)]
1321enum ServiceSource {
1322 Router {
1324 router: McpRouter,
1325 factory: ServiceFactory,
1326 },
1327 Service(Arc<std::sync::Mutex<McpBoxService>>),
1330}
1331
1332struct AppState {
1334 service_source: ServiceSource,
1336 protocol_support: ProtocolSupport,
1338 sessions: Arc<SessionRegistry>,
1340 validate_origin: bool,
1342 allowed_origins: Vec<String>,
1344 validate_host: bool,
1346 allowed_hosts: Vec<String>,
1348 optional_sessions: bool,
1350 strict_initialization: bool,
1353 #[cfg(feature = "stateless")]
1355 stateless_config: Option<crate::stateless::StatelessConfig>,
1356 #[cfg(feature = "stateless")]
1359 stamp_server_info: bool,
1360 #[cfg(feature = "stateless")]
1362 modern_subscriptions: Arc<ModernSubscriptionRegistry>,
1363 sse_responses: bool,
1365 max_body_size: usize,
1367}
1368
1369#[cfg(feature = "stateless")]
1370struct ModernSubscription {
1371 subscription_id: RequestId,
1372 filter: SubscriptionFilter,
1373 tx: mpsc::UnboundedSender<String>,
1374}
1375
1376#[cfg(feature = "stateless")]
1381struct ModernSubscriptionRegistry {
1382 next_key: AtomicU64,
1383 subscriptions: std::sync::Mutex<HashMap<u64, ModernSubscription>>,
1384 server_info: Option<Implementation>,
1385}
1386
1387#[cfg(feature = "stateless")]
1388impl ModernSubscriptionRegistry {
1389 fn new(server_info: Option<Implementation>) -> Self {
1390 Self {
1391 next_key: AtomicU64::new(0),
1392 subscriptions: std::sync::Mutex::new(HashMap::new()),
1393 server_info,
1394 }
1395 }
1396
1397 fn register(
1398 self: &Arc<Self>,
1399 subscription_id: RequestId,
1400 filter: SubscriptionFilter,
1401 ) -> (mpsc::UnboundedReceiver<String>, ModernSubscriptionGuard) {
1402 let key = self.next_key.fetch_add(1, Ordering::Relaxed);
1403 let (tx, rx) = mpsc::unbounded_channel();
1404 self.subscriptions.lock().unwrap().insert(
1405 key,
1406 ModernSubscription {
1407 subscription_id,
1408 filter,
1409 tx,
1410 },
1411 );
1412 (
1413 rx,
1414 ModernSubscriptionGuard {
1415 key,
1416 registry: self.clone(),
1417 },
1418 )
1419 }
1420
1421 fn publish(&self, notification: &ServerNotification) -> bool {
1424 let subscription_scoped = matches!(
1425 notification,
1426 ServerNotification::ResourceUpdated { .. }
1427 | ServerNotification::ResourcesListChanged
1428 | ServerNotification::ToolsListChanged
1429 | ServerNotification::PromptsListChanged
1430 | ServerNotification::FinalTaskStatusChanged(_)
1431 );
1432 if !subscription_scoped {
1433 return false;
1434 }
1435
1436 let mut subscriptions = self.subscriptions.lock().unwrap();
1437 tracing::trace!(
1438 active_subscriptions = subscriptions.len(),
1439 notification = ?notification,
1440 "Routing final-protocol subscription notification"
1441 );
1442 subscriptions.retain(|_, subscription| {
1443 if subscription_matches(notification, &subscription.filter)
1444 && let Some(json) =
1445 tagged_subscription_notification(notification, &subscription.subscription_id)
1446 {
1447 return subscription.tx.send(json).is_ok();
1448 }
1449 !subscription.tx.is_closed()
1450 });
1451 true
1452 }
1453
1454 fn len(&self) -> usize {
1455 self.subscriptions.lock().unwrap().len()
1456 }
1457
1458 fn close_all(&self) -> usize {
1460 let subscriptions = {
1461 let mut active = self.subscriptions.lock().unwrap();
1462 active
1463 .drain()
1464 .map(|(_, subscription)| subscription)
1465 .collect::<Vec<_>>()
1466 };
1467 let count = subscriptions.len();
1468 for subscription in subscriptions {
1469 let response = subscription_complete_response(
1470 subscription.subscription_id,
1471 self.server_info.clone(),
1472 );
1473 if let Ok(json) = serde_json::to_string(&response) {
1474 let _ = subscription.tx.send(json);
1475 }
1476 }
1477 count
1478 }
1479}
1480
1481#[cfg(feature = "stateless")]
1482impl Default for ModernSubscriptionRegistry {
1483 fn default() -> Self {
1484 Self::new(None)
1485 }
1486}
1487
1488#[cfg(feature = "stateless")]
1489struct ModernSubscriptionGuard {
1490 key: u64,
1491 registry: Arc<ModernSubscriptionRegistry>,
1492}
1493
1494#[cfg(feature = "stateless")]
1495impl Drop for ModernSubscriptionGuard {
1496 fn drop(&mut self) {
1497 self.registry
1498 .subscriptions
1499 .lock()
1500 .unwrap()
1501 .remove(&self.key);
1502 }
1503}
1504
1505#[cfg(feature = "oauth")]
1510#[derive(Clone)]
1511pub(crate) struct OAuthConfig {
1512 pub(crate) metadata: crate::oauth::ProtectedResourceMetadata,
1514}
1515
1516pub struct HttpTransport {
1533 service_source: ServiceSource,
1534 protocol_support: ProtocolSupport,
1535 validate_origin: bool,
1536 allowed_origins: Vec<String>,
1537 validate_host: bool,
1538 allowed_hosts: Vec<String>,
1539 session_config: SessionConfig,
1540 sampling_enabled: bool,
1541 optional_sessions: bool,
1542 session_store: Arc<dyn crate::session_store::SessionStore>,
1543 event_store: Arc<dyn crate::event_store::EventStore>,
1544 auto_reinit_sessions: bool,
1545 external_notifications: Option<NotificationReceiver>,
1549 #[cfg(feature = "stateless")]
1550 stateless_config: Option<crate::stateless::StatelessConfig>,
1551 #[cfg(feature = "stateless")]
1556 stamp_server_info: bool,
1557 #[cfg(feature = "oauth")]
1558 oauth_config: Option<OAuthConfig>,
1559 sse_responses: bool,
1563 max_body_size: usize,
1567}
1568
1569impl HttpTransport {
1570 pub fn new(router: McpRouter) -> Self {
1574 Self {
1575 service_source: ServiceSource::Router {
1576 router,
1577 factory: identity_factory(),
1578 },
1579 protocol_support: ProtocolSupport::default(),
1580 validate_origin: true,
1581 allowed_origins: vec![],
1582 validate_host: true,
1583 allowed_hosts: vec![],
1584 session_config: SessionConfig::default(),
1585 sampling_enabled: false,
1586 optional_sessions: true,
1587 session_store: Arc::new(crate::session_store::MemorySessionStore::new()),
1588 event_store: Arc::new(crate::event_store::MemoryEventStore::new()),
1589 auto_reinit_sessions: false,
1590 external_notifications: None,
1591 #[cfg(feature = "stateless")]
1592 stateless_config: None,
1593 #[cfg(feature = "stateless")]
1594 stamp_server_info: true,
1595 #[cfg(feature = "oauth")]
1596 oauth_config: None,
1597 sse_responses: false,
1598 max_body_size: DEFAULT_MAX_BODY_SIZE,
1599 }
1600 }
1601
1602 pub fn from_service<S>(service: S) -> Self
1625 where
1626 S: tower::Service<
1627 RouterRequest,
1628 Response = RouterResponse,
1629 Error = std::convert::Infallible,
1630 > + Clone
1631 + Send
1632 + 'static,
1633 S::Future: Send,
1634 {
1635 Self {
1636 service_source: ServiceSource::Service(Arc::new(std::sync::Mutex::new(
1637 BoxCloneService::new(service),
1638 ))),
1639 protocol_support: ProtocolSupport::default(),
1640 validate_origin: true,
1641 allowed_origins: vec![],
1642 validate_host: true,
1643 allowed_hosts: vec![],
1644 session_config: SessionConfig::default(),
1645 sampling_enabled: false,
1646 optional_sessions: true,
1647 session_store: Arc::new(crate::session_store::MemorySessionStore::new()),
1648 event_store: Arc::new(crate::event_store::MemoryEventStore::new()),
1649 auto_reinit_sessions: false,
1650 external_notifications: None,
1651 #[cfg(feature = "stateless")]
1652 stateless_config: None,
1653 #[cfg(feature = "stateless")]
1654 stamp_server_info: true,
1655 #[cfg(feature = "oauth")]
1656 oauth_config: None,
1657 sse_responses: false,
1658 max_body_size: DEFAULT_MAX_BODY_SIZE,
1659 }
1660 }
1661
1662 pub fn with_notifications(router: McpRouter, notification_rx: NotificationReceiver) -> Self {
1704 Self {
1705 external_notifications: Some(notification_rx),
1706 ..Self::new(router)
1707 }
1708 }
1709
1710 pub fn external_notifications(mut self, notification_rx: NotificationReceiver) -> Self {
1717 self.external_notifications = Some(notification_rx);
1718 self
1719 }
1720
1721 pub fn with_sampling(mut self) -> Self {
1760 self.sampling_enabled = true;
1761 self
1762 }
1763
1764 pub fn require_sessions(mut self) -> Self {
1790 self.optional_sessions = false;
1791 self
1792 }
1793
1794 pub fn protocol_support(mut self, support: ProtocolSupport) -> Self {
1800 self.protocol_support = support;
1801 self
1802 }
1803
1804 pub fn protocol_versions<I, S>(
1809 mut self,
1810 versions: I,
1811 ) -> std::result::Result<Self, ProtocolSupportError>
1812 where
1813 I: IntoIterator<Item = S>,
1814 S: Into<String>,
1815 {
1816 self.protocol_support = ProtocolSupport::try_new(versions)?;
1817 Ok(self)
1818 }
1819
1820 pub fn sse_responses(mut self, enabled: bool) -> Self {
1854 self.sse_responses = enabled;
1855 self
1856 }
1857
1858 #[cfg(feature = "stateless")]
1881 pub fn stamp_server_info(mut self, enabled: bool) -> Self {
1882 self.stamp_server_info = enabled;
1883 self
1884 }
1885
1886 pub fn max_body_size(mut self, bytes: usize) -> Self {
1918 self.max_body_size = bytes;
1919 self
1920 }
1921
1922 #[cfg(feature = "stateless")]
1965 pub fn stateless(mut self, config: crate::stateless::StatelessConfig) -> Self {
1966 self.stateless_config = Some(config);
1967 self
1968 }
1969
1970 pub fn disable_origin_validation(mut self) -> Self {
1972 self.validate_origin = false;
1973 self
1974 }
1975
1976 pub fn allowed_origins(mut self, origins: Vec<String>) -> Self {
1978 self.allowed_origins = origins;
1979 self
1980 }
1981
1982 pub fn disable_host_validation(mut self) -> Self {
1990 self.validate_host = false;
1991 self
1992 }
1993
1994 pub fn allowed_hosts(mut self, hosts: Vec<String>) -> Self {
2006 self.allowed_hosts = hosts;
2007 self
2008 }
2009
2010 pub fn session_config(mut self, config: SessionConfig) -> Self {
2012 self.session_config = config;
2013 self
2014 }
2015
2016 pub fn session_ttl(mut self, ttl: Duration) -> Self {
2018 self.session_config.ttl = ttl;
2019 self
2020 }
2021
2022 pub fn max_sessions(mut self, max: usize) -> Self {
2024 self.session_config.max_sessions = Some(max);
2025 self
2026 }
2027
2028 pub fn session_store(mut self, store: Arc<dyn crate::session_store::SessionStore>) -> Self {
2052 self.session_store = store;
2053 self
2054 }
2055
2056 pub fn event_store(mut self, store: Arc<dyn crate::event_store::EventStore>) -> Self {
2081 self.event_store = store;
2082 self
2083 }
2084
2085 pub fn auto_reinitialize_sessions(mut self, enabled: bool) -> Self {
2113 self.auto_reinit_sessions = enabled;
2114 self
2115 }
2116
2117 #[cfg(feature = "oauth")]
2138 pub fn oauth(mut self, metadata: crate::oauth::ProtectedResourceMetadata) -> Self {
2139 self.oauth_config = Some(OAuthConfig { metadata });
2140 self
2141 }
2142
2143 #[cfg(feature = "oauth")]
2155 pub fn into_oauth_router<V>(
2156 self,
2157 validator: V,
2158 metadata: crate::oauth::ProtectedResourceMetadata,
2159 policy: crate::oauth::ScopePolicy,
2160 ) -> std::result::Result<Router, crate::oauth::ProtectedResourceMetadataError>
2161 where
2162 V: crate::oauth::TokenValidator,
2163 {
2164 let (router, _) = self.into_oauth_router_with_handle(validator, metadata, policy)?;
2165 Ok(router)
2166 }
2167
2168 #[cfg(feature = "oauth")]
2172 pub fn into_oauth_router_with_handle<V>(
2173 self,
2174 validator: V,
2175 metadata: crate::oauth::ProtectedResourceMetadata,
2176 policy: crate::oauth::ScopePolicy,
2177 ) -> std::result::Result<(Router, SessionHandle), crate::oauth::ProtectedResourceMetadataError>
2178 where
2179 V: crate::oauth::TokenValidator,
2180 {
2181 metadata.validate()?;
2182 let oauth_layer =
2183 crate::oauth::OAuthLayer::new(validator, metadata.clone()).scope_policy(policy.clone());
2184 let transport = self
2185 .layer(crate::oauth::ScopeEnforcementLayer::new(policy))
2186 .oauth(metadata);
2187 let (router, handle) = transport.into_router_with_handle();
2188 Ok((router.layer(oauth_layer), handle))
2189 }
2190
2191 #[cfg(feature = "oauth")]
2196 pub fn into_oauth_router_at<V>(
2197 self,
2198 path: &str,
2199 validator: V,
2200 metadata: crate::oauth::ProtectedResourceMetadata,
2201 policy: crate::oauth::ScopePolicy,
2202 ) -> std::result::Result<Router, crate::oauth::ProtectedResourceMetadataError>
2203 where
2204 V: crate::oauth::TokenValidator,
2205 {
2206 let (router, _) =
2207 self.into_oauth_router_at_with_handle(path, validator, metadata, policy)?;
2208 Ok(router)
2209 }
2210
2211 #[cfg(feature = "oauth")]
2213 pub fn into_oauth_router_at_with_handle<V>(
2214 self,
2215 path: &str,
2216 validator: V,
2217 metadata: crate::oauth::ProtectedResourceMetadata,
2218 policy: crate::oauth::ScopePolicy,
2219 ) -> std::result::Result<(Router, SessionHandle), crate::oauth::ProtectedResourceMetadataError>
2220 where
2221 V: crate::oauth::TokenValidator,
2222 {
2223 metadata.validate()?;
2224 let oauth_layer =
2225 crate::oauth::OAuthLayer::new(validator, metadata.clone()).scope_policy(policy.clone());
2226 let transport = self
2227 .layer(crate::oauth::ScopeEnforcementLayer::new(policy))
2228 .oauth(metadata);
2229 let (router, handle) = transport.into_router_at_with_handle(path);
2230 Ok((router.layer(oauth_layer), handle))
2231 }
2232
2233 pub fn layer<L>(mut self, layer: L) -> Self
2240 where
2241 L: tower::Layer<McpRouter> + Send + Sync + 'static,
2242 L::Service:
2243 tower::Service<RouterRequest, Response = RouterResponse> + Clone + Send + 'static,
2244 <L::Service as tower::Service<RouterRequest>>::Error: std::fmt::Display + Send,
2245 <L::Service as tower::Service<RouterRequest>>::Future: Send,
2246 {
2247 match &mut self.service_source {
2248 ServiceSource::Router { factory, .. } => {
2249 *factory = Arc::new(move |router: McpRouter| {
2250 let annotations = router.tool_annotations_map();
2251 let wrapped = layer.layer(router);
2252 tower::util::BoxCloneService::new(InjectAnnotations::new(
2253 CatchError::new(wrapped),
2254 annotations,
2255 ))
2256 });
2257 }
2258 ServiceSource::Service(_) => {
2259 panic!(
2260 "layer() cannot be used with from_service() — \
2261 wrap the service with middleware before passing it in"
2262 );
2263 }
2264 }
2265 self
2266 }
2267
2268 fn build_state(&self) -> Arc<AppState> {
2269 #[cfg(feature = "stateless")]
2270 let modern_subscriptions = Arc::new(ModernSubscriptionRegistry::new(
2271 match &self.service_source {
2272 ServiceSource::Router { router, .. } if self.stamp_server_info => {
2273 Some(router.implementation())
2274 }
2275 _ => None,
2276 },
2277 ));
2278
2279 #[cfg(feature = "stateless")]
2284 let service_source = match &self.service_source {
2285 ServiceSource::Router { router, factory } => {
2286 let (tx, mut rx) = notification_channel(256);
2287 let direct_subscriptions = modern_subscriptions.clone();
2288 router.attach_modern_notification_sink(Arc::new(move |notification| {
2289 direct_subscriptions.publish(notification)
2290 }));
2291 let subscriptions = modern_subscriptions.clone();
2292 tokio::spawn(async move {
2293 while let Some(notification) = rx.recv().await {
2294 subscriptions.publish(¬ification);
2295 }
2296 });
2297 ServiceSource::Router {
2298 router: router.clone().with_notification_sender(tx),
2299 factory: factory.clone(),
2300 }
2301 }
2302 ServiceSource::Service(service) => ServiceSource::Service(service.clone()),
2303 };
2304 #[cfg(not(feature = "stateless"))]
2305 let service_source = self.service_source.clone();
2306
2307 let sessions = Arc::new(SessionRegistry::new(
2308 self.session_config.clone(),
2309 self.sampling_enabled,
2310 self.session_store.clone(),
2311 self.event_store.clone(),
2312 service_source.clone(),
2313 self.auto_reinit_sessions,
2314 ));
2315
2316 let cleanup_sessions = sessions.clone();
2318 let cleanup_interval = self.session_config.cleanup_interval;
2319 tokio::spawn(async move {
2320 loop {
2321 tokio::time::sleep(cleanup_interval).await;
2322 cleanup_sessions.cleanup_expired().await;
2323 }
2324 });
2325
2326 Arc::new(AppState {
2327 service_source,
2328 protocol_support: self.protocol_support.clone(),
2329 sessions,
2330 validate_origin: self.validate_origin,
2331 allowed_origins: self.allowed_origins.clone(),
2332 validate_host: self.validate_host,
2333 allowed_hosts: self.allowed_hosts.clone(),
2334 optional_sessions: self.optional_sessions,
2335 strict_initialization: self.session_config.strict_initialization,
2336 #[cfg(feature = "stateless")]
2337 stateless_config: self.stateless_config.clone(),
2338 #[cfg(feature = "stateless")]
2339 stamp_server_info: self.stamp_server_info,
2340 #[cfg(feature = "stateless")]
2341 modern_subscriptions,
2342 sse_responses: self.sse_responses,
2343 max_body_size: self.max_body_size,
2344 })
2345 }
2346
2347 pub fn into_router(self) -> Router {
2349 let (router, _handle) = self.into_router_with_handle();
2350 router
2351 }
2352
2353 pub fn into_router_with_handle(mut self) -> (Router, SessionHandle) {
2366 let external_rx = self.external_notifications.take();
2367 let state = self.build_state();
2368 let handle = SessionHandle {
2369 store: state.sessions.clone(),
2370 #[cfg(feature = "stateless")]
2371 modern_subscriptions: state.modern_subscriptions.clone(),
2372 };
2373
2374 spawn_external_notification_fanout(
2375 external_rx,
2376 state.sessions.clone(),
2377 #[cfg(feature = "stateless")]
2378 state.modern_subscriptions.clone(),
2379 );
2380
2381 let router = Router::new()
2382 .route("/", post(handle_post))
2383 .route("/", get(handle_get))
2384 .route("/", delete(handle_delete))
2385 .route("/health", get(handle_health))
2386 .with_state(state);
2387
2388 #[cfg(feature = "oauth")]
2389 let router = self.add_oauth_route(router, "");
2390
2391 (router, handle)
2392 }
2393
2394 pub fn into_router_at(self, path: &str) -> Router {
2396 let (router, _handle) = self.into_router_at_with_handle(path);
2397 router
2398 }
2399
2400 pub fn into_router_at_with_handle(mut self, path: &str) -> (Router, SessionHandle) {
2403 let external_rx = self.external_notifications.take();
2404 let state = self.build_state();
2405 let handle = SessionHandle {
2406 store: state.sessions.clone(),
2407 #[cfg(feature = "stateless")]
2408 modern_subscriptions: state.modern_subscriptions.clone(),
2409 };
2410
2411 spawn_external_notification_fanout(
2412 external_rx,
2413 state.sessions.clone(),
2414 #[cfg(feature = "stateless")]
2415 state.modern_subscriptions.clone(),
2416 );
2417
2418 let mcp_router = Router::new()
2419 .route("/", post(handle_post))
2420 .route("/", get(handle_get))
2421 .route("/", delete(handle_delete))
2422 .route("/health", get(handle_health))
2423 .with_state(state);
2424
2425 let router = Router::new().nest(path, mcp_router);
2426
2427 #[cfg(feature = "oauth")]
2428 let router = self.add_oauth_route(router, path);
2429
2430 (router, handle)
2431 }
2432
2433 pub async fn serve(self, addr: &str) -> Result<()> {
2437 let listener = tokio::net::TcpListener::bind(addr)
2438 .await
2439 .map_err(|e| Error::Transport(format!("Failed to bind to {}: {}", addr, e)))?;
2440
2441 tracing::info!("MCP HTTP transport listening on {}", addr);
2442
2443 let router = self.into_router();
2444 axum::serve(listener, router)
2445 .await
2446 .map_err(|e| Error::Transport(format!("Server error: {}", e)))?;
2447
2448 Ok(())
2449 }
2450
2451 #[cfg(feature = "oauth")]
2453 fn add_oauth_route(&self, router: Router, _base_path: &str) -> Router {
2454 if let Some(ref config) = self.oauth_config {
2455 let metadata = config.metadata.clone();
2456 let well_known_path =
2457 crate::oauth::ProtectedResourceMetadata::well_known_path_for_resource(
2458 &metadata.resource,
2459 )
2460 .unwrap_or_else(|_| {
2461 crate::oauth::ProtectedResourceMetadata::well_known_path().to_string()
2462 });
2463 router.route(
2464 &well_known_path,
2465 get(move || {
2466 let m = metadata.clone();
2467 async move { axum::Json(m) }
2468 }),
2469 )
2470 } else {
2471 router
2472 }
2473 }
2474}
2475
2476fn spawn_external_notification_fanout(
2483 rx: Option<NotificationReceiver>,
2484 sessions: Arc<SessionRegistry>,
2485 #[cfg(feature = "stateless")] modern_subscriptions: Arc<ModernSubscriptionRegistry>,
2486) {
2487 let Some(mut rx) = rx else {
2488 return;
2489 };
2490 tokio::spawn(async move {
2491 while let Some(notification) = rx.recv().await {
2492 #[cfg(feature = "stateless")]
2493 modern_subscriptions.publish(¬ification);
2494 if let Some(json) = crate::transport::stdio::serialize_notification(¬ification) {
2495 sessions.broadcast_to_all(&json).await;
2496 }
2497 }
2498 tracing::debug!("External notification channel closed; fan-out task exiting");
2499 });
2500}
2501
2502fn is_localhost_origin(origin: &str) -> bool {
2503 if let Some(rest) = origin
2505 .strip_prefix("http://")
2506 .or_else(|| origin.strip_prefix("https://"))
2507 {
2508 is_localhost_host(rest)
2509 } else {
2510 false
2511 }
2512}
2513
2514fn is_localhost_host(host: &str) -> bool {
2519 let host_only = if host.starts_with('[') {
2520 host.split(']')
2522 .next()
2523 .unwrap_or(host)
2524 .trim_start_matches('[')
2525 } else {
2526 host.split(':').next().unwrap_or(host)
2528 };
2529 matches!(host_only, "localhost" | "127.0.0.1" | "::1")
2530}
2531
2532fn effective_host<'a>(headers: &'a HeaderMap, uri: &'a axum::http::Uri) -> Option<&'a str> {
2539 if let Some(value) = headers.get(header::HOST)
2540 && let Ok(s) = value.to_str()
2541 {
2542 return Some(s);
2543 }
2544 uri.authority().map(|a| a.as_str())
2545}
2546
2547fn validate_host(headers: &HeaderMap, uri: &axum::http::Uri, state: &AppState) -> Option<Response> {
2551 if !state.validate_host {
2552 return None;
2553 }
2554
2555 let Some(host) = effective_host(headers, uri) else {
2556 if state.allowed_hosts.is_empty() {
2557 return None;
2561 }
2562 tracing::warn!("Rejecting request: missing Host header and no :authority fallback");
2563 return Some((StatusCode::BAD_REQUEST, "Missing Host header").into_response());
2564 };
2565
2566 if is_localhost_host(host) {
2567 return None;
2568 }
2569
2570 if state.allowed_hosts.is_empty() {
2571 return None;
2576 }
2577
2578 if state.allowed_hosts.iter().any(|h| h == host) {
2579 return None;
2580 }
2581
2582 tracing::warn!(host = %host, "Rejecting request: Host not in allowlist");
2583 Some((StatusCode::BAD_REQUEST, "Host not allowed").into_response())
2584}
2585
2586fn validate_origin(headers: &HeaderMap, state: &AppState) -> Option<Response> {
2596 if !state.validate_origin {
2597 return None;
2598 }
2599
2600 if let Some(origin) = headers.get(header::ORIGIN) {
2601 let origin_str = origin.to_str().unwrap_or("");
2602
2603 if is_localhost_origin(origin_str) {
2605 return None;
2606 }
2607
2608 if state.allowed_origins.is_empty() {
2610 tracing::warn!(
2611 origin = %origin_str,
2612 "Rejecting request: cross-origin not allowed (no allowlist configured)"
2613 );
2614 return Some(
2615 (StatusCode::FORBIDDEN, "Cross-origin requests not allowed").into_response(),
2616 );
2617 }
2618
2619 if !state
2620 .allowed_origins
2621 .iter()
2622 .any(|o| o == origin_str || o == "*")
2623 {
2624 tracing::warn!(origin = %origin_str, "Rejecting request: Origin not in allowlist");
2625 return Some((StatusCode::FORBIDDEN, "Origin not allowed").into_response());
2626 }
2627 }
2628
2629 None
2630}
2631
2632fn get_session_id(headers: &HeaderMap) -> Option<String> {
2634 headers
2635 .get(MCP_SESSION_ID_HEADER)
2636 .and_then(|v| v.to_str().ok())
2637 .map(|s| s.to_string())
2638}
2639
2640fn get_protocol_version(headers: &HeaderMap) -> Option<String> {
2642 headers
2643 .get(MCP_PROTOCOL_VERSION_HEADER)
2644 .and_then(|v| v.to_str().ok())
2645 .map(|s| s.to_string())
2646}
2647
2648fn get_last_event_id(headers: &HeaderMap) -> Option<u64> {
2650 headers
2651 .get(LAST_EVENT_ID_HEADER)
2652 .and_then(|v| v.to_str().ok())
2653 .and_then(|s| s.parse::<u64>().ok())
2654}
2655
2656fn is_initialize_request(body: &serde_json::Value) -> bool {
2658 body.get("method")
2659 .and_then(|m| m.as_str())
2660 .map(|m| m == "initialize")
2661 .unwrap_or(false)
2662}
2663
2664fn is_response(parsed: &serde_json::Value) -> bool {
2666 parsed.get("method").is_none()
2667 && (parsed.get("result").is_some() || parsed.get("error").is_some())
2668}
2669
2670fn request_tool_input_schema(
2675 service_source: &ServiceSource,
2676 parsed: &serde_json::Value,
2677) -> Option<serde_json::Value> {
2678 if parsed.get("method").and_then(serde_json::Value::as_str) != Some("tools/call") {
2679 return None;
2680 }
2681 let name = parsed
2682 .get("params")
2683 .and_then(serde_json::Value::as_object)
2684 .and_then(|params| params.get("name"))
2685 .and_then(serde_json::Value::as_str)?;
2686 match service_source {
2687 ServiceSource::Router { router, .. } => router.tool_input_schema(name),
2688 ServiceSource::Service(_) => None,
2689 }
2690}
2691
2692fn claims_modern_protocol(headers: &HeaderMap, parsed: &serde_json::Value) -> bool {
2700 get_protocol_version(headers).as_deref() == Some(PROTOCOL_VERSION_2026_07_28)
2701 || parsed
2702 .get("params")
2703 .and_then(serde_json::Value::as_object)
2704 .and_then(|params| params.get("_meta"))
2705 .and_then(serde_json::Value::as_object)
2706 .is_some_and(|meta| meta.contains_key("io.modelcontextprotocol/protocolVersion"))
2707}
2708
2709fn validate_modern_request_meta(
2714 parsed: &serde_json::Value,
2715) -> std::result::Result<String, JsonRpcError> {
2716 let params = parsed
2717 .get("params")
2718 .and_then(serde_json::Value::as_object)
2719 .ok_or_else(|| {
2720 JsonRpcError::invalid_params("Modern requests require a params object containing _meta")
2721 })?;
2722 let meta_value = params
2723 .get("_meta")
2724 .ok_or_else(|| JsonRpcError::invalid_params("Modern requests require a _meta object"))?;
2725 crate::protocol::validate_meta_object(meta_value)
2726 .map_err(|error| JsonRpcError::invalid_params(error.to_string()))?;
2727 let meta = meta_value
2728 .as_object()
2729 .expect("validate_meta_object accepted a JSON object");
2730 let protocol_version = meta
2731 .get("io.modelcontextprotocol/protocolVersion")
2732 .and_then(serde_json::Value::as_str)
2733 .ok_or_else(|| {
2734 JsonRpcError::invalid_params(
2735 "Missing or invalid _meta.io.modelcontextprotocol/protocolVersion",
2736 )
2737 })?;
2738 let client_capabilities = meta
2739 .get("io.modelcontextprotocol/clientCapabilities")
2740 .ok_or_else(|| {
2741 JsonRpcError::invalid_params("Missing _meta.io.modelcontextprotocol/clientCapabilities")
2742 })?;
2743 if !client_capabilities.is_object()
2744 || serde_json::from_value::<ClientCapabilities>(client_capabilities.clone()).is_err()
2745 {
2746 return Err(JsonRpcError::invalid_params(
2747 "Invalid _meta.io.modelcontextprotocol/clientCapabilities",
2748 ));
2749 }
2750
2751 Ok(protocol_version.to_string())
2752}
2753
2754fn is_removed_modern_method(method: &str) -> bool {
2756 matches!(
2757 method,
2758 "initialize"
2759 | "notifications/initialized"
2760 | "ping"
2761 | "logging/setLevel"
2762 | "resources/subscribe"
2763 | "resources/unsubscribe"
2764 | "notifications/roots/list_changed"
2765 )
2766}
2767
2768#[cfg(feature = "stateless")]
2772fn modern_response_status(response: &JsonRpcResponse) -> StatusCode {
2773 let JsonRpcResponse::Error(error) = response else {
2774 return StatusCode::OK;
2775 };
2776 if error.error.code == ErrorCode::MethodNotFound as i32 {
2777 StatusCode::NOT_FOUND
2778 } else if error.error.code == McpErrorCode::MissingRequiredClientCapability.code() {
2779 StatusCode::BAD_REQUEST
2780 } else {
2781 StatusCode::OK
2782 }
2783}
2784
2785fn extract_request_id(parsed: &serde_json::Value) -> Option<RequestId> {
2787 parsed.get("id").and_then(|id| {
2788 if let Some(n) = id.as_i64() {
2789 Some(RequestId::Number(n))
2790 } else {
2791 id.as_str().map(|s| RequestId::String(s.to_string()))
2792 }
2793 })
2794}
2795
2796async fn handle_post(
2798 State(state): State<Arc<AppState>>,
2799 request: axum::extract::Request,
2800) -> Response {
2801 let (parts, body_bytes) = request.into_parts();
2802 let headers = parts.headers;
2803 let uri = parts.uri.clone();
2804
2805 if let Some(resp) = validate_host(&headers, &uri, &state) {
2807 return resp;
2808 }
2809
2810 if let Some(resp) = validate_origin(&headers, &state) {
2812 return resp;
2813 }
2814
2815 if let Some(declared) = headers
2821 .get(header::CONTENT_LENGTH)
2822 .and_then(|v| v.to_str().ok())
2823 .and_then(|v| v.parse::<usize>().ok())
2824 && declared > state.max_body_size
2825 {
2826 return body_too_large_response(state.max_body_size);
2827 }
2828
2829 let body = match axum::body::to_bytes(body_bytes, state.max_body_size).await {
2830 Ok(bytes) => match String::from_utf8(bytes.to_vec()) {
2831 Ok(s) => s,
2832 Err(e) => {
2833 return json_rpc_error_response(
2834 None,
2835 JsonRpcError::parse_error(format!("Invalid UTF-8: {}", e)),
2836 );
2837 }
2838 },
2839 Err(e) if is_length_limit_error(&e) => {
2840 return body_too_large_response(state.max_body_size);
2841 }
2842 Err(e) => {
2843 return json_rpc_error_response(
2844 None,
2845 JsonRpcError::parse_error(format!("Failed to read body: {}", e)),
2846 );
2847 }
2848 };
2849
2850 #[cfg(feature = "oauth")]
2852 let http_extensions = parts.extensions;
2853 #[cfg(not(feature = "oauth"))]
2854 let _ = parts.extensions;
2855
2856 let parsed: serde_json::Value = match serde_json::from_str(&body) {
2858 Ok(v) => v,
2859 Err(e) => {
2860 return json_rpc_error_response(
2861 None,
2862 JsonRpcError::parse_error(format!("Invalid JSON: {}", e)),
2863 );
2864 }
2865 };
2866
2867 if parsed.is_array()
2871 && let Some(version) = get_protocol_version(&headers)
2872 {
2873 let revision = match version.parse::<McpProtocolRevision>() {
2874 Ok(revision) => revision,
2875 Err(_) => {
2876 return json_rpc_error_response(
2877 None,
2878 JsonRpcError::unsupported_protocol_version(
2879 version,
2880 state.protocol_support.versions().iter().map(String::as_str),
2881 ),
2882 );
2883 }
2884 };
2885 if let Err(error) = inspect_runtime_value(
2886 &parsed,
2887 revision,
2888 &state.protocol_support,
2889 McpDirection::ClientToServer,
2890 ) {
2891 let status = if revision == McpProtocolRevision::V2026_07_28 {
2892 StatusCode::BAD_REQUEST
2893 } else {
2894 StatusCode::OK
2895 };
2896 return json_rpc_error_response_with_status(None, error, status);
2897 }
2898 }
2899
2900 let is_init = is_initialize_request(&parsed);
2902 let request_method = parsed
2903 .get("method")
2904 .and_then(|method| method.as_str())
2905 .unwrap_or_default()
2906 .to_string();
2907 let tool_input_schema = request_tool_input_schema(&state.service_source, &parsed);
2908 let modern_request = claims_modern_protocol(&headers, &parsed);
2909
2910 if modern_request {
2916 let id = extract_request_id(&parsed);
2917 let body_version = match validate_modern_request_meta(&parsed) {
2918 Ok(version) => version,
2919 Err(error) => {
2920 return json_rpc_error_response_with_status(id, error, StatusCode::BAD_REQUEST);
2921 }
2922 };
2923
2924 let Some(header_version) = get_protocol_version(&headers) else {
2925 return json_rpc_error_response_with_status(
2926 id,
2927 JsonRpcError::header_mismatch("MCP-Protocol-Version header is required"),
2928 StatusCode::BAD_REQUEST,
2929 );
2930 };
2931 if header_version != body_version {
2932 return json_rpc_error_response_with_status(
2933 id,
2934 JsonRpcError::header_mismatch(format!(
2935 "MCP-Protocol-Version header value {header_version:?} does not match \
2936 request _meta protocol version {body_version:?}"
2937 )),
2938 StatusCode::BAD_REQUEST,
2939 );
2940 }
2941
2942 if !state.protocol_support.contains(&body_version) {
2943 return json_rpc_error_response_with_status(
2944 id,
2945 JsonRpcError::unsupported_protocol_version(
2946 body_version,
2947 state.protocol_support.versions().iter().map(String::as_str),
2948 ),
2949 StatusCode::BAD_REQUEST,
2950 );
2951 }
2952
2953 let revision = match body_version.parse::<McpProtocolRevision>() {
2954 Ok(revision) => revision,
2955 Err(_) => {
2956 return json_rpc_error_response_with_status(
2957 id,
2958 JsonRpcError::unsupported_protocol_version(
2959 body_version,
2960 state.protocol_support.versions().iter().map(String::as_str),
2961 ),
2962 StatusCode::BAD_REQUEST,
2963 );
2964 }
2965 };
2966 if let Err(error) = inspect_runtime_value(
2967 &parsed,
2968 revision,
2969 &state.protocol_support,
2970 McpDirection::ClientToServer,
2971 ) {
2972 return json_rpc_error_response_with_status(id, error, StatusCode::BAD_REQUEST);
2973 }
2974
2975 let sep_2243_mode = super::http_headers::mode_for_version(&body_version);
2976 if let Err(error) = super::http_headers::validate_with_tool_schema(
2977 &headers,
2978 &parsed,
2979 sep_2243_mode,
2980 tool_input_schema.as_ref(),
2981 ) {
2982 tracing::warn!(
2983 mode = ?sep_2243_mode,
2984 version = %body_version,
2985 error = %error.message,
2986 "Rejecting modern request: HTTP header validation failed",
2987 );
2988 return json_rpc_error_response_with_status(id, error, StatusCode::BAD_REQUEST);
2989 }
2990
2991 if is_removed_modern_method(&request_method) {
2992 return json_rpc_error_response_with_status(
2993 id,
2994 JsonRpcError::method_not_found(&request_method),
2995 StatusCode::NOT_FOUND,
2996 );
2997 }
2998 }
2999
3000 #[cfg(feature = "stateless")]
3012 {
3013 let version_in_play: Option<String> = if is_init && !modern_request {
3014 parsed
3017 .get("params")
3018 .and_then(|p| p.get("protocolVersion"))
3019 .and_then(|v| v.as_str())
3020 .map(|s| s.to_string())
3021 } else {
3022 get_protocol_version(&headers)
3026 };
3027
3028 if let Some(ref version) = version_in_play
3029 && is_stateless_protocol_version(version)
3030 && state.protocol_support.contains(version)
3031 && parsed.get("method").and_then(|m| m.as_str()) != Some("subscriptions/listen")
3034 {
3035 if !is_init && (parsed.get("id").is_none() || is_response(&parsed)) {
3037 return StatusCode::ACCEPTED.into_response();
3038 }
3039
3040 let sep_2243_mode = super::http_headers::mode_for_version(version);
3043 if let Err(err) = super::http_headers::validate_with_tool_schema(
3044 &headers,
3045 &parsed,
3046 sep_2243_mode,
3047 tool_input_schema.as_ref(),
3048 ) {
3049 tracing::warn!(
3050 mode = ?sep_2243_mode,
3051 version = %version,
3052 error = %err.message,
3053 "Rejecting stateless request: SEP-2243 header validation failed",
3054 );
3055 let id = extract_request_id(&parsed);
3056 let mut resp = json_rpc_error_response(id, err);
3057 *resp.status_mut() = StatusCode::BAD_REQUEST;
3058 return resp;
3059 }
3060
3061 let request: JsonRpcRequest = match serde_json::from_value(parsed) {
3062 Ok(r) => r,
3063 Err(e) => {
3064 return json_rpc_error_response(
3065 None,
3066 JsonRpcError::parse_error(format!("Invalid request: {}", e)),
3067 );
3068 }
3069 };
3070
3071 let server_identity = match &state.service_source {
3086 ServiceSource::Router { router, .. } if state.stamp_server_info => {
3087 Some(router.implementation())
3088 }
3089 _ => None,
3090 };
3091
3092 let (notif_tx, mut notif_rx) = crate::context::notification_channel(64);
3093 let mut service = match &state.service_source {
3094 ServiceSource::Router { router, factory } => {
3095 let ephemeral = router
3096 .with_fresh_session()
3097 .with_request_notification_sender(notif_tx);
3098 ephemeral.session().mark_initialized();
3099 JsonRpcService::new(factory(ephemeral))
3100 }
3101 ServiceSource::Service(mutex) => JsonRpcService::new(mutex.lock().unwrap().clone()),
3102 };
3103
3104 let mut ext = crate::router::Extensions::new();
3105 ext.insert(state.protocol_support.clone());
3106 #[cfg(feature = "oauth")]
3107 if let Some(claims) = http_extensions.get::<crate::oauth::token::TokenClaims>() {
3108 ext.insert(claims.clone());
3109 }
3110 stash_per_request_meta(&request, &mut ext);
3111
3112 let cancel_token = crate::context::CancellationToken::new();
3122 let mut cancel_guard = CancelOnDisconnect::arm(cancel_token.clone());
3123 ext.insert(cancel_token);
3124
3125 service = service.with_extensions(ext);
3126
3127 let mut call: std::pin::Pin<
3128 Box<dyn std::future::Future<Output = crate::error::Result<JsonRpcResponse>> + Send>,
3129 > = Box::pin(async move {
3130 let mut service = service;
3131 service.call_single(request).await
3132 });
3133
3134 enum FirstOutbound {
3135 Response(crate::error::Result<JsonRpcResponse>),
3136 Notification(crate::context::ServerNotification),
3137 }
3138
3139 let first = loop {
3143 let outbound = tokio::select! {
3144 biased;
3148 maybe = notif_rx.recv() => match maybe {
3149 Some(n) => FirstOutbound::Notification(n),
3150 None => FirstOutbound::Response((&mut call).await),
3151 },
3152 result = &mut call => FirstOutbound::Response(result),
3153 };
3154 match outbound {
3155 FirstOutbound::Notification(notification)
3156 if state.modern_subscriptions.publish(¬ification) =>
3157 {
3158 continue;
3159 }
3160 outbound => break outbound,
3161 }
3162 };
3163
3164 match first {
3165 FirstOutbound::Response(result) => {
3166 while let Ok(notification) = notif_rx.try_recv() {
3170 if state.modern_subscriptions.publish(¬ification) {
3171 continue;
3172 }
3173 let ready_call: std::pin::Pin<
3174 Box<
3175 dyn std::future::Future<
3176 Output = crate::error::Result<JsonRpcResponse>,
3177 > + Send,
3178 >,
3179 > = Box::pin(async move { result });
3180 let mut resp = stateless_sse_with_notifications(
3181 notification,
3182 ready_call,
3183 notif_rx,
3184 StatelessSseContext {
3185 version: version.clone(),
3186 method: request_method.clone(),
3187 cancel_guard,
3188 server_identity,
3189 subscriptions: state.modern_subscriptions.clone(),
3190 },
3191 );
3192 resp.headers_mut().insert(
3193 MCP_PROTOCOL_VERSION_HEADER,
3194 HeaderValue::from_str(version).unwrap(),
3195 );
3196 return resp;
3197 }
3198
3199 cancel_guard.disarm();
3203 let mut response = match result {
3204 Ok(resp) => resp,
3205 Err(e) => {
3206 return json_rpc_error_response(
3207 None,
3208 JsonRpcError::internal_error(e.to_string()),
3209 );
3210 }
3211 };
3212
3213 if is_init
3217 && let JsonRpcResponse::Result(ref mut result) = response
3218 && let Some(pv) = result.result.get_mut("protocolVersion")
3219 {
3220 *pv = serde_json::Value::String(version.clone());
3221 }
3222 apply_protocol_result_fields(&mut response, &request_method, version);
3223 if let Some(ref identity) = server_identity {
3224 stamp_server_info(&mut response, identity);
3225 }
3226
3227 let status = modern_response_status(&response);
3228 let mut resp = if state.sse_responses {
3229 sse_json_response(&response)
3230 } else {
3231 axum::Json(response).into_response()
3232 };
3233 *resp.status_mut() = status;
3234 resp.headers_mut().insert(
3235 MCP_PROTOCOL_VERSION_HEADER,
3236 HeaderValue::from_str(version).unwrap(),
3237 );
3238 return resp;
3240 }
3241 FirstOutbound::Notification(first_notif) => {
3242 let mut resp = stateless_sse_with_notifications(
3243 first_notif,
3244 call,
3245 notif_rx,
3246 StatelessSseContext {
3247 version: version.clone(),
3248 method: request_method.clone(),
3249 cancel_guard,
3250 server_identity,
3251 subscriptions: state.modern_subscriptions.clone(),
3252 },
3253 );
3254 resp.headers_mut().insert(
3255 MCP_PROTOCOL_VERSION_HEADER,
3256 HeaderValue::from_str(version).unwrap(),
3257 );
3258 return resp;
3260 }
3261 }
3262 }
3263 }
3264
3265 #[cfg(feature = "stateless")]
3270 if !is_init && state.stateless_config.is_some() && get_session_id(&headers).is_none() {
3271 let version_from_header = get_protocol_version(&headers);
3272 let params = parsed.get("params").unwrap_or(&parsed);
3273 let version_from_meta = crate::stateless::StatelessRequestMeta::from_params(params)
3274 .and_then(|m| m.protocol_version);
3275
3276 if let Some(version) = version_from_header.or(version_from_meta) {
3277 if let Err(err) = crate::stateless::validate_protocol_version(&version) {
3278 return json_rpc_error_response(None, err);
3279 }
3280
3281 if parsed.get("id").is_none() || is_response(&parsed) {
3283 return StatusCode::ACCEPTED.into_response();
3284 }
3285
3286 let request: JsonRpcRequest = match serde_json::from_value(parsed) {
3287 Ok(r) => r,
3288 Err(e) => {
3289 return json_rpc_error_response(
3290 None,
3291 JsonRpcError::parse_error(format!("Invalid request: {}", e)),
3292 );
3293 }
3294 };
3295
3296 let mut service = match &state.service_source {
3298 ServiceSource::Router { router, factory } => {
3299 let ephemeral = router.with_fresh_session();
3300 ephemeral.session().mark_initialized();
3301 JsonRpcService::new(factory(ephemeral))
3302 }
3303 ServiceSource::Service(mutex) => JsonRpcService::new(mutex.lock().unwrap().clone()),
3304 };
3305
3306 let mut ext = crate::router::Extensions::new();
3307 ext.insert(state.protocol_support.clone());
3308 #[cfg(feature = "oauth")]
3309 if let Some(claims) = http_extensions.get::<crate::oauth::token::TokenClaims>() {
3310 ext.insert(claims.clone());
3311 }
3312 #[cfg(feature = "stateless")]
3313 stash_per_request_meta(&request, &mut ext);
3314 if !ext.is_empty() {
3315 service = service.with_extensions(ext);
3316 }
3317
3318 let mut response = match service.call_single(request).await {
3319 Ok(resp) => resp,
3320 Err(e) => {
3321 return json_rpc_error_response(
3322 None,
3323 JsonRpcError::internal_error(e.to_string()),
3324 );
3325 }
3326 };
3327 apply_protocol_result_fields(&mut response, &request_method, &version);
3328
3329 let mut resp = if state.sse_responses {
3330 sse_json_response(&response)
3331 } else {
3332 axum::Json(response).into_response()
3333 };
3334 resp.headers_mut().insert(
3335 MCP_PROTOCOL_VERSION_HEADER,
3336 HeaderValue::from_str(&version).unwrap(),
3337 );
3338 return resp;
3339 }
3340 }
3341
3342 #[cfg(feature = "stateless")]
3345 if modern_request && request_method == "subscriptions/listen" {
3346 return handle_modern_subscriptions_listen_sse(state, &parsed).await;
3347 }
3348
3349 if !is_init
3353 && let Some(version) = get_protocol_version(&headers)
3354 && !state.protocol_support.contains(&version)
3355 {
3356 return json_rpc_error_response(
3357 extract_request_id(&parsed),
3358 JsonRpcError::unsupported_protocol_version(
3359 version,
3360 state.protocol_support.versions().iter().map(String::as_str),
3361 ),
3362 );
3363 }
3364
3365 let uses_transient_session = !is_init
3366 && !modern_request
3367 && get_session_id(&headers).is_none()
3368 && state.optional_sessions;
3369
3370 let session = if is_init {
3372 let create_result = match &state.service_source {
3374 ServiceSource::Router { router, factory } => {
3375 state
3377 .sessions
3378 .create(router.with_fresh_session(), factory.clone())
3379 .await
3380 }
3381 ServiceSource::Service(mutex) => {
3382 let service = mutex.lock().unwrap().clone();
3383 state.sessions.create_from_service(service).await
3384 }
3385 };
3386 match create_result {
3387 Some(s) => s,
3388 None => {
3389 return (
3390 StatusCode::SERVICE_UNAVAILABLE,
3391 "Maximum session limit reached",
3392 )
3393 .into_response();
3394 }
3395 }
3396 } else if !modern_request && let Some(session_id) = get_session_id(&headers) {
3397 match state.sessions.get(&session_id).await {
3399 Some(s) => s,
3400 None => {
3401 return json_rpc_error_response(
3403 None,
3404 JsonRpcError::session_not_found_with_id(&session_id),
3405 );
3406 }
3407 }
3408 } else if state.optional_sessions {
3409 let create_result = match &state.service_source {
3415 ServiceSource::Router { router, factory } => {
3416 state
3417 .sessions
3418 .create_initialized(router.with_fresh_session(), factory.clone())
3419 .await
3420 }
3421 ServiceSource::Service(mutex) => {
3422 let service = mutex.lock().unwrap().clone();
3423 state
3424 .sessions
3425 .create_initialized_from_service(service)
3426 .await
3427 }
3428 };
3429 match create_result {
3430 Some(s) => s,
3431 None => {
3432 return (
3433 StatusCode::SERVICE_UNAVAILABLE,
3434 "Maximum session limit reached",
3435 )
3436 .into_response();
3437 }
3438 }
3439 } else {
3440 return json_rpc_error_response(None, JsonRpcError::session_required());
3442 };
3443
3444 let session_protocol_version = if uses_transient_session {
3448 let version = state
3449 .protocol_support
3450 .versions()
3451 .iter()
3452 .find(|version| {
3453 crate::protocol::SUPPORTED_PROTOCOL_VERSIONS.contains(&version.as_str())
3454 })
3455 .map_or_else(
3456 || state.protocol_support.preferred().to_string(),
3457 Clone::clone,
3458 );
3459 *session.protocol_version.write().await = version.clone();
3460 version
3461 } else {
3462 session.protocol_version.read().await.clone()
3463 };
3464 let session_revision = match session_protocol_version.parse::<McpProtocolRevision>() {
3465 Ok(revision) => revision,
3466 Err(_) => {
3467 return json_rpc_error_response(
3468 extract_request_id(&parsed),
3469 JsonRpcError::unsupported_protocol_version(
3470 session_protocol_version,
3471 state.protocol_support.versions().iter().map(String::as_str),
3472 ),
3473 );
3474 }
3475 };
3476 if !is_init
3477 && let Err(error) = inspect_runtime_value(
3478 &parsed,
3479 session_revision,
3480 &state.protocol_support,
3481 McpDirection::ClientToServer,
3482 )
3483 {
3484 return json_rpc_error_response(extract_request_id(&parsed), error);
3485 }
3486
3487 if parsed.is_array() {
3488 if state.strict_initialization
3489 && !session
3490 .initialized_notification_received
3491 .load(Ordering::Acquire)
3492 {
3493 return json_rpc_error_response(
3494 None,
3495 JsonRpcError::invalid_request(
3496 "Client must send notifications/initialized before making requests",
3497 ),
3498 );
3499 }
3500
3501 let message: JsonRpcMessage = match serde_json::from_value(parsed) {
3502 Ok(message) => message,
3503 Err(error) => {
3504 return json_rpc_error_response(
3505 None,
3506 JsonRpcError::invalid_request(format!("Invalid request batch: {error}")),
3507 );
3508 }
3509 };
3510
3511 let mut extensions = crate::router::Extensions::new();
3512 extensions.insert(state.protocol_support.clone());
3513 extensions.insert(session_revision);
3514 #[cfg(feature = "oauth")]
3515 if let Some(claims) = http_extensions.get::<crate::oauth::token::TokenClaims>() {
3516 extensions.insert(claims.clone());
3517 }
3518
3519 let mut service = JsonRpcService::new(session.make_service())
3520 .with_extensions(extensions)
3521 .protocol_support(state.protocol_support.clone())
3522 .with_negotiated_protocol_version(&session_protocol_version);
3523 let response = match service.call_message(message).await {
3524 Ok(response) => response,
3525 Err(error) => {
3526 return json_rpc_error_response(
3527 None,
3528 JsonRpcError::internal_error(error.to_string()),
3529 );
3530 }
3531 };
3532 let mut response = axum::Json(response).into_response();
3533 response.headers_mut().insert(
3534 MCP_PROTOCOL_VERSION_HEADER,
3535 HeaderValue::from_str(&session_protocol_version).unwrap(),
3536 );
3537 return response;
3538 }
3539
3540 {
3552 let method_str = parsed.get("method").and_then(|m| m.as_str()).unwrap_or("");
3553 if method_str == "subscriptions/listen" {
3554 let req_id = extract_request_id(&parsed);
3555 let effective_version = if let Some(v) = get_protocol_version(&headers) {
3556 v
3557 } else {
3558 session.protocol_version.read().await.clone()
3559 };
3560 if version_supports_subscriptions_listen(&effective_version, &state.protocol_support) {
3561 return handle_subscriptions_listen_sse(session).await;
3562 } else {
3563 return json_rpc_error_response(
3564 req_id,
3565 JsonRpcError::method_not_found("subscriptions/listen"),
3566 );
3567 }
3568 }
3569 }
3570
3571 let sep_2243_version = if is_init {
3583 match parsed
3584 .get("params")
3585 .and_then(|p| p.get("protocolVersion"))
3586 .and_then(|v| v.as_str())
3587 {
3588 Some(v) => v.to_string(),
3589 None => session.protocol_version.read().await.clone(),
3590 }
3591 } else {
3592 session.protocol_version.read().await.clone()
3593 };
3594 let sep_2243_mode = super::http_headers::mode_for_version(&sep_2243_version);
3595 if let Err(err) = super::http_headers::validate_with_tool_schema(
3596 &headers,
3597 &parsed,
3598 sep_2243_mode,
3599 tool_input_schema.as_ref(),
3600 ) {
3601 tracing::warn!(
3602 mode = ?sep_2243_mode,
3603 version = %sep_2243_version,
3604 error = %err.message,
3605 "Rejecting request: SEP-2243 header validation failed",
3606 );
3607 let id = extract_request_id(&parsed);
3608 let mut resp = json_rpc_error_response(id, err);
3609 *resp.status_mut() = StatusCode::BAD_REQUEST;
3611 return resp;
3612 }
3613
3614 if is_response(&parsed) {
3616 if let Some(id) = extract_request_id(&parsed) {
3617 let result = if let Some(error) = parsed.get("error") {
3618 let code = error.get("code").and_then(|c| c.as_i64()).unwrap_or(-1);
3619 let message = error
3620 .get("message")
3621 .and_then(|m| m.as_str())
3622 .unwrap_or("Unknown error");
3623 Err(Error::Internal(format!(
3624 "Client error ({}): {}",
3625 code, message
3626 )))
3627 } else if let Some(result) = parsed.get("result") {
3628 Ok(result.clone())
3629 } else {
3630 Err(Error::Internal(
3631 "Response has neither result nor error".to_string(),
3632 ))
3633 };
3634
3635 if session.complete_pending_request(&id, result).await {
3636 tracing::debug!(request_id = ?id, "Completed pending request");
3637 } else {
3638 tracing::warn!(request_id = ?id, "Received response for unknown request");
3639 }
3640 }
3641 return StatusCode::ACCEPTED.into_response();
3642 }
3643
3644 if parsed.get("id").is_none() {
3646 if let Ok(notification) = serde_json::from_value::<JsonRpcNotification>(parsed)
3648 && let Ok(mcp_notification) = McpNotification::from_jsonrpc(¬ification)
3649 {
3650 if matches!(&mcp_notification, McpNotification::Initialized) {
3656 session
3657 .initialized_notification_received
3658 .store(true, Ordering::Release);
3659 tracing::debug!(session_id = %session.id, "Received notifications/initialized");
3660 }
3661 session.handle_notification(mcp_notification);
3662 }
3663 return StatusCode::ACCEPTED.into_response();
3664 }
3665
3666 if !is_init
3670 && state.strict_initialization
3671 && !session
3672 .initialized_notification_received
3673 .load(Ordering::Acquire)
3674 {
3675 let id = extract_request_id(&parsed);
3676 tracing::warn!(
3677 session_id = %session.id,
3678 "Rejecting request: notifications/initialized not yet received"
3679 );
3680 return json_rpc_error_response(
3681 id,
3682 JsonRpcError::invalid_request(
3683 "Client must send notifications/initialized before making requests",
3684 ),
3685 );
3686 }
3687
3688 let init_client_metadata: Option<(Option<Implementation>, Option<ClientCapabilities>)> =
3695 if is_init {
3696 let params = parsed.get("params");
3697 let client_info = params
3698 .and_then(|p| p.get("clientInfo"))
3699 .and_then(|v| serde_json::from_value::<Implementation>(v.clone()).ok());
3700 let client_capabilities = params
3701 .and_then(|p| p.get("capabilities"))
3702 .and_then(|v| serde_json::from_value::<ClientCapabilities>(v.clone()).ok());
3703 Some((client_info, client_capabilities))
3704 } else {
3705 None
3706 };
3707
3708 let request: JsonRpcRequest = match serde_json::from_value(parsed) {
3710 Ok(r) => r,
3711 Err(e) => {
3712 return json_rpc_error_response(
3713 None,
3714 JsonRpcError::parse_error(format!("Invalid request: {}", e)),
3715 );
3716 }
3717 };
3718
3719 let mut service = JsonRpcService::new(session.make_service());
3721
3722 #[allow(unused_mut)]
3726 let mut ext = crate::router::Extensions::new();
3727 ext.insert(state.protocol_support.clone());
3728 ext.insert(session_revision);
3729 #[cfg(feature = "oauth")]
3730 if let Some(claims) = http_extensions.get::<crate::oauth::token::TokenClaims>() {
3731 ext.insert(claims.clone());
3732 }
3733 #[cfg(feature = "stateless")]
3734 stash_per_request_meta(&request, &mut ext);
3735
3736 let mut associated_request_rx = if !is_init {
3741 session.request_id_allocator.as_ref().map(|next_id| {
3742 let (request_tx, request_rx) = outgoing_request_channel(32);
3743 let requester: ClientRequesterHandle = Arc::new(
3744 ChannelClientRequester::with_id_allocator(request_tx, next_id.clone()),
3745 );
3746 ext.insert(requester);
3747 request_rx
3748 })
3749 } else {
3750 None
3751 };
3752
3753 if !ext.is_empty() {
3754 service = service.with_extensions(ext);
3755 }
3756
3757 let request_id = request.id.clone();
3758 let mut call: AssociatedCall = Box::pin(async move { service.call_single(request).await });
3759 let mut response = if let Some(mut request_rx) = associated_request_rx.take() {
3760 tokio::select! {
3761 result = &mut call => match result {
3762 Ok(response) => response,
3763 Err(error) => {
3764 return json_rpc_error_response(
3765 Some(request_id),
3766 JsonRpcError::internal_error(error.to_string()),
3767 );
3768 }
3769 },
3770 outgoing = request_rx.recv() => {
3771 match outgoing {
3772 Some(outgoing) => {
3773 let negotiated_version = session.protocol_version.read().await.clone();
3774 return associated_request_sse_response(
3775 session,
3776 call,
3777 request_rx,
3778 outgoing,
3779 request_id,
3780 request_method,
3781 negotiated_version,
3782 );
3783 }
3784 None => match call.await {
3785 Ok(response) => response,
3786 Err(error) => {
3787 return json_rpc_error_response(
3788 Some(request_id),
3789 JsonRpcError::internal_error(error.to_string()),
3790 );
3791 }
3792 },
3793 }
3794 }
3795 }
3796 } else {
3797 match call.await {
3798 Ok(response) => response,
3799 Err(error) => {
3800 return json_rpc_error_response(
3801 Some(request_id),
3802 JsonRpcError::internal_error(error.to_string()),
3803 );
3804 }
3805 }
3806 };
3807
3808 if is_init && let JsonRpcResponse::Result(ref result) = response {
3814 if let Some(version) = result
3815 .result
3816 .get("protocolVersion")
3817 .and_then(|v| v.as_str())
3818 {
3819 *session.protocol_version.write().await = version.to_string();
3820 }
3821 if let Some((client_info, client_capabilities)) = init_client_metadata {
3822 *session.client_info.write().await = client_info;
3823 *session.client_capabilities.write().await = client_capabilities;
3824 }
3825 state.sessions.save_record(&session).await;
3826 }
3827
3828 let negotiated_version = session.protocol_version.read().await.clone();
3829 let response_version = if request_method == "server/discover"
3830 && state.protocol_support.contains(PROTOCOL_VERSION_2026_07_28)
3831 {
3832 PROTOCOL_VERSION_2026_07_28
3833 } else {
3834 &negotiated_version
3835 };
3836 apply_protocol_result_fields(&mut response, &request_method, response_version);
3837
3838 let mut resp = if state.sse_responses {
3840 sse_json_response(&response)
3841 } else {
3842 axum::Json(response).into_response()
3843 };
3844
3845 if is_init {
3846 resp.headers_mut().insert(
3847 MCP_SESSION_ID_HEADER,
3848 HeaderValue::from_str(&session.id).unwrap(),
3849 );
3850 }
3851
3852 resp.headers_mut().insert(
3854 MCP_PROTOCOL_VERSION_HEADER,
3855 HeaderValue::from_str(&negotiated_version).unwrap(),
3856 );
3857
3858 resp
3859}
3860
3861fn associated_request_sse_response(
3866 session: Arc<Session>,
3867 mut call: AssociatedCall,
3868 mut request_rx: OutgoingRequestReceiver,
3869 first_outgoing: OutgoingRequest,
3870 original_request_id: RequestId,
3871 request_method: String,
3872 negotiated_version: String,
3873) -> Response {
3874 let (event_tx, event_rx) =
3875 tokio::sync::mpsc::channel::<std::result::Result<Event, Infallible>>(32);
3876 let call_version = negotiated_version.clone();
3877
3878 tokio::spawn(async move {
3879 let mut pending_ids = Vec::new();
3880 if !send_associated_request(&session, &event_tx, first_outgoing, &mut pending_ids).await {
3881 session
3882 .fail_pending_requests(
3883 &pending_ids,
3884 "originating POST disconnected before the client request was delivered",
3885 )
3886 .await;
3887 return;
3888 }
3889
3890 let mut requests_open = true;
3891 loop {
3892 tokio::select! {
3893 _ = event_tx.closed() => {
3894 session
3895 .fail_pending_requests(
3896 &pending_ids,
3897 "originating POST response stream disconnected",
3898 )
3899 .await;
3900 return;
3901 }
3902 result = &mut call => {
3903 session
3904 .fail_pending_requests(
3905 &pending_ids,
3906 "originating POST completed before the client request response arrived",
3907 )
3908 .await;
3909
3910 let mut response = match result {
3911 Ok(response) => response,
3912 Err(error) => JsonRpcResponse::error(
3913 Some(original_request_id),
3914 JsonRpcError::internal_error(error.to_string()),
3915 ),
3916 };
3917 apply_protocol_result_fields(
3918 &mut response,
3919 &request_method,
3920 &call_version,
3921 );
3922
3923 match serde_json::to_string(&response) {
3924 Ok(data) => {
3925 let _ = event_tx
3926 .send(Ok(
3927 Event::default()
3928 .event(SSE_MESSAGE_EVENT)
3929 .data(data),
3930 ))
3931 .await;
3932 }
3933 Err(error) => {
3934 tracing::error!(
3935 error = %error,
3936 "Failed to serialize associated POST response",
3937 );
3938 }
3939 }
3940 return;
3941 }
3942 outgoing = request_rx.recv(), if requests_open => {
3943 match outgoing {
3944 Some(outgoing) => {
3945 if !send_associated_request(
3946 &session,
3947 &event_tx,
3948 outgoing,
3949 &mut pending_ids,
3950 )
3951 .await
3952 {
3953 session
3954 .fail_pending_requests(
3955 &pending_ids,
3956 "originating POST disconnected before the client request was delivered",
3957 )
3958 .await;
3959 return;
3960 }
3961 }
3962 None => requests_open = false,
3963 }
3964 }
3965 }
3966 }
3967 });
3968
3969 let stream = tokio_stream::wrappers::ReceiverStream::new(event_rx);
3970 let mut response = Sse::new(stream)
3971 .keep_alive(
3972 axum::response::sse::KeepAlive::new()
3973 .interval(Duration::from_secs(30))
3974 .text("ping"),
3975 )
3976 .into_response();
3977 response.headers_mut().insert(
3978 MCP_PROTOCOL_VERSION_HEADER,
3979 HeaderValue::from_str(&negotiated_version).unwrap(),
3980 );
3981 response
3982}
3983
3984async fn send_associated_request(
3985 session: &Session,
3986 event_tx: &tokio::sync::mpsc::Sender<std::result::Result<Event, Infallible>>,
3987 outgoing: OutgoingRequest,
3988 pending_ids: &mut Vec<RequestId>,
3989) -> bool {
3990 let id = outgoing.id.clone();
3991 let request = JsonRpcRequest {
3992 jsonrpc: "2.0".to_string(),
3993 id: id.clone(),
3994 method: outgoing.method,
3995 params: Some(outgoing.params),
3996 };
3997 let data = match serde_json::to_string(&request) {
3998 Ok(data) => data,
3999 Err(error) => {
4000 let _ = outgoing.response_tx.send(Err(Error::Internal(format!(
4001 "Failed to serialize associated client request: {error}"
4002 ))));
4003 return true;
4004 }
4005 };
4006
4007 session
4008 .add_pending_request(id.clone(), outgoing.response_tx)
4009 .await;
4010 pending_ids.push(id);
4011
4012 event_tx
4013 .send(Ok(Event::default().event(SSE_MESSAGE_EVENT).data(data)))
4014 .await
4015 .is_ok()
4016}
4017
4018fn version_supports_subscriptions_listen(
4024 version: &str,
4025 protocol_support: &ProtocolSupport,
4026) -> bool {
4027 version == PROTOCOL_VERSION_2026_07_28 && protocol_support.contains(version)
4028}
4029
4030#[cfg(feature = "stateless")]
4037fn is_stateless_protocol_version(version: &str) -> bool {
4038 version == PROTOCOL_VERSION_2026_07_28
4039}
4040
4041#[cfg(feature = "stateless")]
4050fn stamp_server_info(response: &mut JsonRpcResponse, implementation: &Implementation) {
4051 let JsonRpcResponse::Result(result) = response else {
4052 return;
4053 };
4054 let Some(obj) = result.result.as_object_mut() else {
4055 return;
4056 };
4057 let meta = obj
4058 .entry("_meta")
4059 .or_insert_with(|| serde_json::Value::Object(Default::default()));
4060 let Some(meta_obj) = meta.as_object_mut() else {
4061 return;
4062 };
4063 if let Ok(value) = serde_json::to_value(implementation) {
4064 meta_obj.insert("io.modelcontextprotocol/serverInfo".to_string(), value);
4065 }
4066}
4067
4068#[cfg(feature = "stateless")]
4077struct CancelOnDisconnect(Option<crate::context::CancellationToken>);
4078
4079#[cfg(feature = "stateless")]
4080impl CancelOnDisconnect {
4081 fn arm(token: crate::context::CancellationToken) -> Self {
4082 Self(Some(token))
4083 }
4084
4085 fn disarm(&mut self) {
4086 self.0 = None;
4087 }
4088}
4089
4090#[cfg(feature = "stateless")]
4091impl Drop for CancelOnDisconnect {
4092 fn drop(&mut self) {
4093 if let Some(token) = self.0.take() {
4094 token.cancel();
4095 }
4096 }
4097}
4098
4099#[cfg(feature = "stateless")]
4109struct StatelessSseContext {
4110 version: String,
4111 method: String,
4112 cancel_guard: CancelOnDisconnect,
4113 server_identity: Option<Implementation>,
4114 subscriptions: Arc<ModernSubscriptionRegistry>,
4115}
4116
4117#[cfg(feature = "stateless")]
4118fn stateless_sse_with_notifications(
4119 first: crate::context::ServerNotification,
4120 call: std::pin::Pin<
4121 Box<dyn std::future::Future<Output = crate::error::Result<JsonRpcResponse>> + Send>,
4122 >,
4123 rx: crate::context::NotificationReceiver,
4124 request: StatelessSseContext,
4125) -> Response {
4126 struct Ctx {
4127 call: Option<
4128 std::pin::Pin<
4129 Box<dyn std::future::Future<Output = crate::error::Result<JsonRpcResponse>> + Send>,
4130 >,
4131 >,
4132 rx: crate::context::NotificationReceiver,
4133 rx_open: bool,
4134 queue: std::collections::VecDeque<String>,
4135 terminal: Option<String>,
4136 version: String,
4137 method: String,
4138 cancel_guard: CancelOnDisconnect,
4142 server_identity: Option<Implementation>,
4145 subscriptions: Arc<ModernSubscriptionRegistry>,
4146 }
4147
4148 let mut queue = std::collections::VecDeque::new();
4149 if !request.subscriptions.publish(&first)
4150 && let Some(json) = crate::transport::stdio::serialize_notification(&first)
4151 {
4152 queue.push_back(json);
4153 }
4154 let ctx = Ctx {
4155 call: Some(call),
4156 rx,
4157 rx_open: true,
4158 queue,
4159 terminal: None,
4160 version: request.version,
4161 method: request.method,
4162 cancel_guard: request.cancel_guard,
4163 server_identity: request.server_identity,
4164 subscriptions: request.subscriptions,
4165 };
4166
4167 let stream = futures::stream::unfold(ctx, |mut ctx| async move {
4168 loop {
4169 if let Some(json) = ctx.queue.pop_front() {
4171 return Some((
4172 Ok::<_, Infallible>(Event::default().event(SSE_MESSAGE_EVENT).data(json)),
4173 ctx,
4174 ));
4175 }
4176 if let Some(json) = ctx.terminal.take() {
4178 return Some((
4179 Ok(Event::default().event(SSE_MESSAGE_EVENT).data(json)),
4180 ctx,
4181 ));
4182 }
4183 let mut call = ctx.call.take()?;
4184 tokio::select! {
4185 result = &mut call => {
4186 ctx.cancel_guard.disarm();
4189 while let Ok(n) = ctx.rx.try_recv() {
4192 if !ctx.subscriptions.publish(&n)
4193 && let Some(json) =
4194 crate::transport::stdio::serialize_notification(&n)
4195 {
4196 ctx.queue.push_back(json);
4197 }
4198 }
4199 let terminal_json = match result {
4200 Ok(mut response) => {
4201 if ctx.method == "initialize"
4203 && let JsonRpcResponse::Result(ref mut r) = response
4204 && let Some(pv) = r.result.get_mut("protocolVersion")
4205 {
4206 *pv = serde_json::Value::String(ctx.version.clone());
4207 }
4208 apply_protocol_result_fields(
4209 &mut response,
4210 &ctx.method,
4211 &ctx.version,
4212 );
4213 if let Some(ref identity) = ctx.server_identity {
4214 stamp_server_info(&mut response, identity);
4215 }
4216 serde_json::to_string(&response).ok()
4217 }
4218 Err(e) => Some(
4219 serde_json::json!({
4220 "jsonrpc": "2.0",
4221 "id": serde_json::Value::Null,
4222 "error": JsonRpcError::internal_error(e.to_string()),
4223 })
4224 .to_string(),
4225 ),
4226 };
4227 ctx.terminal = terminal_json;
4228 }
4230 maybe = ctx.rx.recv(), if ctx.rx_open => {
4231 match maybe {
4232 Some(n) => {
4233 if !ctx.subscriptions.publish(&n)
4234 && let Some(json) =
4235 crate::transport::stdio::serialize_notification(&n)
4236 {
4237 ctx.queue.push_back(json);
4238 }
4239 }
4240 None => ctx.rx_open = false,
4241 }
4242 ctx.call = Some(call);
4243 }
4244 }
4245 }
4246 });
4247
4248 Sse::new(stream)
4249 .keep_alive(
4250 axum::response::sse::KeepAlive::new()
4251 .interval(Duration::from_secs(30))
4252 .text("ping"),
4253 )
4254 .into_response()
4255}
4256
4257#[cfg(feature = "stateless")]
4262fn listen_request_declares_tasks(parsed: &serde_json::Value) -> bool {
4263 parsed
4264 .get("params")
4265 .and_then(crate::stateless::StatelessRequestMeta::from_params)
4266 .and_then(|meta| meta.client_capabilities)
4267 .and_then(|capabilities| capabilities.extensions)
4268 .is_some_and(|declared| {
4269 declared.contains_key(tower_mcp_types::protocol::TASKS_EXTENSION_ID)
4270 })
4271}
4272
4273#[cfg(feature = "stateless")]
4276async fn handle_modern_subscriptions_listen_sse(
4277 state: Arc<AppState>,
4278 parsed: &serde_json::Value,
4279) -> Response {
4280 let id = extract_request_id(parsed);
4281 let Some(subscription_id) = id.clone() else {
4282 return json_rpc_error_response_with_status(
4283 None,
4284 JsonRpcError::invalid_request("subscriptions/listen requires a request id"),
4285 StatusCode::BAD_REQUEST,
4286 );
4287 };
4288 let params = match parsed
4289 .get("params")
4290 .cloned()
4291 .ok_or_else(|| JsonRpcError::invalid_params("subscriptions/listen requires params"))
4292 .and_then(|value| {
4293 serde_json::from_value::<SubscriptionsListenParams>(value)
4294 .map_err(|error| JsonRpcError::invalid_params(error.to_string()))
4295 }) {
4296 Ok(params) => params,
4297 Err(error) => {
4298 return json_rpc_error_response_with_status(id, error, StatusCode::BAD_REQUEST);
4299 }
4300 };
4301 let Some(requested) = params.notifications else {
4302 return json_rpc_error_response_with_status(
4303 id,
4304 JsonRpcError::invalid_params("subscriptions/listen requires a notifications filter"),
4305 StatusCode::BAD_REQUEST,
4306 );
4307 };
4308 if requested.task_ids.is_some() && !listen_request_declares_tasks(parsed) {
4311 return json_rpc_error_response_with_status(
4312 id,
4313 JsonRpcError::missing_required_client_capability(
4314 crate::router::tasks_client_capabilities(),
4315 ),
4316 StatusCode::BAD_REQUEST,
4317 );
4318 }
4319
4320 let accepted = accepted_subscription_filter(requested, state.tasks_extension_enabled());
4321 let (rx, guard) = state
4322 .modern_subscriptions
4323 .register(subscription_id.clone(), accepted.clone());
4324 let acknowledgment = serde_json::json!({
4325 "jsonrpc": "2.0",
4326 "method": "notifications/subscriptions/acknowledged",
4327 "params": {
4328 "_meta": {
4329 "io.modelcontextprotocol/subscriptionId": subscription_id
4330 },
4331 "notifications": accepted
4332 }
4333 })
4334 .to_string();
4335
4336 struct ModernListenStream {
4337 first: Option<String>,
4338 rx: mpsc::UnboundedReceiver<String>,
4339 _guard: ModernSubscriptionGuard,
4340 }
4341
4342 let stream = futures::stream::unfold(
4343 ModernListenStream {
4344 first: Some(acknowledgment),
4345 rx,
4346 _guard: guard,
4347 },
4348 |mut state| async move {
4349 let message = match state.first.take() {
4350 Some(first) => Some(first),
4351 None => state.rx.recv().await,
4352 }?;
4353 Some((
4354 Ok::<_, Infallible>(Event::default().event(SSE_MESSAGE_EVENT).data(message)),
4355 state,
4356 ))
4357 },
4358 );
4359
4360 let mut response = Sse::new(stream)
4361 .keep_alive(
4362 axum::response::sse::KeepAlive::new()
4363 .interval(Duration::from_secs(30))
4364 .text("ping"),
4365 )
4366 .into_response();
4367 response.headers_mut().insert(
4368 MCP_PROTOCOL_VERSION_HEADER,
4369 HeaderValue::from_static(PROTOCOL_VERSION_2026_07_28),
4370 );
4371 response
4372}
4373
4374async fn handle_subscriptions_listen_sse(session: Arc<Session>) -> Response {
4384 let rx = session.notifications_tx.subscribe();
4385 let session_clone = session.clone();
4386
4387 let stream = BroadcastStream::new(rx)
4388 .then(move |result: std::result::Result<String, _>| {
4389 let session = session_clone.clone();
4390 async move {
4391 match result {
4392 Ok(msg) => {
4393 let event_id = session.next_event_id();
4394 session.buffer_event(event_id, msg.clone()).await;
4396 Some(Ok::<_, Infallible>(
4397 Event::default()
4398 .id(event_id.to_string())
4399 .event(SSE_MESSAGE_EVENT)
4400 .data(msg),
4401 ))
4402 }
4403 Err(_) => None,
4404 }
4405 }
4406 })
4407 .filter_map(|x| x);
4408
4409 Sse::new(stream)
4410 .keep_alive(
4411 axum::response::sse::KeepAlive::new()
4412 .interval(Duration::from_secs(30))
4413 .text("ping"),
4414 )
4415 .into_response()
4416}
4417
4418async fn handle_get(
4420 State(state): State<Arc<AppState>>,
4421 request: axum::extract::Request,
4422) -> Response {
4423 let (parts, _body) = request.into_parts();
4424 let headers = parts.headers;
4425 let uri = parts.uri.clone();
4426
4427 if let Some(resp) = validate_host(&headers, &uri, &state) {
4429 return resp;
4430 }
4431
4432 if let Some(resp) = validate_origin(&headers, &state) {
4434 return resp;
4435 }
4436
4437 let accept = headers
4439 .get(header::ACCEPT)
4440 .and_then(|v| v.to_str().ok())
4441 .unwrap_or("");
4442
4443 if !accept.contains("text/event-stream") {
4444 return (
4445 StatusCode::NOT_ACCEPTABLE,
4446 "Accept header must include text/event-stream",
4447 )
4448 .into_response();
4449 }
4450
4451 let session_id = match get_session_id(&headers) {
4453 Some(id) => id,
4454 None => {
4455 return json_rpc_error_response(None, JsonRpcError::session_required());
4456 }
4457 };
4458
4459 let session = match state.sessions.get(&session_id).await {
4460 Some(s) => s,
4461 None => {
4462 return json_rpc_error_response(
4463 None,
4464 JsonRpcError::session_not_found_with_id(&session_id),
4465 );
4466 }
4467 };
4468
4469 let last_event_id = get_last_event_id(&headers);
4471
4472 let rx = session.notifications_tx.subscribe();
4475 let session_clone = session.clone();
4476
4477 let replay_events: Vec<_> = if let Some(after_id) = last_event_id {
4479 let events = session.get_events_after(after_id).await;
4480 tracing::debug!(
4481 after_id = after_id,
4482 replay_count = events.len(),
4483 "Replaying buffered events for stream resumption"
4484 );
4485 events
4486 .into_iter()
4487 .map(|e| {
4488 Ok::<_, Infallible>(
4489 Event::default()
4490 .id(e.id.to_string())
4491 .event(SSE_MESSAGE_EVENT)
4492 .data(e.data),
4493 )
4494 })
4495 .collect()
4496 } else {
4497 Vec::new()
4498 };
4499
4500 let replay_stream = tokio_stream::iter(replay_events);
4502
4503 let live_stream = BroadcastStream::new(rx)
4506 .then(move |result: std::result::Result<String, _>| {
4507 let session = session_clone.clone();
4508 async move {
4509 match result {
4510 Ok(msg) => {
4511 let event_id = session.next_event_id();
4512 session.buffer_event(event_id, msg.clone()).await;
4514 Some(Ok::<_, Infallible>(
4515 Event::default()
4516 .id(event_id.to_string())
4517 .event(SSE_MESSAGE_EVENT)
4518 .data(msg),
4519 ))
4520 }
4521 Err(_) => None,
4522 }
4523 }
4524 })
4525 .filter_map(|x| x);
4526
4527 let stream = replay_stream.chain(live_stream);
4529
4530 Sse::new(stream)
4531 .keep_alive(
4532 axum::response::sse::KeepAlive::new()
4533 .interval(Duration::from_secs(30))
4534 .text("ping"),
4535 )
4536 .into_response()
4537}
4538
4539async fn handle_delete(
4541 State(state): State<Arc<AppState>>,
4542 request: axum::extract::Request,
4543) -> Response {
4544 let (parts, _body) = request.into_parts();
4545 let headers = parts.headers;
4546 let uri = parts.uri.clone();
4547
4548 if let Some(resp) = validate_host(&headers, &uri, &state) {
4550 return resp;
4551 }
4552
4553 if let Some(resp) = validate_origin(&headers, &state) {
4555 return resp;
4556 }
4557
4558 let session_id = match get_session_id(&headers) {
4559 Some(id) => id,
4560 None => {
4561 return json_rpc_error_response(None, JsonRpcError::session_required());
4562 }
4563 };
4564
4565 if state.sessions.remove(&session_id).await {
4566 tracing::info!(session_id = %session_id, "Session terminated");
4567 StatusCode::OK.into_response()
4568 } else {
4569 tracing::debug!(session_id = %session_id, "Session already removed or never existed");
4572 StatusCode::OK.into_response()
4573 }
4574}
4575
4576async fn handle_health() -> Response {
4581 StatusCode::OK.into_response()
4582}
4583
4584fn sse_json_response(response: impl serde::Serialize) -> Response {
4595 let json = match serde_json::to_string(&response) {
4596 Ok(s) => s,
4597 Err(e) => {
4598 tracing::error!(error = %e, "Failed to serialize response for SSE wrapping");
4599 return StatusCode::INTERNAL_SERVER_ERROR.into_response();
4600 }
4601 };
4602 let sse_body = format!("event: message\ndata: {json}\n\n");
4603 (
4604 StatusCode::OK,
4605 [
4606 (header::CONTENT_TYPE, "text/event-stream"),
4607 (header::CACHE_CONTROL, "no-cache"),
4608 ],
4609 sse_body,
4610 )
4611 .into_response()
4612}
4613
4614fn json_rpc_error_response(
4616 id: Option<crate::protocol::RequestId>,
4617 error: JsonRpcError,
4618) -> Response {
4619 let response = JsonRpcResponse::error(id, error);
4620 axum::Json(response).into_response()
4621}
4622
4623fn json_rpc_error_response_with_status(
4624 id: Option<crate::protocol::RequestId>,
4625 error: JsonRpcError,
4626 status: StatusCode,
4627) -> Response {
4628 let mut response = json_rpc_error_response(id, error);
4629 *response.status_mut() = status;
4630 response
4631}
4632
4633fn body_too_large_response(limit: usize) -> Response {
4635 let mut resp = json_rpc_error_response(
4636 None,
4637 JsonRpcError::invalid_request(format!(
4638 "Request body exceeds the maximum size of {} bytes",
4639 limit
4640 )),
4641 );
4642 *resp.status_mut() = StatusCode::PAYLOAD_TOO_LARGE;
4643 resp
4644}
4645
4646fn is_length_limit_error(err: &axum::Error) -> bool {
4649 let mut source: Option<&(dyn std::error::Error + 'static)> = Some(err);
4650 while let Some(e) = source {
4651 if e.is::<http_body_util::LengthLimitError>() {
4652 return true;
4653 }
4654 source = e.source();
4655 }
4656 false
4657}
4658
4659#[cfg(test)]
4660mod tests {
4661 use super::*;
4662 use axum::body::Body;
4663 use axum::http::Request;
4664 use proptest::prelude::*;
4665 use tower::ServiceExt;
4666
4667 #[cfg(feature = "oauth")]
4668 fn oauth_test_token(audience: &str, scope: &str) -> String {
4669 jsonwebtoken::encode(
4670 &jsonwebtoken::Header::new(jsonwebtoken::Algorithm::HS256),
4671 &serde_json::json!({
4672 "sub": "test-user",
4673 "aud": audience,
4674 "scope": scope,
4675 }),
4676 &jsonwebtoken::EncodingKey::from_secret(b"resource-server-test-secret"),
4677 )
4678 .unwrap()
4679 }
4680
4681 #[cfg(feature = "oauth")]
4682 #[tokio::test]
4683 async fn oauth_resource_server_setup_is_path_aware_and_audience_bound() {
4684 let resource = "http://localhost:3000/tenant/mcp";
4685 let metadata = crate::oauth::ProtectedResourceMetadata::new(resource)
4686 .authorization_server("https://auth.example.com")
4687 .scope("mcp:read");
4688 let validator = crate::oauth::JwtValidator::from_secret(b"resource-server-test-secret")
4689 .disable_exp_validation();
4690 let app = HttpTransport::new(create_test_router())
4691 .disable_origin_validation()
4692 .disable_host_validation()
4693 .into_oauth_router_at(
4694 "/tenant/mcp",
4695 validator,
4696 metadata,
4697 crate::oauth::ScopePolicy::new().default_scope("mcp:read"),
4698 )
4699 .unwrap();
4700
4701 let metadata_request = Request::builder()
4702 .uri("/.well-known/oauth-protected-resource/tenant/mcp")
4703 .body(Body::empty())
4704 .unwrap();
4705 let metadata_response = app.clone().oneshot(metadata_request).await.unwrap();
4706 assert_eq!(metadata_response.status(), StatusCode::OK);
4707
4708 let unauthenticated = Request::builder()
4709 .method("POST")
4710 .uri("/tenant/mcp")
4711 .body(Body::empty())
4712 .unwrap();
4713 let response = app.clone().oneshot(unauthenticated).await.unwrap();
4714 assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
4715 assert!(
4716 response
4717 .headers()
4718 .get("WWW-Authenticate")
4719 .unwrap()
4720 .to_str()
4721 .unwrap()
4722 .contains("http://localhost:3000/.well-known/oauth-protected-resource/tenant/mcp")
4723 );
4724
4725 let wrong_audience = oauth_test_token("http://localhost:3000/other", "mcp:read");
4726 let request = Request::builder()
4727 .method("POST")
4728 .uri("/tenant/mcp")
4729 .header("Authorization", format!("Bearer {wrong_audience}"))
4730 .body(Body::empty())
4731 .unwrap();
4732 assert_eq!(
4733 app.clone().oneshot(request).await.unwrap().status(),
4734 StatusCode::UNAUTHORIZED
4735 );
4736
4737 let token = oauth_test_token(resource, "mcp:read");
4738 let request = Request::builder()
4739 .method("POST")
4740 .uri("/tenant/mcp")
4741 .header("Authorization", format!("Bearer {token}"))
4742 .header("Content-Type", "application/json")
4743 .header("Accept", "application/json, text/event-stream")
4744 .body(Body::from(
4745 serde_json::json!({
4746 "jsonrpc": "2.0",
4747 "id": 1,
4748 "method": "initialize",
4749 "params": {
4750 "protocolVersion": "2025-11-25",
4751 "capabilities": {},
4752 "clientInfo": { "name": "oauth-test", "version": "1.0" }
4753 }
4754 })
4755 .to_string(),
4756 ))
4757 .unwrap();
4758 let response = app.oneshot(request).await.unwrap();
4759 assert_eq!(response.status(), StatusCode::OK);
4760 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
4761 .await
4762 .unwrap();
4763 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
4764 assert!(json.get("result").is_some(), "unexpected response: {json}");
4765 }
4766
4767 #[cfg(feature = "oauth")]
4768 #[test]
4769 fn oauth_resource_server_setup_validates_metadata() {
4770 let result = HttpTransport::new(create_test_router()).into_oauth_router(
4771 crate::oauth::JwtValidator::from_secret(b"secret"),
4772 crate::oauth::ProtectedResourceMetadata::new("https://mcp.example.com"),
4773 crate::oauth::ScopePolicy::new(),
4774 );
4775 assert!(matches!(
4776 result.unwrap_err(),
4777 crate::oauth::ProtectedResourceMetadataError::MissingAuthorizationServer
4778 ));
4779 }
4780
4781 fn arb_json() -> impl Strategy<Value = serde_json::Value> {
4782 let leaf = prop_oneof![
4783 Just(serde_json::Value::Null),
4784 any::<bool>().prop_map(serde_json::Value::Bool),
4785 any::<i64>().prop_map(|number| serde_json::json!(number)),
4786 prop::collection::vec(any::<char>(), 0..256)
4787 .prop_map(|chars| serde_json::Value::String(chars.into_iter().collect())),
4788 ];
4789 leaf.prop_recursive(6, 128, 10, |inner| {
4790 prop_oneof![
4791 prop::collection::vec(inner.clone(), 0..10).prop_map(serde_json::Value::Array),
4792 prop::collection::hash_map("[a-zA-Z0-9_]{0,24}", inner, 0..10)
4793 .prop_map(|map| serde_json::Value::Object(map.into_iter().collect())),
4794 ]
4795 })
4796 }
4797
4798 proptest! {
4799 #![proptest_config(ProptestConfig::with_cases(512))]
4800
4801 #[test]
4804 fn extract_request_id_never_panics(value in arb_json()) {
4805 let _ = extract_request_id(&value);
4806 }
4807
4808 #[test]
4809 fn extract_request_id_accepts_all_i64_values(id in any::<i64>()) {
4810 prop_assert_eq!(
4811 extract_request_id(&serde_json::json!({ "id": id })),
4812 Some(RequestId::Number(id))
4813 );
4814 }
4815
4816 #[test]
4817 fn extract_request_id_accepts_arbitrary_strings(
4818 chars in prop::collection::vec(any::<char>(), 0..1024)
4819 ) {
4820 let id: String = chars.into_iter().collect();
4821 prop_assert_eq!(
4822 extract_request_id(&serde_json::json!({ "id": id })),
4823 Some(RequestId::String(id))
4824 );
4825 }
4826 }
4827
4828 fn create_test_router() -> McpRouter {
4829 McpRouter::new().server_info("test-server", "1.0.0")
4830 }
4831
4832 #[test]
4833 fn final_result_fields_are_method_and_version_aware() {
4834 for method in [
4835 "server/discover",
4836 "tools/list",
4837 "prompts/list",
4838 "resources/list",
4839 "resources/read",
4840 "resources/templates/list",
4841 ] {
4842 let mut response =
4843 JsonRpcResponse::result(RequestId::Number(1), serde_json::json!({"value": true}));
4844 apply_protocol_result_fields(&mut response, method, PROTOCOL_VERSION_2026_07_28);
4845 let json = serde_json::to_value(response).unwrap();
4846 assert_eq!(json["result"]["resultType"], "complete", "{method}");
4847 assert_eq!(json["result"]["ttlMs"], 0, "{method}");
4848 assert_eq!(json["result"]["cacheScope"], "private", "{method}");
4849 }
4850
4851 let mut ordinary =
4852 JsonRpcResponse::result(RequestId::Number(1), serde_json::json!({"content": []}));
4853 apply_protocol_result_fields(&mut ordinary, "tools/call", PROTOCOL_VERSION_2026_07_28);
4854 let json = serde_json::to_value(ordinary).unwrap();
4855 assert_eq!(json["result"]["resultType"], "complete");
4856 assert!(json["result"].get("ttlMs").is_none());
4857 assert!(json["result"].get("cacheScope").is_none());
4858 }
4859
4860 #[test]
4861 fn final_result_fields_preserve_explicit_values_and_legacy_wire_shape() {
4862 let explicit = serde_json::json!({
4863 "contents": [],
4864 "ttlMs": 42,
4865 "cacheScope": "public"
4866 });
4867 let mut response = JsonRpcResponse::result(RequestId::Number(1), explicit.clone());
4868 apply_protocol_result_fields(&mut response, "resources/read", PROTOCOL_VERSION_2026_07_28);
4869 let json = serde_json::to_value(response).unwrap();
4870 assert_eq!(json["result"]["ttlMs"], 42);
4871 assert_eq!(json["result"]["cacheScope"], "public");
4872
4873 for discriminator in ["input_required", "task"] {
4874 let mut response = JsonRpcResponse::result(
4875 RequestId::Number(1),
4876 serde_json::json!({"resultType": discriminator}),
4877 );
4878 apply_protocol_result_fields(&mut response, "tools/call", PROTOCOL_VERSION_2026_07_28);
4879 let json = serde_json::to_value(response).unwrap();
4880 assert_eq!(json["result"]["resultType"], discriminator);
4881 }
4882
4883 let mut legacy = JsonRpcResponse::result(RequestId::Number(1), explicit);
4884 let before = serde_json::to_value(&legacy).unwrap();
4885 apply_protocol_result_fields(&mut legacy, "resources/read", "2025-11-25");
4886 assert_eq!(serde_json::to_value(legacy).unwrap(), before);
4887 }
4888
4889 #[tokio::test]
4890 #[cfg(feature = "stateless")]
4891 async fn modern_subscription_registry_filters_and_tags_notifications() {
4892 let registry = Arc::new(ModernSubscriptionRegistry::default());
4893 let (mut rx, guard) = registry.register(
4894 RequestId::String("listen-1".to_string()),
4895 SubscriptionFilter {
4896 tools_list_changed: Some(true),
4897 ..SubscriptionFilter::default()
4898 },
4899 );
4900
4901 assert!(registry.publish(&ServerNotification::PromptsListChanged));
4902 assert!(matches!(
4903 rx.try_recv(),
4904 Err(mpsc::error::TryRecvError::Empty)
4905 ));
4906
4907 assert!(registry.publish(&ServerNotification::ToolsListChanged));
4908 let message = rx.recv().await.expect("matching notification");
4909 let json: serde_json::Value = serde_json::from_str(&message).unwrap();
4910 assert_eq!(json["method"], "notifications/tools/list_changed");
4911 assert_eq!(
4912 json["params"]["_meta"]["io.modelcontextprotocol/subscriptionId"],
4913 "listen-1"
4914 );
4915
4916 drop(guard);
4917 assert!(registry.subscriptions.lock().unwrap().is_empty());
4918 }
4919
4920 #[tokio::test]
4921 async fn runtime_protocol_allowlist_drives_discovery() {
4922 let transport = HttpTransport::new(create_test_router())
4923 .disable_origin_validation()
4924 .protocol_versions(["2025-03-26"])
4925 .unwrap();
4926 let app = transport.into_router();
4927 let request = Request::builder()
4928 .method("POST")
4929 .uri("/")
4930 .header("Content-Type", "application/json")
4931 .header("Accept", "application/json")
4932 .body(Body::from(
4933 serde_json::json!({
4934 "jsonrpc": "2.0",
4935 "id": 1,
4936 "method": "server/discover"
4937 })
4938 .to_string(),
4939 ))
4940 .unwrap();
4941
4942 let response = app.oneshot(request).await.unwrap();
4943 assert_eq!(response.status(), StatusCode::OK);
4944 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
4945 .await
4946 .unwrap();
4947 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
4948 assert_eq!(
4949 json["result"]["supportedVersions"],
4950 serde_json::json!(["2025-03-26"])
4951 );
4952 }
4953
4954 #[tokio::test]
4955 async fn test_oversized_body_rejected_with_413() {
4956 let transport = HttpTransport::new(create_test_router())
4957 .disable_origin_validation()
4958 .max_body_size(1024);
4959 let app = transport.into_router();
4960
4961 let padding = "x".repeat(2048);
4963 let body = format!(
4964 r#"{{"jsonrpc":"2.0","id":1,"method":"ping","params":{{"pad":"{}"}}}}"#,
4965 padding
4966 );
4967 let request = Request::builder()
4968 .method("POST")
4969 .uri("/")
4970 .header("Content-Type", "application/json")
4971 .header("Accept", "application/json, text/event-stream")
4972 .body(Body::from(body))
4973 .unwrap();
4974
4975 let response = app.oneshot(request).await.unwrap();
4976 assert_eq!(response.status(), StatusCode::PAYLOAD_TOO_LARGE);
4977 }
4978
4979 #[tokio::test]
4980 async fn test_oversized_content_length_rejected_without_reading() {
4981 let transport = HttpTransport::new(create_test_router())
4982 .disable_origin_validation()
4983 .max_body_size(1024);
4984 let app = transport.into_router();
4985
4986 let request = Request::builder()
4989 .method("POST")
4990 .uri("/")
4991 .header("Content-Type", "application/json")
4992 .header("Accept", "application/json, text/event-stream")
4993 .header("Content-Length", "10485760")
4994 .body(Body::from(r#"{"jsonrpc":"2.0","id":1,"method":"ping"}"#))
4995 .unwrap();
4996
4997 let response = app.oneshot(request).await.unwrap();
4998 assert_eq!(response.status(), StatusCode::PAYLOAD_TOO_LARGE);
4999 }
5000
5001 #[tokio::test]
5002 async fn test_body_within_limit_accepted() {
5003 let transport = HttpTransport::new(create_test_router())
5004 .disable_origin_validation()
5005 .max_body_size(1024);
5006 let app = transport.into_router();
5007
5008 let request = Request::builder()
5009 .method("POST")
5010 .uri("/")
5011 .header("Content-Type", "application/json")
5012 .header("Accept", "application/json, text/event-stream")
5013 .body(Body::from(r#"{"jsonrpc":"2.0","id":1,"method":"ping"}"#))
5014 .unwrap();
5015
5016 let response = app.oneshot(request).await.unwrap();
5017 assert_eq!(response.status(), StatusCode::OK);
5018 }
5019
5020 #[tokio::test]
5021 async fn test_initialize_creates_session() {
5022 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
5023 let app = transport.into_router();
5024
5025 let request = Request::builder()
5026 .method("POST")
5027 .uri("/")
5028 .header("Content-Type", "application/json")
5029 .header("Accept", "application/json, text/event-stream")
5030 .body(Body::from(
5031 serde_json::json!({
5032 "jsonrpc": "2.0",
5033 "id": 1,
5034 "method": "initialize",
5035 "params": {
5036 "protocolVersion": "2025-11-25",
5037 "capabilities": {},
5038 "clientInfo": {
5039 "name": "test-client",
5040 "version": "1.0.0"
5041 }
5042 }
5043 })
5044 .to_string(),
5045 ))
5046 .unwrap();
5047
5048 let response = app.oneshot(request).await.unwrap();
5049
5050 assert_eq!(response.status(), StatusCode::OK);
5051 assert!(response.headers().contains_key(MCP_SESSION_ID_HEADER));
5052 assert_eq!(
5054 response
5055 .headers()
5056 .get(MCP_PROTOCOL_VERSION_HEADER)
5057 .and_then(|v| v.to_str().ok()),
5058 Some("2025-11-25")
5059 );
5060 }
5061
5062 #[tokio::test]
5063 async fn test_protocol_version_header_on_subsequent_requests() {
5064 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
5065 let app = transport.into_router();
5066
5067 let init_request = Request::builder()
5069 .method("POST")
5070 .uri("/")
5071 .header("Content-Type", "application/json")
5072 .header("Accept", "application/json, text/event-stream")
5073 .body(Body::from(
5074 serde_json::json!({
5075 "jsonrpc": "2.0",
5076 "id": 1,
5077 "method": "initialize",
5078 "params": {
5079 "protocolVersion": "2025-03-26",
5080 "capabilities": {},
5081 "clientInfo": {
5082 "name": "test-client",
5083 "version": "1.0.0"
5084 }
5085 }
5086 })
5087 .to_string(),
5088 ))
5089 .unwrap();
5090
5091 let init_response = app.clone().oneshot(init_request).await.unwrap();
5092 let session_id = init_response
5093 .headers()
5094 .get(MCP_SESSION_ID_HEADER)
5095 .unwrap()
5096 .to_str()
5097 .unwrap()
5098 .to_string();
5099
5100 assert_eq!(
5102 init_response
5103 .headers()
5104 .get(MCP_PROTOCOL_VERSION_HEADER)
5105 .and_then(|v| v.to_str().ok()),
5106 Some("2025-03-26")
5107 );
5108
5109 let initialized_request = Request::builder()
5111 .method("POST")
5112 .uri("/")
5113 .header("Content-Type", "application/json")
5114 .header("Accept", "application/json, text/event-stream")
5115 .header(MCP_SESSION_ID_HEADER, &session_id)
5116 .header(MCP_PROTOCOL_VERSION_HEADER, "2025-03-26")
5117 .body(Body::from(
5118 serde_json::json!({
5119 "jsonrpc": "2.0",
5120 "method": "notifications/initialized"
5121 })
5122 .to_string(),
5123 ))
5124 .unwrap();
5125
5126 app.clone().oneshot(initialized_request).await.unwrap();
5127
5128 let list_request = Request::builder()
5130 .method("POST")
5131 .uri("/")
5132 .header("Content-Type", "application/json")
5133 .header("Accept", "application/json, text/event-stream")
5134 .header(MCP_SESSION_ID_HEADER, &session_id)
5135 .header(MCP_PROTOCOL_VERSION_HEADER, "2025-03-26")
5136 .body(Body::from(
5137 serde_json::json!({
5138 "jsonrpc": "2.0",
5139 "id": 2,
5140 "method": "tools/list"
5141 })
5142 .to_string(),
5143 ))
5144 .unwrap();
5145
5146 let response = app.oneshot(list_request).await.unwrap();
5147 assert_eq!(response.status(), StatusCode::OK);
5148 assert_eq!(
5149 response
5150 .headers()
5151 .get(MCP_PROTOCOL_VERSION_HEADER)
5152 .and_then(|v| v.to_str().ok()),
5153 Some("2025-03-26")
5154 );
5155 }
5156
5157 #[tokio::test]
5158 async fn unsupported_protocol_version_returns_spec_shape_error() {
5159 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
5163 let app = transport.into_router();
5164
5165 let init_request = Request::builder()
5167 .method("POST")
5168 .uri("/")
5169 .header("Content-Type", "application/json")
5170 .header("Accept", "application/json, text/event-stream")
5171 .body(Body::from(
5172 serde_json::json!({
5173 "jsonrpc": "2.0",
5174 "id": 1,
5175 "method": "initialize",
5176 "params": {
5177 "protocolVersion": "2025-11-25",
5178 "capabilities": {},
5179 "clientInfo": { "name": "t", "version": "0" }
5180 }
5181 })
5182 .to_string(),
5183 ))
5184 .unwrap();
5185 let init_response = app.clone().oneshot(init_request).await.unwrap();
5186 let session_id = init_response
5187 .headers()
5188 .get(MCP_SESSION_ID_HEADER)
5189 .unwrap()
5190 .to_str()
5191 .unwrap()
5192 .to_string();
5193
5194 let bad = Request::builder()
5196 .method("POST")
5197 .uri("/")
5198 .header("Content-Type", "application/json")
5199 .header("Accept", "application/json")
5200 .header(MCP_SESSION_ID_HEADER, &session_id)
5201 .header(MCP_PROTOCOL_VERSION_HEADER, "1999-01-01")
5202 .body(Body::from(
5203 serde_json::json!({
5204 "jsonrpc": "2.0",
5205 "id": 99,
5206 "method": "tools/list"
5207 })
5208 .to_string(),
5209 ))
5210 .unwrap();
5211 let response = app.oneshot(bad).await.unwrap();
5212 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
5213 .await
5214 .unwrap();
5215 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
5216 assert_eq!(json["error"]["code"].as_i64().unwrap(), -32022);
5217 assert_eq!(json["error"]["data"]["requested"], "1999-01-01");
5218 let supported = json["error"]["data"]["supported"]
5219 .as_array()
5220 .expect("supported must be an array");
5221 assert!(supported.contains(&serde_json::json!("2025-11-25")));
5222 assert_eq!(json["id"], 99);
5224 assert!(
5226 json["error"]["data"].get("supportedVersions").is_none(),
5227 "error data must use 'supported', not 'supportedVersions': {:?}",
5228 json["error"]["data"]
5229 );
5230 let expected: Vec<serde_json::Value> = crate::COMPILED_PROTOCOL_VERSIONS
5233 .iter()
5234 .map(|v| serde_json::json!(v))
5235 .collect();
5236 assert_eq!(
5237 supported, &expected,
5238 "data.supported must exactly match COMPILED_PROTOCOL_VERSIONS"
5239 );
5240 }
5241
5242 #[cfg(feature = "stateless")]
5247 #[tokio::test]
5248 async fn stateless_unsupported_protocol_version_returns_spec_shape_error() {
5249 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
5250 let app = transport.into_router();
5251
5252 let req = Request::builder()
5255 .method("POST")
5256 .uri("/")
5257 .header("Content-Type", "application/json")
5258 .header("Accept", "application/json")
5259 .header(MCP_PROTOCOL_VERSION_HEADER, "2099-01-01")
5260 .body(Body::from(
5261 serde_json::json!({
5262 "jsonrpc": "2.0",
5263 "id": 42,
5264 "method": "tools/list"
5265 })
5266 .to_string(),
5267 ))
5268 .unwrap();
5269 let response = app.oneshot(req).await.unwrap();
5270 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
5271 .await
5272 .unwrap();
5273 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
5274 assert_eq!(
5275 json["error"]["code"].as_i64().unwrap(),
5276 -32022,
5277 "must return UnsupportedProtocolVersion (-32022): {json}"
5278 );
5279 assert_eq!(
5280 json["error"]["data"]["requested"], "2099-01-01",
5281 "data.requested must echo the version: {json}"
5282 );
5283 assert!(
5285 json["error"]["data"].get("supportedVersions").is_none(),
5286 "error data must use 'supported', not 'supportedVersions': {json}"
5287 );
5288 let supported = json["error"]["data"]["supported"]
5289 .as_array()
5290 .expect("data.supported must be an array");
5291 let expected: Vec<serde_json::Value> = crate::COMPILED_PROTOCOL_VERSIONS
5292 .iter()
5293 .map(|v| serde_json::json!(v))
5294 .collect();
5295 assert_eq!(
5296 supported, &expected,
5297 "data.supported must exactly match COMPILED_PROTOCOL_VERSIONS"
5298 );
5299 }
5300
5301 #[tokio::test]
5309 async fn sep_2243_lenient_mode_accepts_missing_headers() {
5310 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
5311 let app = transport.into_router();
5312
5313 let init = Request::builder()
5315 .method("POST")
5316 .uri("/")
5317 .header("Content-Type", "application/json")
5318 .header("Accept", "application/json, text/event-stream")
5319 .body(Body::from(
5320 serde_json::json!({
5321 "jsonrpc": "2.0",
5322 "id": 1,
5323 "method": "initialize",
5324 "params": {
5325 "protocolVersion": "2025-11-25",
5326 "capabilities": {},
5327 "clientInfo": { "name": "t", "version": "0" }
5328 }
5329 })
5330 .to_string(),
5331 ))
5332 .unwrap();
5333 let init_response = app.clone().oneshot(init).await.unwrap();
5334 assert_eq!(init_response.status(), StatusCode::OK);
5335 let session_id = init_response
5336 .headers()
5337 .get(MCP_SESSION_ID_HEADER)
5338 .unwrap()
5339 .to_str()
5340 .unwrap()
5341 .to_string();
5342
5343 let req = Request::builder()
5345 .method("POST")
5346 .uri("/")
5347 .header("Content-Type", "application/json")
5348 .header("Accept", "application/json, text/event-stream")
5349 .header(MCP_SESSION_ID_HEADER, &session_id)
5350 .body(Body::from(
5351 serde_json::json!({
5352 "jsonrpc": "2.0",
5353 "id": 2,
5354 "method": "tools/list"
5355 })
5356 .to_string(),
5357 ))
5358 .unwrap();
5359 let response = app.oneshot(req).await.unwrap();
5360 assert_eq!(response.status(), StatusCode::OK);
5361 }
5362
5363 #[tokio::test]
5367 async fn sep_2243_lenient_mode_validates_present_headers() {
5368 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
5369 let app = transport.into_router();
5370
5371 let init = Request::builder()
5372 .method("POST")
5373 .uri("/")
5374 .header("Content-Type", "application/json")
5375 .header("Accept", "application/json, text/event-stream")
5376 .header(MCP_METHOD_HEADER, "initialize")
5377 .body(Body::from(
5378 serde_json::json!({
5379 "jsonrpc": "2.0",
5380 "id": 1,
5381 "method": "initialize",
5382 "params": {
5383 "protocolVersion": "2025-11-25",
5384 "capabilities": {},
5385 "clientInfo": { "name": "t", "version": "0" }
5386 }
5387 })
5388 .to_string(),
5389 ))
5390 .unwrap();
5391 let init_response = app.clone().oneshot(init).await.unwrap();
5392 assert_eq!(init_response.status(), StatusCode::OK);
5393 let session_id = init_response
5394 .headers()
5395 .get(MCP_SESSION_ID_HEADER)
5396 .unwrap()
5397 .to_str()
5398 .unwrap()
5399 .to_string();
5400
5401 let req = Request::builder()
5403 .method("POST")
5404 .uri("/")
5405 .header("Content-Type", "application/json")
5406 .header("Accept", "application/json")
5407 .header(MCP_SESSION_ID_HEADER, &session_id)
5408 .header(MCP_METHOD_HEADER, "ping")
5409 .body(Body::from(
5410 serde_json::json!({
5411 "jsonrpc": "2.0",
5412 "id": 2,
5413 "method": "tools/list"
5414 })
5415 .to_string(),
5416 ))
5417 .unwrap();
5418 let response = app.oneshot(req).await.unwrap();
5419 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
5420 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
5421 .await
5422 .unwrap();
5423 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
5424 assert_eq!(json["error"]["code"].as_i64().unwrap(), -32020);
5425 assert!(
5426 json["error"]["message"]
5427 .as_str()
5428 .unwrap()
5429 .contains("Mcp-Method")
5430 );
5431 assert_eq!(json["id"], 2);
5432 }
5433
5434 #[tokio::test]
5442 #[cfg(not(feature = "stateless"))]
5443 async fn sep_2243_strict_mode_tools_call_with_matching_headers() {
5444 use crate::{CallToolResult, ToolBuilder};
5445
5446 let router = McpRouter::new().server_info("t", "1.0.0").tool(
5447 ToolBuilder::new("echo")
5448 .description("echo")
5449 .handler(|args: serde_json::Value| async move {
5450 Ok(CallToolResult::text(args.to_string()))
5451 })
5452 .build(),
5453 );
5454 let transport = HttpTransport::new(router).disable_origin_validation();
5455 let app = transport.into_router();
5456
5457 let init = Request::builder()
5462 .method("POST")
5463 .uri("/")
5464 .header("Content-Type", "application/json")
5465 .header("Accept", "application/json, text/event-stream")
5466 .header(MCP_METHOD_HEADER, "initialize")
5467 .body(Body::from(
5468 serde_json::json!({
5469 "jsonrpc": "2.0",
5470 "id": 1,
5471 "method": "initialize",
5472 "params": {
5473 "protocolVersion": "2026-07-28",
5474 "capabilities": {},
5475 "clientInfo": { "name": "t", "version": "0" }
5476 }
5477 })
5478 .to_string(),
5479 ))
5480 .unwrap();
5481 let init_response = app.clone().oneshot(init).await.unwrap();
5482 assert_eq!(init_response.status(), StatusCode::OK);
5483 let session_id = init_response
5484 .headers()
5485 .get(MCP_SESSION_ID_HEADER)
5486 .unwrap()
5487 .to_str()
5488 .unwrap()
5489 .to_string();
5490 let negotiated_version = init_response
5491 .headers()
5492 .get(MCP_PROTOCOL_VERSION_HEADER)
5493 .unwrap()
5494 .to_str()
5495 .unwrap()
5496 .to_string();
5497
5498 let req = Request::builder()
5505 .method("POST")
5506 .uri("/")
5507 .header("Content-Type", "application/json")
5508 .header("Accept", "application/json")
5509 .header(MCP_SESSION_ID_HEADER, &session_id)
5510 .header(MCP_PROTOCOL_VERSION_HEADER, &negotiated_version)
5511 .header(MCP_METHOD_HEADER, "tools/call")
5512 .header(MCP_NAME_HEADER, "echo")
5513 .body(Body::from(
5514 serde_json::json!({
5515 "jsonrpc": "2.0",
5516 "id": 2,
5517 "method": "tools/call",
5518 "params": {
5519 "name": "echo",
5520 "arguments": {"message": "hi"}
5521 }
5522 })
5523 .to_string(),
5524 ))
5525 .unwrap();
5526 let response = app.oneshot(req).await.unwrap();
5527 assert_eq!(response.status(), StatusCode::OK);
5528 }
5529
5530 #[tokio::test]
5533 async fn sep_2243_tools_call_mcp_name_mismatch_rejected() {
5534 use crate::{CallToolResult, ToolBuilder};
5535
5536 let router = McpRouter::new().server_info("t", "1.0.0").tool(
5537 ToolBuilder::new("echo")
5538 .description("echo")
5539 .handler(|args: serde_json::Value| async move {
5540 Ok(CallToolResult::text(args.to_string()))
5541 })
5542 .build(),
5543 );
5544 let transport = HttpTransport::new(router).disable_origin_validation();
5545 let app = transport.into_router();
5546
5547 let init = Request::builder()
5548 .method("POST")
5549 .uri("/")
5550 .header("Content-Type", "application/json")
5551 .header("Accept", "application/json, text/event-stream")
5552 .body(Body::from(
5553 serde_json::json!({
5554 "jsonrpc": "2.0",
5555 "id": 1,
5556 "method": "initialize",
5557 "params": {
5558 "protocolVersion": "2025-11-25",
5559 "capabilities": {},
5560 "clientInfo": { "name": "t", "version": "0" }
5561 }
5562 })
5563 .to_string(),
5564 ))
5565 .unwrap();
5566 let init_response = app.clone().oneshot(init).await.unwrap();
5567 let session_id = init_response
5568 .headers()
5569 .get(MCP_SESSION_ID_HEADER)
5570 .unwrap()
5571 .to_str()
5572 .unwrap()
5573 .to_string();
5574
5575 let req = Request::builder()
5576 .method("POST")
5577 .uri("/")
5578 .header("Content-Type", "application/json")
5579 .header("Accept", "application/json")
5580 .header(MCP_SESSION_ID_HEADER, &session_id)
5581 .header(MCP_METHOD_HEADER, "tools/call")
5582 .header(MCP_NAME_HEADER, "not-echo")
5583 .body(Body::from(
5584 serde_json::json!({
5585 "jsonrpc": "2.0",
5586 "id": 7,
5587 "method": "tools/call",
5588 "params": {
5589 "name": "echo",
5590 "arguments": {"message": "hi"}
5591 }
5592 })
5593 .to_string(),
5594 ))
5595 .unwrap();
5596 let response = app.oneshot(req).await.unwrap();
5597 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
5598 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
5599 .await
5600 .unwrap();
5601 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
5602 assert_eq!(json["error"]["code"].as_i64().unwrap(), -32020);
5603 let msg = json["error"]["message"].as_str().unwrap();
5604 assert!(msg.contains("Mcp-Name"), "got: {msg}");
5605 assert_eq!(json["id"], 7);
5606 }
5607
5608 #[tokio::test]
5611 async fn sep_2243_mcp_param_base64_decoded_and_matched() {
5612 use crate::{CallToolResult, ToolBuilder};
5613
5614 let router = McpRouter::new().server_info("t", "1.0.0").tool(
5615 ToolBuilder::new("echo")
5616 .description("echo")
5617 .handler(|args: serde_json::Value| async move {
5618 Ok(CallToolResult::text(args.to_string()))
5619 })
5620 .build(),
5621 );
5622 let transport = HttpTransport::new(router).disable_origin_validation();
5623 let app = transport.into_router();
5624
5625 let init = Request::builder()
5626 .method("POST")
5627 .uri("/")
5628 .header("Content-Type", "application/json")
5629 .header("Accept", "application/json, text/event-stream")
5630 .body(Body::from(
5631 serde_json::json!({
5632 "jsonrpc": "2.0",
5633 "id": 1,
5634 "method": "initialize",
5635 "params": {
5636 "protocolVersion": "2025-11-25",
5637 "capabilities": {},
5638 "clientInfo": { "name": "t", "version": "0" }
5639 }
5640 })
5641 .to_string(),
5642 ))
5643 .unwrap();
5644 let init_response = app.clone().oneshot(init).await.unwrap();
5645 let session_id = init_response
5646 .headers()
5647 .get(MCP_SESSION_ID_HEADER)
5648 .unwrap()
5649 .to_str()
5650 .unwrap()
5651 .to_string();
5652
5653 let req = Request::builder()
5655 .method("POST")
5656 .uri("/")
5657 .header("Content-Type", "application/json")
5658 .header("Accept", "application/json")
5659 .header(MCP_SESSION_ID_HEADER, &session_id)
5660 .header(MCP_METHOD_HEADER, "tools/call")
5661 .header(MCP_NAME_HEADER, "echo")
5662 .header("mcp-param-message", "=?base64?SGVsbG8=?=")
5663 .body(Body::from(
5664 serde_json::json!({
5665 "jsonrpc": "2.0",
5666 "id": 5,
5667 "method": "tools/call",
5668 "params": {
5669 "name": "echo",
5670 "arguments": {"message": "Hello"}
5671 }
5672 })
5673 .to_string(),
5674 ))
5675 .unwrap();
5676 let response = app.oneshot(req).await.unwrap();
5677 assert_eq!(response.status(), StatusCode::OK);
5678 }
5679
5680 #[cfg(feature = "stateless")]
5681 #[tokio::test]
5682 async fn sep_2243_final_request_requires_schema_annotated_header() {
5683 use crate::extract::RawArgs;
5684 use crate::{CallToolResult, ToolBuilder};
5685
5686 let tool = ToolBuilder::new("route")
5687 .input_schema(serde_json::json!({
5688 "type": "object",
5689 "properties": {
5690 "tenant_id": {
5691 "type": "string",
5692 "x-mcp-header": "Tenant"
5693 }
5694 }
5695 }))
5696 .extractor_handler((), |RawArgs(args): RawArgs| async move {
5697 Ok(CallToolResult::text(args["tenant_id"].to_string()))
5698 })
5699 .build();
5700 let app = HttpTransport::new(
5701 McpRouter::new()
5702 .server_info("header-test", "1.0.0")
5703 .tool(tool),
5704 )
5705 .disable_origin_validation()
5706 .into_router();
5707
5708 let request = |custom_header: Option<&'static str>| {
5709 let mut builder = Request::builder()
5710 .method("POST")
5711 .uri("/")
5712 .header("Content-Type", "application/json")
5713 .header(MCP_PROTOCOL_VERSION_HEADER, PROTOCOL_VERSION_2026_07_28)
5714 .header(MCP_METHOD_HEADER, "tools/call")
5715 .header(MCP_NAME_HEADER, "route");
5716 if let Some(value) = custom_header {
5717 builder = builder.header("Mcp-Param-Tenant", value);
5718 }
5719 builder
5720 .body(Body::from(
5721 serde_json::json!({
5722 "jsonrpc": "2.0",
5723 "id": 9,
5724 "method": "tools/call",
5725 "params": {
5726 "name": "route",
5727 "arguments": {"tenant_id": "acme"},
5728 "_meta": {
5729 "io.modelcontextprotocol/protocolVersion":
5730 PROTOCOL_VERSION_2026_07_28,
5731 "io.modelcontextprotocol/clientCapabilities": {}
5732 }
5733 }
5734 })
5735 .to_string(),
5736 ))
5737 .unwrap()
5738 };
5739
5740 let response = app.clone().oneshot(request(None)).await.unwrap();
5741 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
5742 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
5743 .await
5744 .unwrap();
5745 let error: serde_json::Value = serde_json::from_slice(&body).unwrap();
5746 assert_eq!(error["id"], 9);
5747 assert_eq!(error["error"]["code"], -32020);
5748
5749 let response = app.oneshot(request(Some("acme"))).await.unwrap();
5750 assert_eq!(response.status(), StatusCode::OK);
5751 }
5752
5753 #[tokio::test]
5756 async fn sep_2243_notification_with_matching_method_header_accepted() {
5757 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
5758 let app = transport.into_router();
5759
5760 let init = Request::builder()
5761 .method("POST")
5762 .uri("/")
5763 .header("Content-Type", "application/json")
5764 .header("Accept", "application/json, text/event-stream")
5765 .body(Body::from(
5766 serde_json::json!({
5767 "jsonrpc": "2.0",
5768 "id": 1,
5769 "method": "initialize",
5770 "params": {
5771 "protocolVersion": "2025-11-25",
5772 "capabilities": {},
5773 "clientInfo": { "name": "t", "version": "0" }
5774 }
5775 })
5776 .to_string(),
5777 ))
5778 .unwrap();
5779 let init_response = app.clone().oneshot(init).await.unwrap();
5780 let session_id = init_response
5781 .headers()
5782 .get(MCP_SESSION_ID_HEADER)
5783 .unwrap()
5784 .to_str()
5785 .unwrap()
5786 .to_string();
5787
5788 let req = Request::builder()
5789 .method("POST")
5790 .uri("/")
5791 .header("Content-Type", "application/json")
5792 .header("Accept", "application/json, text/event-stream")
5793 .header(MCP_SESSION_ID_HEADER, &session_id)
5794 .header(MCP_METHOD_HEADER, "notifications/initialized")
5795 .body(Body::from(
5796 serde_json::json!({
5797 "jsonrpc": "2.0",
5798 "method": "notifications/initialized"
5799 })
5800 .to_string(),
5801 ))
5802 .unwrap();
5803 let response = app.oneshot(req).await.unwrap();
5804 assert_eq!(response.status(), StatusCode::ACCEPTED);
5805 }
5806
5807 #[tokio::test]
5808 async fn test_request_without_session_fails() {
5809 let transport = HttpTransport::new(create_test_router())
5810 .disable_origin_validation()
5811 .require_sessions();
5812 let app = transport.into_router();
5813
5814 let request = Request::builder()
5815 .method("POST")
5816 .uri("/")
5817 .header("Content-Type", "application/json")
5818 .body(Body::from(
5819 serde_json::json!({
5820 "jsonrpc": "2.0",
5821 "id": 1,
5822 "method": "tools/list"
5823 })
5824 .to_string(),
5825 ))
5826 .unwrap();
5827
5828 let response = app.oneshot(request).await.unwrap();
5829
5830 assert_eq!(response.status(), StatusCode::OK);
5832
5833 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
5835 .await
5836 .unwrap();
5837 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
5838 assert!(json.get("error").is_some());
5839 assert_eq!(json["error"]["code"], -32006); }
5841
5842 #[tokio::test]
5843 async fn test_delete_session() {
5844 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
5845 let app = transport.into_router();
5846
5847 let init_request = Request::builder()
5849 .method("POST")
5850 .uri("/")
5851 .header("Content-Type", "application/json")
5852 .header("Accept", "application/json, text/event-stream")
5853 .body(Body::from(
5854 serde_json::json!({
5855 "jsonrpc": "2.0",
5856 "id": 1,
5857 "method": "initialize",
5858 "params": {
5859 "protocolVersion": "2025-11-25",
5860 "capabilities": {},
5861 "clientInfo": {
5862 "name": "test-client",
5863 "version": "1.0.0"
5864 }
5865 }
5866 })
5867 .to_string(),
5868 ))
5869 .unwrap();
5870
5871 let response = app.clone().oneshot(init_request).await.unwrap();
5872 let session_id = response
5873 .headers()
5874 .get(MCP_SESSION_ID_HEADER)
5875 .unwrap()
5876 .to_str()
5877 .unwrap()
5878 .to_string();
5879
5880 let delete_request = Request::builder()
5882 .method("DELETE")
5883 .uri("/")
5884 .header(MCP_SESSION_ID_HEADER, &session_id)
5885 .body(Body::empty())
5886 .unwrap();
5887
5888 let response = app.clone().oneshot(delete_request).await.unwrap();
5889 assert_eq!(response.status(), StatusCode::OK);
5890
5891 let list_request = Request::builder()
5893 .method("POST")
5894 .uri("/")
5895 .header("Content-Type", "application/json")
5896 .header(MCP_SESSION_ID_HEADER, &session_id)
5897 .body(Body::from(
5898 serde_json::json!({
5899 "jsonrpc": "2.0",
5900 "id": 2,
5901 "method": "tools/list"
5902 })
5903 .to_string(),
5904 ))
5905 .unwrap();
5906
5907 let response = app.oneshot(list_request).await.unwrap();
5908 assert_eq!(response.status(), StatusCode::OK);
5910
5911 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
5913 .await
5914 .unwrap();
5915 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
5916 assert!(json.get("error").is_some());
5917 assert_eq!(json["error"]["code"], -32005); }
5919
5920 #[tokio::test]
5921 async fn test_custom_session_store_receives_create_and_delete() {
5922 use crate::session_store::{MemorySessionStore, SessionStore as PublicSessionStore};
5923
5924 let store = Arc::new(MemorySessionStore::new());
5925 let store_dyn: Arc<dyn PublicSessionStore> = store.clone();
5926
5927 let transport = HttpTransport::new(create_test_router())
5928 .disable_origin_validation()
5929 .session_store(store_dyn);
5930 let (app, handle) = transport.into_router_with_handle();
5931
5932 let init_request = Request::builder()
5934 .method("POST")
5935 .uri("/")
5936 .header("Content-Type", "application/json")
5937 .header("Accept", "application/json, text/event-stream")
5938 .body(Body::from(
5939 serde_json::json!({
5940 "jsonrpc": "2.0",
5941 "id": 1,
5942 "method": "initialize",
5943 "params": {
5944 "protocolVersion": "2025-11-25",
5945 "capabilities": {},
5946 "clientInfo": { "name": "test-client", "version": "1.0.0" }
5947 }
5948 })
5949 .to_string(),
5950 ))
5951 .unwrap();
5952
5953 let response = app.clone().oneshot(init_request).await.unwrap();
5954 assert_eq!(response.status(), StatusCode::OK);
5955 let session_id = response
5956 .headers()
5957 .get(MCP_SESSION_ID_HEADER)
5958 .unwrap()
5959 .to_str()
5960 .unwrap()
5961 .to_string();
5962
5963 assert_eq!(store.len().await, 1);
5965 let record = store
5966 .load(&session_id)
5967 .await
5968 .unwrap()
5969 .expect("expected session to be persisted");
5970 assert_eq!(record.id, session_id);
5971
5972 let client_info = record
5977 .client_info
5978 .expect("client_info should be populated after initialize");
5979 assert_eq!(client_info.name, "test-client");
5980 assert_eq!(client_info.version, "1.0.0");
5981 assert!(
5982 record.client_capabilities.is_some(),
5983 "client_capabilities should be populated after initialize"
5984 );
5985
5986 assert!(handle.terminate_session(&session_id).await);
5988 assert_eq!(store.len().await, 0);
5989 assert!(store.load(&session_id).await.unwrap().is_none());
5990 }
5991
5992 #[tokio::test]
5993 async fn test_session_store_record_carries_negotiated_protocol_version() {
5994 use crate::session_store::{MemorySessionStore, SessionStore as PublicSessionStore};
5998
5999 let store = Arc::new(MemorySessionStore::new());
6000 let store_dyn: Arc<dyn PublicSessionStore> = store.clone();
6001
6002 let transport = HttpTransport::new(create_test_router())
6003 .disable_origin_validation()
6004 .session_store(store_dyn);
6005 let app = transport.into_router();
6006
6007 let init_request = Request::builder()
6010 .method("POST")
6011 .uri("/")
6012 .header("Content-Type", "application/json")
6013 .header("Accept", "application/json, text/event-stream")
6014 .body(Body::from(
6015 serde_json::json!({
6016 "jsonrpc": "2.0",
6017 "id": 1,
6018 "method": "initialize",
6019 "params": {
6020 "protocolVersion": "2025-03-26",
6021 "capabilities": {},
6022 "clientInfo": { "name": "v-client", "version": "2.0.0" }
6023 }
6024 })
6025 .to_string(),
6026 ))
6027 .unwrap();
6028
6029 let response = app.oneshot(init_request).await.unwrap();
6030 assert_eq!(response.status(), StatusCode::OK);
6031 let session_id = response
6032 .headers()
6033 .get(MCP_SESSION_ID_HEADER)
6034 .unwrap()
6035 .to_str()
6036 .unwrap()
6037 .to_string();
6038
6039 let record = store
6040 .load(&session_id)
6041 .await
6042 .unwrap()
6043 .expect("session should be persisted");
6044 assert_eq!(record.protocol_version, "2025-03-26");
6045 let client_info = record.client_info.expect("client_info should be populated");
6046 assert_eq!(client_info.name, "v-client");
6047 }
6048
6049 #[tokio::test]
6050 async fn test_restored_session_exposes_original_client_info() {
6051 use crate::session_store::{MemorySessionStore, SessionStore as PublicSessionStore};
6055
6056 let store = Arc::new(MemorySessionStore::new());
6057 let store_dyn: Arc<dyn PublicSessionStore> = store.clone();
6058
6059 let session_id = {
6062 let transport = HttpTransport::new(create_test_router())
6063 .disable_origin_validation()
6064 .session_store(store_dyn.clone());
6065 let app = transport.into_router();
6066
6067 let init_request = Request::builder()
6068 .method("POST")
6069 .uri("/")
6070 .header("Content-Type", "application/json")
6071 .header("Accept", "application/json, text/event-stream")
6072 .body(Body::from(
6073 serde_json::json!({
6074 "jsonrpc": "2.0",
6075 "id": 1,
6076 "method": "initialize",
6077 "params": {
6078 "protocolVersion": "2025-11-25",
6079 "capabilities": { "roots": {} },
6080 "clientInfo": {
6081 "name": "original-client",
6082 "version": "3.1.4"
6083 }
6084 }
6085 })
6086 .to_string(),
6087 ))
6088 .unwrap();
6089 let response = app.oneshot(init_request).await.unwrap();
6090 assert_eq!(response.status(), StatusCode::OK);
6091 response
6092 .headers()
6093 .get(MCP_SESSION_ID_HEADER)
6094 .unwrap()
6095 .to_str()
6096 .unwrap()
6097 .to_string()
6098 };
6099
6100 let stored = store
6102 .load(&session_id)
6103 .await
6104 .unwrap()
6105 .expect("record should survive transport drop");
6106 assert_eq!(
6107 stored.client_info.as_ref().map(|c| c.name.as_str()),
6108 Some("original-client")
6109 );
6110
6111 let transport2 = HttpTransport::new(create_test_router())
6114 .disable_origin_validation()
6115 .session_store(store_dyn);
6116 let app2 = transport2.into_router();
6117
6118 let list_request = Request::builder()
6119 .method("POST")
6120 .uri("/")
6121 .header("Content-Type", "application/json")
6122 .header(MCP_SESSION_ID_HEADER, &session_id)
6123 .body(Body::from(
6124 serde_json::json!({
6125 "jsonrpc": "2.0",
6126 "id": 1,
6127 "method": "tools/list"
6128 })
6129 .to_string(),
6130 ))
6131 .unwrap();
6132 let response = app2.oneshot(list_request).await.unwrap();
6133 assert_eq!(response.status(), StatusCode::OK);
6134 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
6135 .await
6136 .unwrap();
6137 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
6138 assert!(
6139 json.get("result").is_some(),
6140 "expected tools/list result, got {json}"
6141 );
6142
6143 let after_restore = store
6147 .load(&session_id)
6148 .await
6149 .unwrap()
6150 .expect("record should still be present after restore");
6151 let client_info = after_restore
6152 .client_info
6153 .expect("restored record should retain client_info");
6154 assert_eq!(client_info.name, "original-client");
6155 assert_eq!(client_info.version, "3.1.4");
6156 assert!(
6157 after_restore.client_capabilities.is_some(),
6158 "restored record should retain client_capabilities"
6159 );
6160 }
6161
6162 #[tokio::test]
6163 async fn test_auto_reinitialize_marks_synthetic_client_info() {
6164 use crate::session_store::{MemorySessionStore, SessionStore as PublicSessionStore};
6169
6170 let store = Arc::new(MemorySessionStore::new());
6171 let store_dyn: Arc<dyn PublicSessionStore> = store.clone();
6172
6173 let transport = HttpTransport::new(create_test_router())
6174 .disable_origin_validation()
6175 .session_store(store_dyn)
6176 .auto_reinitialize_sessions(true);
6177 let app = transport.into_router();
6178
6179 let list_request = Request::builder()
6180 .method("POST")
6181 .uri("/")
6182 .header("Content-Type", "application/json")
6183 .header(MCP_SESSION_ID_HEADER, "made-up-id")
6184 .body(Body::from(
6185 serde_json::json!({
6186 "jsonrpc": "2.0",
6187 "id": 1,
6188 "method": "tools/list"
6189 })
6190 .to_string(),
6191 ))
6192 .unwrap();
6193 let response = app.oneshot(list_request).await.unwrap();
6194 assert_eq!(response.status(), StatusCode::OK);
6195
6196 let record = store
6197 .load("made-up-id")
6198 .await
6199 .unwrap()
6200 .expect("auto-reinitialize should persist a record");
6201 assert_eq!(
6202 record.client_info.as_ref().map(|c| c.name.as_str()),
6203 Some("auto-recovered")
6204 );
6205 }
6206
6207 #[tokio::test]
6208 async fn test_custom_event_store_buffers_and_purges() {
6209 use crate::event_store::{EventStore as PublicEventStore, MemoryEventStore};
6210
6211 let events = Arc::new(MemoryEventStore::new());
6212 let events_dyn: Arc<dyn PublicEventStore> = events.clone();
6213
6214 let session = Arc::new(Session::new(
6217 create_test_router(),
6218 false,
6219 identity_factory(),
6220 events_dyn,
6221 ));
6222
6223 session.buffer_event(0, "first".to_string()).await;
6224 session.buffer_event(1, "second".to_string()).await;
6225
6226 assert_eq!(events.total_events().await, 2);
6228 let replayed = events.replay_after(&session.id, 0).await.unwrap();
6229 assert_eq!(replayed.len(), 1);
6230 assert_eq!(replayed[0].id, 1);
6231 assert_eq!(replayed[0].data, "second");
6232
6233 events.purge_session(&session.id).await.unwrap();
6235 assert_eq!(events.total_events().await, 0);
6236 }
6237
6238 #[tokio::test]
6239 async fn test_restore_from_store_serves_unknown_session_id() {
6240 use crate::session_store::{MemorySessionStore, SessionRecord, SessionStore};
6241
6242 let store = Arc::new(MemorySessionStore::new());
6245 let store_dyn: Arc<dyn SessionStore> = store.clone();
6246
6247 let mut seeded = SessionRecord::new(
6249 "shared-session".to_string(),
6250 "2025-11-25".to_string(),
6251 Duration::from_secs(60),
6252 );
6253 store.create(&mut seeded).await.unwrap();
6254 let seeded_id = seeded.id;
6255
6256 let transport = HttpTransport::new(create_test_router())
6258 .disable_origin_validation()
6259 .session_store(store_dyn);
6260 let app = transport.into_router();
6261
6262 let list_request = Request::builder()
6263 .method("POST")
6264 .uri("/")
6265 .header("Content-Type", "application/json")
6266 .header(MCP_SESSION_ID_HEADER, &seeded_id)
6267 .body(Body::from(
6268 serde_json::json!({
6269 "jsonrpc": "2.0",
6270 "id": 1,
6271 "method": "tools/list"
6272 })
6273 .to_string(),
6274 ))
6275 .unwrap();
6276
6277 let response = app.oneshot(list_request).await.unwrap();
6278 assert_eq!(response.status(), StatusCode::OK);
6281
6282 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
6283 .await
6284 .unwrap();
6285 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
6286 assert!(
6287 json.get("result").is_some(),
6288 "expected tools/list result, got {json}"
6289 );
6290 }
6291
6292 #[tokio::test]
6293 async fn test_auto_reinitialize_serves_unknown_session_without_store_record() {
6294 let transport = HttpTransport::new(create_test_router())
6298 .disable_origin_validation()
6299 .auto_reinitialize_sessions(true);
6300 let app = transport.into_router();
6301
6302 let list_request = Request::builder()
6303 .method("POST")
6304 .uri("/")
6305 .header("Content-Type", "application/json")
6306 .header(MCP_SESSION_ID_HEADER, "client-made-up-id")
6307 .body(Body::from(
6308 serde_json::json!({
6309 "jsonrpc": "2.0",
6310 "id": 1,
6311 "method": "tools/list"
6312 })
6313 .to_string(),
6314 ))
6315 .unwrap();
6316
6317 let response = app.oneshot(list_request).await.unwrap();
6318 assert_eq!(response.status(), StatusCode::OK);
6319 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
6320 .await
6321 .unwrap();
6322 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
6323 assert!(
6324 json.get("result").is_some(),
6325 "expected tools/list result, got {json}"
6326 );
6327 }
6328
6329 #[tokio::test]
6330 async fn test_unknown_session_without_restore_or_auto_reinit_returns_error() {
6331 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
6333 let app = transport.into_router();
6334
6335 let list_request = Request::builder()
6336 .method("POST")
6337 .uri("/")
6338 .header("Content-Type", "application/json")
6339 .header(MCP_SESSION_ID_HEADER, "never-seen-before")
6340 .body(Body::from(
6341 serde_json::json!({
6342 "jsonrpc": "2.0",
6343 "id": 1,
6344 "method": "tools/list"
6345 })
6346 .to_string(),
6347 ))
6348 .unwrap();
6349
6350 let response = app.oneshot(list_request).await.unwrap();
6351 assert_eq!(response.status(), StatusCode::OK);
6352 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
6353 .await
6354 .unwrap();
6355 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
6356 assert!(json.get("error").is_some(), "expected error, got {json}");
6357 assert_eq!(json["error"]["code"], -32005); }
6359
6360 #[tokio::test]
6361 async fn test_session_expiration() {
6362 let config = SessionConfig::with_ttl(Duration::from_millis(50))
6364 .cleanup_interval(Duration::from_millis(10));
6365 let transport = HttpTransport::new(create_test_router())
6366 .disable_origin_validation()
6367 .session_config(config);
6368 let app = transport.into_router();
6369
6370 let init_request = Request::builder()
6372 .method("POST")
6373 .uri("/")
6374 .header("Content-Type", "application/json")
6375 .header("Accept", "application/json, text/event-stream")
6376 .body(Body::from(
6377 serde_json::json!({
6378 "jsonrpc": "2.0",
6379 "id": 1,
6380 "method": "initialize",
6381 "params": {
6382 "protocolVersion": "2025-11-25",
6383 "capabilities": {},
6384 "clientInfo": {
6385 "name": "test-client",
6386 "version": "1.0.0"
6387 }
6388 }
6389 })
6390 .to_string(),
6391 ))
6392 .unwrap();
6393
6394 let response = app.clone().oneshot(init_request).await.unwrap();
6395 assert_eq!(response.status(), StatusCode::OK);
6396 let session_id = response
6397 .headers()
6398 .get(MCP_SESSION_ID_HEADER)
6399 .unwrap()
6400 .to_str()
6401 .unwrap()
6402 .to_string();
6403
6404 tokio::time::sleep(Duration::from_millis(100)).await;
6406
6407 let list_request = Request::builder()
6409 .method("POST")
6410 .uri("/")
6411 .header("Content-Type", "application/json")
6412 .header(MCP_SESSION_ID_HEADER, &session_id)
6413 .body(Body::from(
6414 serde_json::json!({
6415 "jsonrpc": "2.0",
6416 "id": 2,
6417 "method": "tools/list"
6418 })
6419 .to_string(),
6420 ))
6421 .unwrap();
6422
6423 let response = app.oneshot(list_request).await.unwrap();
6424 assert_eq!(response.status(), StatusCode::OK);
6426
6427 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
6429 .await
6430 .unwrap();
6431 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
6432 assert!(json.get("error").is_some());
6433 assert_eq!(json["error"]["code"], -32005); }
6435
6436 #[tokio::test]
6437 async fn test_layer_with_identity() {
6438 let transport = HttpTransport::new(create_test_router())
6441 .disable_origin_validation()
6442 .layer(tower::layer::util::Identity::new());
6443 let app = transport.into_router();
6444
6445 let request = Request::builder()
6446 .method("POST")
6447 .uri("/")
6448 .header("Content-Type", "application/json")
6449 .header("Accept", "application/json, text/event-stream")
6450 .body(Body::from(
6451 serde_json::json!({
6452 "jsonrpc": "2.0",
6453 "id": 1,
6454 "method": "initialize",
6455 "params": {
6456 "protocolVersion": "2025-11-25",
6457 "capabilities": {},
6458 "clientInfo": {
6459 "name": "test-client",
6460 "version": "1.0.0"
6461 }
6462 }
6463 })
6464 .to_string(),
6465 ))
6466 .unwrap();
6467
6468 let response = app.oneshot(request).await.unwrap();
6469 assert_eq!(response.status(), StatusCode::OK);
6470 assert!(response.headers().contains_key(MCP_SESSION_ID_HEADER));
6471 }
6472
6473 #[tokio::test]
6474 async fn test_layer_with_timeout() {
6475 use std::time::Duration;
6477 use tower::timeout::TimeoutLayer;
6478
6479 let transport = HttpTransport::new(create_test_router())
6480 .disable_origin_validation()
6481 .layer(TimeoutLayer::new(Duration::from_secs(30)));
6482 let app = transport.into_router();
6483
6484 let request = Request::builder()
6485 .method("POST")
6486 .uri("/")
6487 .header("Content-Type", "application/json")
6488 .header("Accept", "application/json, text/event-stream")
6489 .body(Body::from(
6490 serde_json::json!({
6491 "jsonrpc": "2.0",
6492 "id": 1,
6493 "method": "initialize",
6494 "params": {
6495 "protocolVersion": "2025-11-25",
6496 "capabilities": {},
6497 "clientInfo": {
6498 "name": "test-client",
6499 "version": "1.0.0"
6500 }
6501 }
6502 })
6503 .to_string(),
6504 ))
6505 .unwrap();
6506
6507 let response = app.oneshot(request).await.unwrap();
6508 assert_eq!(response.status(), StatusCode::OK);
6509 assert!(response.headers().contains_key(MCP_SESSION_ID_HEADER));
6510 }
6511
6512 #[tokio::test]
6513 async fn test_layer_middleware_error_produces_jsonrpc_error() {
6514 use std::time::Duration;
6517 use tower::timeout::TimeoutLayer;
6518
6519 let slow_tool = crate::tool::ToolBuilder::new("slow")
6520 .description("A slow tool")
6521 .handler(|_: serde_json::Value| async move {
6522 tokio::time::sleep(Duration::from_secs(10)).await;
6523 Ok(crate::CallToolResult::text("done"))
6524 })
6525 .build();
6526
6527 let router = McpRouter::new()
6528 .server_info("test-server", "1.0.0")
6529 .tool(slow_tool);
6530
6531 let transport = HttpTransport::new(router)
6533 .disable_origin_validation()
6534 .layer(TimeoutLayer::new(Duration::from_millis(1)));
6535 let app = transport.into_router();
6536
6537 let init_request = Request::builder()
6539 .method("POST")
6540 .uri("/")
6541 .header("Content-Type", "application/json")
6542 .header("Accept", "application/json, text/event-stream")
6543 .body(Body::from(
6544 serde_json::json!({
6545 "jsonrpc": "2.0",
6546 "id": 1,
6547 "method": "initialize",
6548 "params": {
6549 "protocolVersion": "2025-11-25",
6550 "capabilities": {},
6551 "clientInfo": {
6552 "name": "test-client",
6553 "version": "1.0.0"
6554 }
6555 }
6556 })
6557 .to_string(),
6558 ))
6559 .unwrap();
6560
6561 let response = app.clone().oneshot(init_request).await.unwrap();
6562 let session_id = response
6563 .headers()
6564 .get(MCP_SESSION_ID_HEADER)
6565 .unwrap()
6566 .to_str()
6567 .unwrap()
6568 .to_string();
6569
6570 let tool_request = Request::builder()
6572 .method("POST")
6573 .uri("/")
6574 .header("Content-Type", "application/json")
6575 .header(MCP_SESSION_ID_HEADER, &session_id)
6576 .body(Body::from(
6577 serde_json::json!({
6578 "jsonrpc": "2.0",
6579 "id": 2,
6580 "method": "tools/call",
6581 "params": {
6582 "name": "slow",
6583 "arguments": {}
6584 }
6585 })
6586 .to_string(),
6587 ))
6588 .unwrap();
6589
6590 let response = app.oneshot(tool_request).await.unwrap();
6591 assert_eq!(response.status(), StatusCode::OK);
6593
6594 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
6595 .await
6596 .unwrap();
6597 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
6598 assert!(
6599 json.get("error").is_some(),
6600 "Expected JSON-RPC error response, got: {}",
6601 json
6602 );
6603 }
6604
6605 #[tokio::test]
6606 async fn test_max_sessions_limit() {
6607 let config = SessionConfig::default().max_sessions(1);
6609 let transport = HttpTransport::new(create_test_router())
6610 .disable_origin_validation()
6611 .session_config(config);
6612 let app = transport.into_router();
6613
6614 let init_request1 = Request::builder()
6616 .method("POST")
6617 .uri("/")
6618 .header("Content-Type", "application/json")
6619 .header("Accept", "application/json, text/event-stream")
6620 .body(Body::from(
6621 serde_json::json!({
6622 "jsonrpc": "2.0",
6623 "id": 1,
6624 "method": "initialize",
6625 "params": {
6626 "protocolVersion": "2025-11-25",
6627 "capabilities": {},
6628 "clientInfo": {
6629 "name": "test-client",
6630 "version": "1.0.0"
6631 }
6632 }
6633 })
6634 .to_string(),
6635 ))
6636 .unwrap();
6637
6638 let response = app.clone().oneshot(init_request1).await.unwrap();
6639 assert_eq!(response.status(), StatusCode::OK);
6640
6641 let init_request2 = Request::builder()
6643 .method("POST")
6644 .uri("/")
6645 .header("Content-Type", "application/json")
6646 .header("Accept", "application/json, text/event-stream")
6647 .body(Body::from(
6648 serde_json::json!({
6649 "jsonrpc": "2.0",
6650 "id": 2,
6651 "method": "initialize",
6652 "params": {
6653 "protocolVersion": "2025-11-25",
6654 "capabilities": {},
6655 "clientInfo": {
6656 "name": "test-client-2",
6657 "version": "1.0.0"
6658 }
6659 }
6660 })
6661 .to_string(),
6662 ))
6663 .unwrap();
6664
6665 let response = app.oneshot(init_request2).await.unwrap();
6666 assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
6667 }
6668
6669 #[tokio::test]
6670 async fn test_session_event_buffering() {
6671 let session = Session::new(
6673 create_test_router(),
6674 false,
6675 identity_factory(),
6676 Arc::new(crate::event_store::MemoryEventStore::new()),
6677 );
6678
6679 session.buffer_event(0, "event0".to_string()).await;
6681 session.buffer_event(1, "event1".to_string()).await;
6682 session.buffer_event(2, "event2".to_string()).await;
6683
6684 let events = session.get_events_after(0).await;
6686 assert_eq!(events.len(), 2);
6687 assert_eq!(events[0].id, 1);
6688 assert_eq!(events[0].data, "event1");
6689 assert_eq!(events[1].id, 2);
6690 assert_eq!(events[1].data, "event2");
6691
6692 let events = session.get_events_after(1).await;
6694 assert_eq!(events.len(), 1);
6695 assert_eq!(events[0].id, 2);
6696
6697 let events = session.get_events_after(2).await;
6699 assert!(events.is_empty());
6700 }
6701
6702 #[tokio::test]
6703 async fn test_session_event_counter_increments() {
6704 let session = Session::new(
6706 create_test_router(),
6707 false,
6708 identity_factory(),
6709 Arc::new(crate::event_store::MemoryEventStore::new()),
6710 );
6711
6712 assert_eq!(session.next_event_id(), 0);
6713 assert_eq!(session.next_event_id(), 1);
6714 assert_eq!(session.next_event_id(), 2);
6715 }
6716
6717 #[tokio::test]
6718 async fn test_session_event_buffer_limit() {
6719 let session = Session::new(
6722 create_test_router(),
6723 false,
6724 identity_factory(),
6725 Arc::new(crate::event_store::MemoryEventStore::new()),
6726 );
6727
6728 for i in 0..10 {
6731 session.buffer_event(i, format!("event{}", i)).await;
6732 }
6733
6734 let events = session.get_events_after(0).await;
6736 assert_eq!(events.len(), 9);
6738 }
6739
6740 #[tokio::test]
6741 async fn test_session_handle_count() {
6742 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
6743 let (app, handle) = transport.into_router_with_handle();
6744
6745 assert_eq!(handle.session_count().await, 0);
6747
6748 let request = Request::builder()
6750 .method("POST")
6751 .uri("/")
6752 .header("Content-Type", "application/json")
6753 .header("Accept", "application/json, text/event-stream")
6754 .body(Body::from(
6755 serde_json::json!({
6756 "jsonrpc": "2.0",
6757 "id": 1,
6758 "method": "initialize",
6759 "params": {
6760 "protocolVersion": "2025-11-25",
6761 "capabilities": {},
6762 "clientInfo": {
6763 "name": "test-client",
6764 "version": "1.0.0"
6765 }
6766 }
6767 })
6768 .to_string(),
6769 ))
6770 .unwrap();
6771
6772 let response = app.oneshot(request).await.unwrap();
6773 assert_eq!(response.status(), 200);
6774
6775 assert_eq!(handle.session_count().await, 1);
6777 }
6778
6779 #[tokio::test]
6780 async fn test_session_handle_list_and_terminate() {
6781 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
6782 let (app, handle) = transport.into_router_with_handle();
6783
6784 assert!(handle.list_sessions().await.is_empty());
6786
6787 let request = Request::builder()
6789 .method("POST")
6790 .uri("/")
6791 .header("Content-Type", "application/json")
6792 .header("Accept", "application/json, text/event-stream")
6793 .body(Body::from(
6794 serde_json::json!({
6795 "jsonrpc": "2.0",
6796 "id": 1,
6797 "method": "initialize",
6798 "params": {
6799 "protocolVersion": "2025-11-25",
6800 "capabilities": {},
6801 "clientInfo": {
6802 "name": "test-client",
6803 "version": "1.0.0"
6804 }
6805 }
6806 })
6807 .to_string(),
6808 ))
6809 .unwrap();
6810
6811 let response = app.oneshot(request).await.unwrap();
6812 assert_eq!(response.status(), 200);
6813
6814 let sessions = handle.list_sessions().await;
6816 assert_eq!(sessions.len(), 1);
6817 assert!(!sessions[0].id.is_empty());
6818
6819 let session_id = sessions[0].id.clone();
6821 assert!(handle.terminate_session(&session_id).await);
6822 assert_eq!(handle.session_count().await, 0);
6823
6824 assert!(!handle.terminate_session(&session_id).await);
6826 }
6827
6828 #[tokio::test]
6829 async fn test_request_without_session_id_rejected() {
6830 let transport = HttpTransport::new(create_test_router())
6831 .disable_origin_validation()
6832 .require_sessions();
6833 let app = transport.into_router();
6834
6835 let request = Request::builder()
6836 .method("POST")
6837 .uri("/")
6838 .header("Content-Type", "application/json")
6839 .header("Accept", "application/json")
6840 .body(Body::from(
6842 serde_json::json!({
6843 "jsonrpc": "2.0",
6844 "id": 1,
6845 "method": "tools/list",
6846 "params": {}
6847 })
6848 .to_string(),
6849 ))
6850 .unwrap();
6851
6852 let response = app.oneshot(request).await.unwrap();
6853 assert_eq!(response.status(), StatusCode::OK); let body = axum::body::to_bytes(response.into_body(), usize::MAX)
6855 .await
6856 .unwrap();
6857 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
6858 assert!(json["error"].is_object());
6860 }
6861
6862 #[tokio::test]
6863 async fn test_invalid_session_id_returns_error() {
6864 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
6865 let app = transport.into_router();
6866
6867 let request = Request::builder()
6868 .method("POST")
6869 .uri("/")
6870 .header("Content-Type", "application/json")
6871 .header("Accept", "application/json")
6872 .header("mcp-session-id", "nonexistent-session-id")
6873 .body(Body::from(
6874 serde_json::json!({
6875 "jsonrpc": "2.0",
6876 "id": 1,
6877 "method": "tools/list",
6878 "params": {}
6879 })
6880 .to_string(),
6881 ))
6882 .unwrap();
6883
6884 let response = app.oneshot(request).await.unwrap();
6885 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
6886 .await
6887 .unwrap();
6888 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
6889 assert_eq!(json["error"]["code"].as_i64().unwrap(), -32005); }
6891
6892 #[tokio::test]
6893 async fn test_notification_returns_accepted() {
6894 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
6895 let app = transport.into_router();
6896
6897 let init_req = Request::builder()
6899 .method("POST")
6900 .uri("/")
6901 .header("Content-Type", "application/json")
6902 .header("Accept", "application/json, text/event-stream")
6903 .body(Body::from(
6904 serde_json::json!({
6905 "jsonrpc": "2.0",
6906 "id": 1,
6907 "method": "initialize",
6908 "params": {
6909 "protocolVersion": "2025-11-25",
6910 "capabilities": {},
6911 "clientInfo": { "name": "test", "version": "1.0" }
6912 }
6913 })
6914 .to_string(),
6915 ))
6916 .unwrap();
6917
6918 let resp = app.clone().oneshot(init_req).await.unwrap();
6919 let session_id = resp
6920 .headers()
6921 .get(MCP_SESSION_ID_HEADER)
6922 .unwrap()
6923 .to_str()
6924 .unwrap()
6925 .to_string();
6926
6927 let notif = Request::builder()
6929 .method("POST")
6930 .uri("/")
6931 .header("Content-Type", "application/json")
6932 .header("mcp-session-id", &session_id)
6933 .body(Body::from(
6934 serde_json::json!({
6935 "jsonrpc": "2.0",
6936 "method": "notifications/initialized"
6937 })
6938 .to_string(),
6939 ))
6940 .unwrap();
6941
6942 let response = app.oneshot(notif).await.unwrap();
6943 assert_eq!(response.status(), StatusCode::ACCEPTED);
6944 }
6945
6946 #[tokio::test]
6947 async fn test_invalid_json_returns_parse_error() {
6948 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
6949 let app = transport.into_router();
6950
6951 let request = Request::builder()
6952 .method("POST")
6953 .uri("/")
6954 .header("Content-Type", "application/json")
6955 .header("Accept", "application/json")
6956 .body(Body::from("not valid json{{{"))
6957 .unwrap();
6958
6959 let response = app.oneshot(request).await.unwrap();
6960 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
6961 .await
6962 .unwrap();
6963 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
6964 tower_mcp_types::testing::assert_jsonrpc_error_response(&json);
6965 assert!(
6966 json["id"].is_null(),
6967 "id must be null on parse error: {json}"
6968 );
6969 assert_eq!(json["error"]["code"].as_i64().unwrap(), -32700);
6970 }
6971
6972 #[tokio::test]
6973 async fn test_session_config_max_sessions() {
6974 let transport = HttpTransport::new(create_test_router())
6975 .disable_origin_validation()
6976 .session_config(SessionConfig::default().max_sessions(1));
6977 let app = transport.into_router();
6978
6979 let init1 = Request::builder()
6981 .method("POST")
6982 .uri("/")
6983 .header("Content-Type", "application/json")
6984 .header("Accept", "application/json, text/event-stream")
6985 .body(Body::from(
6986 serde_json::json!({
6987 "jsonrpc": "2.0",
6988 "id": 1,
6989 "method": "initialize",
6990 "params": {
6991 "protocolVersion": "2025-11-25",
6992 "capabilities": {},
6993 "clientInfo": { "name": "test1", "version": "1.0" }
6994 }
6995 })
6996 .to_string(),
6997 ))
6998 .unwrap();
6999
7000 let resp1 = app.clone().oneshot(init1).await.unwrap();
7001 assert_eq!(resp1.status(), StatusCode::OK);
7002
7003 let init2 = Request::builder()
7005 .method("POST")
7006 .uri("/")
7007 .header("Content-Type", "application/json")
7008 .header("Accept", "application/json, text/event-stream")
7009 .body(Body::from(
7010 serde_json::json!({
7011 "jsonrpc": "2.0",
7012 "id": 2,
7013 "method": "initialize",
7014 "params": {
7015 "protocolVersion": "2025-11-25",
7016 "capabilities": {},
7017 "clientInfo": { "name": "test2", "version": "1.0" }
7018 }
7019 })
7020 .to_string(),
7021 ))
7022 .unwrap();
7023
7024 let resp2 = app.oneshot(init2).await.unwrap();
7025 assert_eq!(resp2.status(), StatusCode::SERVICE_UNAVAILABLE);
7026 }
7027
7028 #[tokio::test]
7029 async fn test_delete_terminates_session() {
7030 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
7031 let app = transport.into_router();
7032
7033 let init_req = Request::builder()
7035 .method("POST")
7036 .uri("/")
7037 .header("Content-Type", "application/json")
7038 .header("Accept", "application/json, text/event-stream")
7039 .body(Body::from(
7040 serde_json::json!({
7041 "jsonrpc": "2.0",
7042 "id": 1,
7043 "method": "initialize",
7044 "params": {
7045 "protocolVersion": "2025-11-25",
7046 "capabilities": {},
7047 "clientInfo": { "name": "test", "version": "1.0" }
7048 }
7049 })
7050 .to_string(),
7051 ))
7052 .unwrap();
7053
7054 let resp = app.clone().oneshot(init_req).await.unwrap();
7055 let session_id = resp
7056 .headers()
7057 .get(MCP_SESSION_ID_HEADER)
7058 .unwrap()
7059 .to_str()
7060 .unwrap()
7061 .to_string();
7062
7063 let delete_req = Request::builder()
7065 .method("DELETE")
7066 .uri("/")
7067 .header("mcp-session-id", &session_id)
7068 .body(Body::empty())
7069 .unwrap();
7070
7071 let resp = app.clone().oneshot(delete_req).await.unwrap();
7072 assert!(resp.status().is_success());
7073
7074 let list_req = Request::builder()
7076 .method("POST")
7077 .uri("/")
7078 .header("Content-Type", "application/json")
7079 .header("Accept", "application/json")
7080 .header("mcp-session-id", &session_id)
7081 .body(Body::from(
7082 serde_json::json!({
7083 "jsonrpc": "2.0",
7084 "id": 2,
7085 "method": "tools/list",
7086 "params": {}
7087 })
7088 .to_string(),
7089 ))
7090 .unwrap();
7091
7092 let resp = app.oneshot(list_req).await.unwrap();
7093 let body = axum::body::to_bytes(resp.into_body(), usize::MAX)
7094 .await
7095 .unwrap();
7096 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
7097 assert_eq!(json["error"]["code"].as_i64().unwrap(), -32005);
7098 }
7099
7100 #[test]
7105 fn test_is_localhost_origin_http() {
7106 assert!(is_localhost_origin("http://localhost"));
7107 assert!(is_localhost_origin("http://localhost:3000"));
7108 assert!(is_localhost_origin("http://127.0.0.1"));
7109 assert!(is_localhost_origin("http://127.0.0.1:8080"));
7110 assert!(is_localhost_origin("http://[::1]"));
7111 assert!(is_localhost_origin("http://[::1]:3000"));
7112 }
7113
7114 #[test]
7115 fn test_is_localhost_origin_https() {
7116 assert!(is_localhost_origin("https://localhost"));
7117 assert!(is_localhost_origin("https://127.0.0.1:443"));
7118 }
7119
7120 #[test]
7121 fn test_is_not_localhost_origin() {
7122 assert!(!is_localhost_origin("http://example.com"));
7123 assert!(!is_localhost_origin("http://evil-localhost.com"));
7124 assert!(!is_localhost_origin("http://localhost.evil.com"));
7125 assert!(!is_localhost_origin("ftp://localhost"));
7126 assert!(!is_localhost_origin("localhost"));
7127 assert!(!is_localhost_origin(""));
7128 }
7129
7130 #[tokio::test]
7131 async fn test_origin_validation_rejects_cross_origin() {
7132 let transport = HttpTransport::new(create_test_router());
7133 let app = transport.into_router();
7134
7135 let req = Request::builder()
7136 .method("POST")
7137 .uri("/")
7138 .header("Content-Type", "application/json")
7139 .header("Accept", "application/json, text/event-stream")
7140 .header("Origin", "http://evil.com")
7141 .body(Body::from(
7142 serde_json::json!({
7143 "jsonrpc": "2.0",
7144 "id": 1,
7145 "method": "initialize",
7146 "params": {
7147 "protocolVersion": "2025-11-25",
7148 "capabilities": {},
7149 "clientInfo": { "name": "test", "version": "1.0" }
7150 }
7151 })
7152 .to_string(),
7153 ))
7154 .unwrap();
7155
7156 let resp = app.oneshot(req).await.unwrap();
7157 assert_eq!(resp.status(), StatusCode::FORBIDDEN);
7158 }
7159
7160 #[tokio::test]
7161 async fn test_origin_validation_allows_localhost() {
7162 let transport = HttpTransport::new(create_test_router());
7163 let app = transport.into_router();
7164
7165 let req = Request::builder()
7166 .method("POST")
7167 .uri("/")
7168 .header("Content-Type", "application/json")
7169 .header("Accept", "application/json, text/event-stream")
7170 .header("Origin", "http://localhost:3000")
7171 .body(Body::from(
7172 serde_json::json!({
7173 "jsonrpc": "2.0",
7174 "id": 1,
7175 "method": "initialize",
7176 "params": {
7177 "protocolVersion": "2025-11-25",
7178 "capabilities": {},
7179 "clientInfo": { "name": "test", "version": "1.0" }
7180 }
7181 })
7182 .to_string(),
7183 ))
7184 .unwrap();
7185
7186 let resp = app.oneshot(req).await.unwrap();
7187 assert_eq!(resp.status(), StatusCode::OK);
7188 }
7189
7190 #[tokio::test]
7191 async fn test_origin_validation_allows_configured_origin() {
7192 let transport = HttpTransport::new(create_test_router())
7193 .allowed_origins(vec!["https://my-app.example.com".to_string()]);
7194 let app = transport.into_router();
7195
7196 let req = Request::builder()
7197 .method("POST")
7198 .uri("/")
7199 .header("Content-Type", "application/json")
7200 .header("Accept", "application/json, text/event-stream")
7201 .header("Origin", "https://my-app.example.com")
7202 .body(Body::from(
7203 serde_json::json!({
7204 "jsonrpc": "2.0",
7205 "id": 1,
7206 "method": "initialize",
7207 "params": {
7208 "protocolVersion": "2025-11-25",
7209 "capabilities": {},
7210 "clientInfo": { "name": "test", "version": "1.0" }
7211 }
7212 })
7213 .to_string(),
7214 ))
7215 .unwrap();
7216
7217 let resp = app.oneshot(req).await.unwrap();
7218 assert_eq!(resp.status(), StatusCode::OK);
7219 }
7220
7221 #[tokio::test]
7222 async fn test_origin_validation_rejects_unconfigured_origin() {
7223 let transport = HttpTransport::new(create_test_router())
7224 .allowed_origins(vec!["https://my-app.example.com".to_string()]);
7225 let app = transport.into_router();
7226
7227 let req = Request::builder()
7228 .method("POST")
7229 .uri("/")
7230 .header("Content-Type", "application/json")
7231 .header("Accept", "application/json, text/event-stream")
7232 .header("Origin", "https://other-app.example.com")
7233 .body(Body::from(
7234 serde_json::json!({
7235 "jsonrpc": "2.0",
7236 "id": 1,
7237 "method": "initialize",
7238 "params": {
7239 "protocolVersion": "2025-11-25",
7240 "capabilities": {},
7241 "clientInfo": { "name": "test", "version": "1.0" }
7242 }
7243 })
7244 .to_string(),
7245 ))
7246 .unwrap();
7247
7248 let resp = app.oneshot(req).await.unwrap();
7249 assert_eq!(resp.status(), StatusCode::FORBIDDEN);
7250 }
7251
7252 #[tokio::test]
7253 async fn test_origin_validation_no_header_allowed() {
7254 let transport = HttpTransport::new(create_test_router());
7256 let app = transport.into_router();
7257
7258 let req = Request::builder()
7259 .method("POST")
7260 .uri("/")
7261 .header("Content-Type", "application/json")
7262 .header("Accept", "application/json, text/event-stream")
7263 .body(Body::from(
7265 serde_json::json!({
7266 "jsonrpc": "2.0",
7267 "id": 1,
7268 "method": "initialize",
7269 "params": {
7270 "protocolVersion": "2025-11-25",
7271 "capabilities": {},
7272 "clientInfo": { "name": "test", "version": "1.0" }
7273 }
7274 })
7275 .to_string(),
7276 ))
7277 .unwrap();
7278
7279 let resp = app.oneshot(req).await.unwrap();
7280 assert_eq!(resp.status(), StatusCode::OK);
7281 }
7282
7283 #[tokio::test]
7284 async fn test_disabled_origin_validation_allows_any() {
7285 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
7286 let app = transport.into_router();
7287
7288 let req = Request::builder()
7289 .method("POST")
7290 .uri("/")
7291 .header("Content-Type", "application/json")
7292 .header("Accept", "application/json, text/event-stream")
7293 .header("Origin", "http://evil.com")
7294 .body(Body::from(
7295 serde_json::json!({
7296 "jsonrpc": "2.0",
7297 "id": 1,
7298 "method": "initialize",
7299 "params": {
7300 "protocolVersion": "2025-11-25",
7301 "capabilities": {},
7302 "clientInfo": { "name": "test", "version": "1.0" }
7303 }
7304 })
7305 .to_string(),
7306 ))
7307 .unwrap();
7308
7309 let resp = app.oneshot(req).await.unwrap();
7310 assert_eq!(resp.status(), StatusCode::OK);
7311 }
7312
7313 fn initialize_body() -> Body {
7318 Body::from(
7319 serde_json::json!({
7320 "jsonrpc": "2.0",
7321 "id": 1,
7322 "method": "initialize",
7323 "params": {
7324 "protocolVersion": "2025-11-25",
7325 "capabilities": {},
7326 "clientInfo": { "name": "test", "version": "1.0" }
7327 }
7328 })
7329 .to_string(),
7330 )
7331 }
7332
7333 #[test]
7334 fn test_is_localhost_host_variants() {
7335 assert!(is_localhost_host("localhost"));
7336 assert!(is_localhost_host("localhost:3000"));
7337 assert!(is_localhost_host("127.0.0.1"));
7338 assert!(is_localhost_host("127.0.0.1:8080"));
7339 assert!(is_localhost_host("[::1]"));
7340 assert!(is_localhost_host("[::1]:3000"));
7341
7342 assert!(!is_localhost_host("evil.com"));
7343 assert!(!is_localhost_host("api.example.com:8443"));
7344 assert!(!is_localhost_host("10.0.0.1"));
7345 }
7346
7347 #[tokio::test]
7348 async fn test_host_validation_allows_localhost() {
7349 let transport = HttpTransport::new(create_test_router())
7350 .allowed_hosts(vec!["api.example.com".to_string()]);
7351 let app = transport.into_router();
7352
7353 let req = Request::builder()
7354 .method("POST")
7355 .uri("/")
7356 .header("Content-Type", "application/json")
7357 .header("Accept", "application/json, text/event-stream")
7358 .header("Host", "127.0.0.1:3000")
7359 .body(initialize_body())
7360 .unwrap();
7361
7362 let resp = app.oneshot(req).await.unwrap();
7363 assert_eq!(resp.status(), StatusCode::OK);
7364 }
7365
7366 #[tokio::test]
7367 async fn test_host_validation_allows_configured_host() {
7368 let transport = HttpTransport::new(create_test_router())
7369 .allowed_hosts(vec!["api.example.com".to_string()]);
7370 let app = transport.into_router();
7371
7372 let req = Request::builder()
7373 .method("POST")
7374 .uri("/")
7375 .header("Content-Type", "application/json")
7376 .header("Accept", "application/json, text/event-stream")
7377 .header("Host", "api.example.com")
7378 .body(initialize_body())
7379 .unwrap();
7380
7381 let resp = app.oneshot(req).await.unwrap();
7382 assert_eq!(resp.status(), StatusCode::OK);
7383 }
7384
7385 #[tokio::test]
7386 async fn test_host_validation_rejects_unconfigured_host() {
7387 let transport = HttpTransport::new(create_test_router())
7388 .allowed_hosts(vec!["api.example.com".to_string()]);
7389 let app = transport.into_router();
7390
7391 let req = Request::builder()
7392 .method("POST")
7393 .uri("/")
7394 .header("Content-Type", "application/json")
7395 .header("Accept", "application/json, text/event-stream")
7396 .header("Host", "evil.com")
7397 .body(initialize_body())
7398 .unwrap();
7399
7400 let resp = app.oneshot(req).await.unwrap();
7401 assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
7402 }
7403
7404 #[tokio::test]
7405 async fn test_host_validation_no_allowlist_accepts_any_host() {
7406 let transport = HttpTransport::new(create_test_router());
7410 let app = transport.into_router();
7411
7412 let req = Request::builder()
7413 .method("POST")
7414 .uri("/")
7415 .header("Content-Type", "application/json")
7416 .header("Accept", "application/json, text/event-stream")
7417 .header("Host", "any.example.com")
7418 .body(initialize_body())
7419 .unwrap();
7420
7421 let resp = app.oneshot(req).await.unwrap();
7422 assert_eq!(resp.status(), StatusCode::OK);
7423 }
7424
7425 #[tokio::test]
7426 async fn test_disabled_host_validation_allows_any_with_allowlist() {
7427 let transport = HttpTransport::new(create_test_router())
7428 .disable_host_validation()
7429 .allowed_hosts(vec!["api.example.com".to_string()]);
7430 let app = transport.into_router();
7431
7432 let req = Request::builder()
7433 .method("POST")
7434 .uri("/")
7435 .header("Content-Type", "application/json")
7436 .header("Accept", "application/json, text/event-stream")
7437 .header("Host", "evil.com")
7438 .body(initialize_body())
7439 .unwrap();
7440
7441 let resp = app.oneshot(req).await.unwrap();
7442 assert_eq!(resp.status(), StatusCode::OK);
7443 }
7444
7445 #[test]
7446 fn test_effective_host_prefers_header() {
7447 let mut headers = HeaderMap::new();
7448 headers.insert(header::HOST, HeaderValue::from_static("api.example.com"));
7449 let uri: axum::http::Uri = "http://other.example.com/path".parse().unwrap();
7450 assert_eq!(effective_host(&headers, &uri), Some("api.example.com"));
7451 }
7452
7453 #[test]
7454 fn test_effective_host_falls_back_to_authority() {
7455 let headers = HeaderMap::new();
7458 let uri: axum::http::Uri = "http://api.example.com/path".parse().unwrap();
7459 assert_eq!(effective_host(&headers, &uri), Some("api.example.com"));
7460 }
7461
7462 #[test]
7463 fn test_effective_host_returns_none_when_both_missing() {
7464 let headers = HeaderMap::new();
7465 let uri: axum::http::Uri = "/path".parse().unwrap();
7466 assert_eq!(effective_host(&headers, &uri), None);
7467 }
7468
7469 async fn init_session(app: &Router) -> String {
7475 let req = Request::builder()
7476 .method("POST")
7477 .uri("/")
7478 .header("Content-Type", "application/json")
7479 .header("Accept", "application/json, text/event-stream")
7480 .body(Body::from(
7481 serde_json::json!({
7482 "jsonrpc": "2.0",
7483 "id": 1,
7484 "method": "initialize",
7485 "params": {
7486 "protocolVersion": "2025-11-25",
7487 "capabilities": {},
7488 "clientInfo": { "name": "test", "version": "1.0" }
7489 }
7490 })
7491 .to_string(),
7492 ))
7493 .unwrap();
7494 let resp = app.clone().oneshot(req).await.unwrap();
7495 assert_eq!(resp.status(), StatusCode::OK);
7496 resp.headers()
7497 .get(MCP_SESSION_ID_HEADER)
7498 .and_then(|v| v.to_str().ok())
7499 .map(|s| s.to_string())
7500 .expect("initialize must return a session id")
7501 }
7502
7503 #[tokio::test]
7504 async fn test_external_notification_reaches_single_session() {
7505 let (notif_tx, notif_rx) = notification_channel(8);
7506 let transport = HttpTransport::with_notifications(create_test_router(), notif_rx);
7507 let (app, session_handle) = transport.into_router_with_handle();
7508
7509 let session_id = init_session(&app).await;
7510
7511 let mut rx = {
7513 let sessions = session_handle.store.sessions.read().await;
7514 let session = sessions
7515 .get(&session_id)
7516 .expect("session should be registered");
7517 session.notifications_tx.subscribe()
7518 };
7519
7520 notif_tx
7521 .send(crate::context::ServerNotification::ResourceUpdated {
7522 uri: "claude://chats/abc".to_string(),
7523 })
7524 .await
7525 .unwrap();
7526
7527 let json = tokio::time::timeout(Duration::from_secs(1), rx.recv())
7528 .await
7529 .expect("notification should arrive within timeout")
7530 .expect("broadcast channel closed");
7531 assert!(json.contains("notifications/resources/updated"));
7532 assert!(json.contains("claude://chats/abc"));
7533 }
7534
7535 #[tokio::test]
7536 async fn test_external_notification_fans_out_to_all_sessions() {
7537 let (notif_tx, notif_rx) = notification_channel(8);
7538 let transport = HttpTransport::with_notifications(create_test_router(), notif_rx);
7539 let (app, session_handle) = transport.into_router_with_handle();
7540
7541 let session_a = init_session(&app).await;
7542 let session_b = init_session(&app).await;
7543 assert_ne!(session_a, session_b);
7544
7545 let (mut rx_a, mut rx_b) = {
7546 let sessions = session_handle.store.sessions.read().await;
7547 let a = sessions.get(&session_a).unwrap();
7548 let b = sessions.get(&session_b).unwrap();
7549 (
7550 a.notifications_tx.subscribe(),
7551 b.notifications_tx.subscribe(),
7552 )
7553 };
7554
7555 notif_tx
7556 .send(crate::context::ServerNotification::ResourcesListChanged)
7557 .await
7558 .unwrap();
7559
7560 let json_a = tokio::time::timeout(Duration::from_secs(1), rx_a.recv())
7561 .await
7562 .unwrap()
7563 .unwrap();
7564 let json_b = tokio::time::timeout(Duration::from_secs(1), rx_b.recv())
7565 .await
7566 .unwrap()
7567 .unwrap();
7568 assert!(json_a.contains("notifications/resources/list_changed"));
7569 assert!(json_b.contains("notifications/resources/list_changed"));
7570 }
7571
7572 #[tokio::test]
7573 async fn test_external_notifications_builder_method() {
7574 let (notif_tx, notif_rx) = notification_channel(8);
7576 let transport = HttpTransport::new(create_test_router()).external_notifications(notif_rx);
7577 let (app, session_handle) = transport.into_router_with_handle();
7578
7579 let session_id = init_session(&app).await;
7580 let mut rx = {
7581 let sessions = session_handle.store.sessions.read().await;
7582 sessions
7583 .get(&session_id)
7584 .unwrap()
7585 .notifications_tx
7586 .subscribe()
7587 };
7588
7589 notif_tx
7590 .send(crate::context::ServerNotification::ToolsListChanged)
7591 .await
7592 .unwrap();
7593
7594 let json = tokio::time::timeout(Duration::from_secs(1), rx.recv())
7595 .await
7596 .unwrap()
7597 .unwrap();
7598 assert!(json.contains("notifications/tools/list_changed"));
7599 }
7600
7601 #[tokio::test]
7602 async fn test_default_transport_has_no_external_fanout_task() {
7603 let transport = HttpTransport::new(create_test_router());
7608 let (app, _handle) = transport.into_router_with_handle();
7609 let _session_id = init_session(&app).await;
7610 }
7611
7612 #[tokio::test]
7618 #[cfg(feature = "stateless")]
7619 async fn stateless_v2026_initialize_is_method_not_found() {
7620 let transport = HttpTransport::new(create_test_router())
7621 .disable_origin_validation()
7622 .disable_host_validation();
7623 let app = transport.into_router();
7624 let req = Request::builder()
7625 .method("POST")
7626 .uri("/")
7627 .header("Content-Type", "application/json")
7628 .header("Accept", "application/json, text/event-stream")
7629 .header(MCP_METHOD_HEADER, "initialize")
7630 .header(MCP_PROTOCOL_VERSION_HEADER, PROTOCOL_VERSION_2026_07_28)
7631 .body(Body::from(
7632 serde_json::json!({
7633 "jsonrpc": "2.0",
7634 "id": 1,
7635 "method": "initialize",
7636 "params": {
7637 "protocolVersion": "2026-07-28",
7638 "capabilities": {},
7639 "clientInfo": { "name": "sc", "version": "1.0" },
7640 "_meta": {
7641 "io.modelcontextprotocol/protocolVersion": "2026-07-28",
7642 "io.modelcontextprotocol/clientCapabilities": {}
7643 }
7644 }
7645 })
7646 .to_string(),
7647 ))
7648 .unwrap();
7649 let response = app.oneshot(req).await.unwrap();
7650 assert_eq!(response.status(), StatusCode::NOT_FOUND);
7651 assert!(
7652 !response.headers().contains_key(MCP_SESSION_ID_HEADER),
7653 "removed final method must not create a session"
7654 );
7655 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
7656 .await
7657 .unwrap();
7658 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
7659 assert_eq!(json["id"], 1);
7660 assert_eq!(json["error"]["code"], ErrorCode::MethodNotFound.code());
7661 }
7662
7663 #[tokio::test]
7664 #[cfg(feature = "stateless")]
7665 async fn stateless_v2026_rejects_missing_required_meta_with_http_400() {
7666 let app = HttpTransport::new(create_test_router())
7667 .disable_origin_validation()
7668 .disable_host_validation()
7669 .into_router();
7670 let request = Request::builder()
7671 .method("POST")
7672 .uri("/")
7673 .header("Content-Type", "application/json")
7674 .header("Accept", "application/json")
7675 .header(MCP_METHOD_HEADER, "server/discover")
7676 .header(MCP_PROTOCOL_VERSION_HEADER, PROTOCOL_VERSION_2026_07_28)
7677 .body(Body::from(
7678 serde_json::json!({
7679 "jsonrpc": "2.0",
7680 "id": 101,
7681 "method": "server/discover",
7682 "params": {}
7683 })
7684 .to_string(),
7685 ))
7686 .unwrap();
7687
7688 let response = app.oneshot(request).await.unwrap();
7689 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
7690 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
7691 .await
7692 .unwrap();
7693 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
7694 assert_eq!(json["id"], 101);
7695 assert_eq!(json["error"]["code"], ErrorCode::InvalidParams.code());
7696 }
7697
7698 #[tokio::test]
7699 #[cfg(feature = "stateless")]
7700 async fn stateless_v2026_rejects_invalid_meta_and_extension_keys_with_http_400() {
7701 let app = HttpTransport::new(create_test_router())
7702 .disable_origin_validation()
7703 .disable_host_validation()
7704 .into_router();
7705
7706 let build_request =
7707 |id: i64, extra_meta: serde_json::Value, extensions: serde_json::Value| {
7708 let mut meta = serde_json::json!({
7709 "io.modelcontextprotocol/protocolVersion": PROTOCOL_VERSION_2026_07_28,
7710 "io.modelcontextprotocol/clientCapabilities": {
7711 "extensions": extensions
7712 }
7713 });
7714 meta.as_object_mut()
7715 .unwrap()
7716 .extend(extra_meta.as_object().unwrap().clone());
7717 Request::builder()
7718 .method("POST")
7719 .uri("/")
7720 .header("Content-Type", "application/json")
7721 .header("Accept", "application/json")
7722 .header(MCP_METHOD_HEADER, "server/discover")
7723 .header(MCP_PROTOCOL_VERSION_HEADER, PROTOCOL_VERSION_2026_07_28)
7724 .body(Body::from(
7725 serde_json::json!({
7726 "jsonrpc": "2.0",
7727 "id": id,
7728 "method": "server/discover",
7729 "params": { "_meta": meta }
7730 })
7731 .to_string(),
7732 ))
7733 .unwrap()
7734 };
7735
7736 for request in [
7737 build_request(
7738 111,
7739 serde_json::json!({"com.example/-invalid": true}),
7740 serde_json::json!({}),
7741 ),
7742 build_request(
7743 112,
7744 serde_json::json!({}),
7745 serde_json::json!({"unprefixed": {}}),
7746 ),
7747 build_request(
7748 113,
7749 serde_json::json!({}),
7750 serde_json::json!({"com.example/feature": true}),
7751 ),
7752 ] {
7753 let response = app.clone().oneshot(request).await.unwrap();
7754 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
7755 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
7756 .await
7757 .unwrap();
7758 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
7759 assert_eq!(json["error"]["code"], ErrorCode::InvalidParams.code());
7760 }
7761 }
7762
7763 #[tokio::test]
7764 #[cfg(feature = "stateless")]
7765 async fn stateless_v2026_rejects_missing_protocol_header_with_http_400() {
7766 let app = HttpTransport::new(create_test_router())
7767 .disable_origin_validation()
7768 .disable_host_validation()
7769 .into_router();
7770 let request = Request::builder()
7771 .method("POST")
7772 .uri("/")
7773 .header("Content-Type", "application/json")
7774 .header("Accept", "application/json")
7775 .header(MCP_METHOD_HEADER, "server/discover")
7776 .body(Body::from(
7777 serde_json::json!({
7778 "jsonrpc": "2.0",
7779 "id": 102,
7780 "method": "server/discover",
7781 "params": {
7782 "_meta": {
7783 "io.modelcontextprotocol/protocolVersion": "2026-07-28",
7784 "io.modelcontextprotocol/clientCapabilities": {}
7785 }
7786 }
7787 })
7788 .to_string(),
7789 ))
7790 .unwrap();
7791
7792 let response = app.oneshot(request).await.unwrap();
7793 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
7794 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
7795 .await
7796 .unwrap();
7797 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
7798 assert_eq!(json["id"], 102);
7799 assert_eq!(json["error"]["code"], McpErrorCode::HeaderMismatch.code());
7800 }
7801
7802 #[tokio::test]
7803 #[cfg(feature = "stateless")]
7804 async fn stateless_v2026_unknown_method_is_http_404() {
7805 let app = HttpTransport::new(create_test_router())
7806 .disable_origin_validation()
7807 .disable_host_validation()
7808 .into_router();
7809 let request = Request::builder()
7810 .method("POST")
7811 .uri("/")
7812 .header("Content-Type", "application/json")
7813 .header("Accept", "application/json")
7814 .header(MCP_METHOD_HEADER, "unknown/method")
7815 .header(MCP_PROTOCOL_VERSION_HEADER, PROTOCOL_VERSION_2026_07_28)
7816 .body(Body::from(
7817 serde_json::json!({
7818 "jsonrpc": "2.0",
7819 "id": 103,
7820 "method": "unknown/method",
7821 "params": {
7822 "_meta": {
7823 "io.modelcontextprotocol/protocolVersion": "2026-07-28",
7824 "io.modelcontextprotocol/clientCapabilities": {}
7825 }
7826 }
7827 })
7828 .to_string(),
7829 ))
7830 .unwrap();
7831
7832 let response = app.oneshot(request).await.unwrap();
7833 assert_eq!(response.status(), StatusCode::NOT_FOUND);
7834 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
7835 .await
7836 .unwrap();
7837 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
7838 assert_eq!(json["id"], 103);
7839 assert_eq!(json["error"]["code"], ErrorCode::MethodNotFound.code());
7840 }
7841
7842 #[tokio::test]
7843 #[cfg(feature = "stateless")]
7844 async fn stateless_v2026_ignores_legacy_session_and_resumption_headers() {
7845 let app = HttpTransport::new(create_test_router())
7846 .disable_origin_validation()
7847 .disable_host_validation()
7848 .into_router();
7849 let request = Request::builder()
7850 .method("POST")
7851 .uri("/")
7852 .header("Content-Type", "application/json")
7853 .header("Accept", "application/json")
7854 .header(MCP_METHOD_HEADER, "tools/list")
7855 .header(MCP_PROTOCOL_VERSION_HEADER, PROTOCOL_VERSION_2026_07_28)
7856 .header(MCP_SESSION_ID_HEADER, "legacy-session-that-does-not-exist")
7857 .header(LAST_EVENT_ID_HEADER, "legacy-event")
7858 .body(Body::from(
7859 serde_json::json!({
7860 "jsonrpc": "2.0",
7861 "id": 104,
7862 "method": "tools/list",
7863 "params": {
7864 "_meta": {
7865 "io.modelcontextprotocol/protocolVersion": "2026-07-28",
7866 "io.modelcontextprotocol/clientCapabilities": {}
7867 }
7868 }
7869 })
7870 .to_string(),
7871 ))
7872 .unwrap();
7873
7874 let response = app.oneshot(request).await.unwrap();
7875 assert_eq!(response.status(), StatusCode::OK);
7876 assert!(!response.headers().contains_key(MCP_SESSION_ID_HEADER));
7877 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
7878 .await
7879 .unwrap();
7880 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
7881 assert!(json["result"]["tools"].is_array());
7882 }
7883
7884 #[tokio::test]
7885 #[cfg(feature = "stateless")]
7886 async fn stateless_v2026_enforces_tool_client_capability_requirements() {
7887 use crate::{CallToolResult, SamplingCapability, ToolBuilder};
7888
7889 let tool = ToolBuilder::new("sample")
7890 .no_params_handler(|| async { Ok(CallToolResult::text("ok")) })
7891 .build()
7892 .require_client_capabilities(ClientCapabilities {
7893 sampling: Some(SamplingCapability::default()),
7894 ..ClientCapabilities::default()
7895 });
7896 let router = McpRouter::new()
7897 .server_info("test-server", "1.0.0")
7898 .tool(tool);
7899 let app = HttpTransport::new(router)
7900 .disable_origin_validation()
7901 .disable_host_validation()
7902 .into_router();
7903
7904 let build_request = |id: i64, capabilities: serde_json::Value| {
7905 Request::builder()
7906 .method("POST")
7907 .uri("/")
7908 .header("Content-Type", "application/json")
7909 .header("Accept", "application/json")
7910 .header(MCP_METHOD_HEADER, "tools/call")
7911 .header(MCP_NAME_HEADER, "sample")
7912 .header(MCP_PROTOCOL_VERSION_HEADER, PROTOCOL_VERSION_2026_07_28)
7913 .body(Body::from(
7914 serde_json::json!({
7915 "jsonrpc": "2.0",
7916 "id": id,
7917 "method": "tools/call",
7918 "params": {
7919 "name": "sample",
7920 "arguments": {},
7921 "_meta": {
7922 "io.modelcontextprotocol/protocolVersion": "2026-07-28",
7923 "io.modelcontextprotocol/clientCapabilities": capabilities
7924 }
7925 }
7926 })
7927 .to_string(),
7928 ))
7929 .unwrap()
7930 };
7931
7932 let response = app
7933 .clone()
7934 .oneshot(build_request(105, serde_json::json!({})))
7935 .await
7936 .unwrap();
7937 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
7938 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
7939 .await
7940 .unwrap();
7941 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
7942 assert_eq!(json["id"], 105);
7943 assert_eq!(
7944 json["error"]["code"],
7945 McpErrorCode::MissingRequiredClientCapability.code()
7946 );
7947 assert_eq!(
7948 json["error"]["data"]["requiredCapabilities"],
7949 serde_json::json!({ "sampling": {} })
7950 );
7951
7952 let response = app
7953 .oneshot(build_request(106, serde_json::json!({ "sampling": {} })))
7954 .await
7955 .unwrap();
7956 assert_eq!(response.status(), StatusCode::OK);
7957 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
7958 .await
7959 .unwrap();
7960 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
7961 assert_eq!(json["result"]["content"][0]["text"], "ok");
7962 }
7963
7964 #[tokio::test]
7966 #[cfg(feature = "stateless")]
7967 async fn stateless_v2026_tools_call_without_session_succeeds() {
7968 use crate::{CallToolResult, ToolBuilder};
7969 let router = McpRouter::new().server_info("t", "1.0.0").tool(
7970 ToolBuilder::new("echo")
7971 .description("echo")
7972 .handler(|args: serde_json::Value| async move {
7973 Ok(CallToolResult::text(args.to_string()))
7974 })
7975 .build(),
7976 );
7977 let transport = HttpTransport::new(router).disable_origin_validation();
7978 let app = transport.into_router();
7979 let req = Request::builder()
7980 .method("POST")
7981 .uri("/")
7982 .header("Content-Type", "application/json")
7983 .header("Accept", "application/json")
7984 .header(MCP_PROTOCOL_VERSION_HEADER, "2026-07-28")
7985 .header(MCP_METHOD_HEADER, "tools/call")
7986 .header(MCP_NAME_HEADER, "echo")
7987 .body(Body::from(
7988 serde_json::json!({
7989 "jsonrpc": "2.0",
7990 "id": 1,
7991 "method": "tools/call",
7992 "params": {
7993 "name": "echo",
7994 "arguments": {"message": "hello"},
7995 "_meta": {
7996 "io.modelcontextprotocol/protocolVersion": "2026-07-28",
7997 "io.modelcontextprotocol/clientInfo": {
7998 "name": "sc", "version": "1.0"
7999 },
8000 "io.modelcontextprotocol/clientCapabilities": {}
8001 }
8002 }
8003 })
8004 .to_string(),
8005 ))
8006 .unwrap();
8007 let response = app.oneshot(req).await.unwrap();
8008 assert_eq!(response.status(), StatusCode::OK);
8009 assert!(
8010 !response.headers().contains_key(MCP_SESSION_ID_HEADER),
8011 "stateless tools/call must not set mcp-session-id"
8012 );
8013 assert_eq!(
8014 response
8015 .headers()
8016 .get(MCP_PROTOCOL_VERSION_HEADER)
8017 .and_then(|v| v.to_str().ok()),
8018 Some("2026-07-28")
8019 );
8020 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
8021 .await
8022 .unwrap();
8023 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
8024 assert!(
8025 json.get("result").is_some(),
8026 "expected tools/call result, got: {json}"
8027 );
8028 assert_eq!(json["result"]["resultType"], "complete");
8029 }
8030
8031 #[cfg(feature = "stateless")]
8033 fn stateless_tools_call_request() -> Request<Body> {
8034 Request::builder()
8035 .method("POST")
8036 .uri("/")
8037 .header("Content-Type", "application/json")
8038 .header("Accept", "application/json")
8039 .header(MCP_PROTOCOL_VERSION_HEADER, "2026-07-28")
8040 .header(MCP_METHOD_HEADER, "tools/call")
8041 .header(MCP_NAME_HEADER, "echo")
8042 .body(Body::from(
8043 serde_json::json!({
8044 "jsonrpc": "2.0",
8045 "id": 1,
8046 "method": "tools/call",
8047 "params": {
8048 "name": "echo",
8049 "arguments": {"message": "hello"},
8050 "_meta": {
8051 "io.modelcontextprotocol/protocolVersion": "2026-07-28",
8052 "io.modelcontextprotocol/clientInfo": {
8053 "name": "sc", "version": "1.0"
8054 },
8055 "io.modelcontextprotocol/clientCapabilities": {}
8056 }
8057 }
8058 })
8059 .to_string(),
8060 ))
8061 .unwrap()
8062 }
8063
8064 #[cfg(feature = "stateless")]
8065 fn echo_router() -> McpRouter {
8066 use crate::{CallToolResult, ToolBuilder};
8067 McpRouter::new().server_info("t", "1.0.0").tool(
8068 ToolBuilder::new("echo")
8069 .description("echo")
8070 .handler(|args: serde_json::Value| async move {
8071 Ok(CallToolResult::text(args.to_string()))
8072 })
8073 .build(),
8074 )
8075 }
8076
8077 #[tokio::test]
8080 #[cfg(feature = "stateless")]
8081 async fn stateless_v2026_response_stamps_server_info_by_default() {
8082 let transport = HttpTransport::new(echo_router()).disable_origin_validation();
8083 let app = transport.into_router();
8084 let response = app.oneshot(stateless_tools_call_request()).await.unwrap();
8085 assert_eq!(response.status(), StatusCode::OK);
8086 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
8087 .await
8088 .unwrap();
8089 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
8090 assert_eq!(
8091 json["result"]["_meta"]["io.modelcontextprotocol/serverInfo"]["name"], "t",
8092 "expected serverInfo stamped into result._meta, got: {json}"
8093 );
8094 assert_eq!(
8095 json["result"]["_meta"]["io.modelcontextprotocol/serverInfo"]["version"],
8096 "1.0.0"
8097 );
8098 }
8099
8100 #[tokio::test]
8102 #[cfg(feature = "stateless")]
8103 async fn stateless_v2026_response_omits_server_info_when_disabled() {
8104 let transport = HttpTransport::new(echo_router())
8105 .disable_origin_validation()
8106 .stamp_server_info(false);
8107 let app = transport.into_router();
8108 let response = app.oneshot(stateless_tools_call_request()).await.unwrap();
8109 assert_eq!(response.status(), StatusCode::OK);
8110 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
8111 .await
8112 .unwrap();
8113 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
8114 assert!(
8115 json["result"].get("_meta").is_none(),
8116 "expected no _meta when stamping is disabled, got: {json}"
8117 );
8118 }
8119
8120 #[tokio::test]
8122 async fn stateless_v2025_initialize_still_gets_session_id() {
8123 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
8124 let app = transport.into_router();
8125 let req = Request::builder()
8126 .method("POST")
8127 .uri("/")
8128 .header("Content-Type", "application/json")
8129 .header("Accept", "application/json, text/event-stream")
8130 .body(Body::from(
8131 serde_json::json!({
8132 "jsonrpc": "2.0", "id": 1, "method": "initialize",
8133 "params": {
8134 "protocolVersion": "2025-11-25",
8135 "capabilities": {},
8136 "clientInfo": { "name": "old-client", "version": "1.0" }
8137 }
8138 })
8139 .to_string(),
8140 ))
8141 .unwrap();
8142 let response = app.oneshot(req).await.unwrap();
8143 assert_eq!(response.status(), StatusCode::OK);
8144 assert!(
8145 response.headers().contains_key(MCP_SESSION_ID_HEADER),
8146 "2025-11-25 initialize must return mcp-session-id"
8147 );
8148 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
8149 .await
8150 .unwrap();
8151 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
8152 assert!(
8153 json["result"].get("resultType").is_none(),
8154 "legacy result must remain unchanged: {json}"
8155 );
8156 }
8157
8158 #[tokio::test]
8161 async fn stateless_v2025_tools_list_without_session_rejected() {
8162 let transport = HttpTransport::new(create_test_router())
8163 .disable_origin_validation()
8164 .require_sessions();
8165 let app = transport.into_router();
8166 let req = Request::builder()
8167 .method("POST")
8168 .uri("/")
8169 .header("Content-Type", "application/json")
8170 .header("Accept", "application/json")
8171 .header(MCP_PROTOCOL_VERSION_HEADER, "2025-11-25")
8172 .body(Body::from(
8173 serde_json::json!({
8174 "jsonrpc": "2.0", "id": 1, "method": "tools/list"
8175 })
8176 .to_string(),
8177 ))
8178 .unwrap();
8179 let response = app.oneshot(req).await.unwrap();
8180 assert_eq!(response.status(), StatusCode::OK);
8181 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
8182 .await
8183 .unwrap();
8184 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
8185 assert!(json.get("error").is_some(), "expected error, got: {json}");
8186 assert_eq!(
8187 json["error"]["code"].as_i64().unwrap(),
8188 -32006,
8189 "expected SessionRequired (-32006)"
8190 );
8191 }
8192
8193 #[tokio::test]
8195 #[cfg(feature = "stateless")]
8196 async fn stateless_v2026_tools_list_without_session_succeeds() {
8197 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
8198 let app = transport.into_router();
8199 let req = Request::builder()
8200 .method("POST")
8201 .uri("/")
8202 .header("Content-Type", "application/json")
8203 .header("Accept", "application/json")
8204 .header(MCP_PROTOCOL_VERSION_HEADER, "2026-07-28")
8205 .header(MCP_METHOD_HEADER, "tools/list")
8206 .body(Body::from(
8207 serde_json::json!({
8208 "jsonrpc": "2.0",
8209 "id": 1,
8210 "method": "tools/list",
8211 "params": {
8212 "_meta": {
8213 "io.modelcontextprotocol/protocolVersion": "2026-07-28",
8214 "io.modelcontextprotocol/clientCapabilities": {}
8215 }
8216 }
8217 })
8218 .to_string(),
8219 ))
8220 .unwrap();
8221 let response = app.oneshot(req).await.unwrap();
8222 assert_eq!(response.status(), StatusCode::OK);
8223 assert!(
8224 !response.headers().contains_key(MCP_SESSION_ID_HEADER),
8225 "stateless tools/list must not set mcp-session-id"
8226 );
8227 assert_eq!(
8228 response
8229 .headers()
8230 .get(MCP_PROTOCOL_VERSION_HEADER)
8231 .and_then(|v| v.to_str().ok()),
8232 Some("2026-07-28")
8233 );
8234 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
8235 .await
8236 .unwrap();
8237 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
8238 assert!(
8239 json["result"]["tools"].is_array(),
8240 "expected tools array in result, got: {json}"
8241 );
8242 assert_eq!(json["result"]["resultType"], "complete");
8243 assert_eq!(json["result"]["ttlMs"], 0);
8244 assert_eq!(json["result"]["cacheScope"], "private");
8245 }
8246
8247 #[tokio::test]
8250 #[cfg(feature = "stateless")]
8251 async fn stateless_v2026_notification_returns_202_no_session() {
8252 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
8253 let (app, handle) = transport.into_router_with_handle();
8254 let req = Request::builder()
8255 .method("POST")
8256 .uri("/")
8257 .header("Content-Type", "application/json")
8258 .header("Accept", "application/json")
8259 .header(MCP_PROTOCOL_VERSION_HEADER, "2026-07-28")
8260 .header(MCP_METHOD_HEADER, "notifications/cancelled")
8261 .body(Body::from(
8262 serde_json::json!({
8263 "jsonrpc": "2.0",
8264 "method": "notifications/cancelled",
8265 "params": {
8266 "requestId": 99,
8267 "reason": "test",
8268 "_meta": {
8269 "io.modelcontextprotocol/protocolVersion": "2026-07-28",
8270 "io.modelcontextprotocol/clientCapabilities": {}
8271 }
8272 }
8273 })
8274 .to_string(),
8275 ))
8276 .unwrap();
8277 let response = app.oneshot(req).await.unwrap();
8278 assert_eq!(
8279 response.status(),
8280 StatusCode::ACCEPTED,
8281 "stateless notification must return 202 ACCEPTED"
8282 );
8283 assert!(
8284 !response.headers().contains_key(MCP_SESSION_ID_HEADER),
8285 "stateless notification must not set mcp-session-id"
8286 );
8287 assert_eq!(
8288 handle.session_count().await,
8289 0,
8290 "stateless notification must not create a session"
8291 );
8292 }
8293
8294 #[tokio::test]
8296 #[cfg(feature = "stateless")]
8297 async fn stateless_v2026_missing_mcp_method_returns_400() {
8298 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
8299 let app = transport.into_router();
8300 let req = Request::builder()
8301 .method("POST")
8302 .uri("/")
8303 .header("Content-Type", "application/json")
8304 .header("Accept", "application/json")
8305 .header(MCP_PROTOCOL_VERSION_HEADER, "2026-07-28")
8306 .body(Body::from(
8308 serde_json::json!({
8309 "jsonrpc": "2.0",
8310 "id": 1,
8311 "method": "tools/list",
8312 "params": {
8313 "_meta": {
8314 "io.modelcontextprotocol/protocolVersion": "2026-07-28",
8315 "io.modelcontextprotocol/clientCapabilities": {}
8316 }
8317 }
8318 })
8319 .to_string(),
8320 ))
8321 .unwrap();
8322 let response = app.oneshot(req).await.unwrap();
8323 assert_eq!(
8324 response.status(),
8325 StatusCode::BAD_REQUEST,
8326 "missing Mcp-Method must return HTTP 400"
8327 );
8328 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
8329 .await
8330 .unwrap();
8331 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
8332 assert!(json.get("error").is_some(), "expected error, got: {json}");
8333 assert_eq!(
8334 json["error"]["code"].as_i64().unwrap(),
8335 -32020,
8336 "expected HeaderMismatch (-32020)"
8337 );
8338 assert!(
8339 json["error"]["message"]
8340 .as_str()
8341 .unwrap_or("")
8342 .contains("Mcp-Method"),
8343 "error message must mention Mcp-Method, got: {json}"
8344 );
8345 }
8346
8347 #[tokio::test]
8348 async fn sse_responses_false_returns_application_json() {
8349 let transport = HttpTransport::new(create_test_router())
8351 .disable_origin_validation()
8352 .sse_responses(false);
8353 let app = transport.into_router();
8354
8355 let request = Request::builder()
8356 .method("POST")
8357 .uri("/")
8358 .header("Content-Type", "application/json")
8359 .header("Accept", "application/json, text/event-stream")
8360 .body(Body::from(
8361 serde_json::json!({
8362 "jsonrpc": "2.0",
8363 "id": 1,
8364 "method": "initialize",
8365 "params": {
8366 "protocolVersion": "2025-11-25",
8367 "capabilities": {},
8368 "clientInfo": {"name": "test", "version": "0.1"}
8369 }
8370 })
8371 .to_string(),
8372 ))
8373 .unwrap();
8374
8375 let response = app.oneshot(request).await.unwrap();
8376 assert_eq!(response.status(), StatusCode::OK);
8377 let ct = response
8378 .headers()
8379 .get(header::CONTENT_TYPE)
8380 .and_then(|v| v.to_str().ok())
8381 .unwrap_or("");
8382 assert!(
8383 ct.contains("application/json"),
8384 "sse_responses(false) should return application/json, got: {ct}"
8385 );
8386 }
8387
8388 #[tokio::test]
8389 async fn sse_responses_true_returns_text_event_stream_with_valid_json() {
8390 let transport = HttpTransport::new(create_test_router())
8392 .disable_origin_validation()
8393 .sse_responses(true);
8394 let app = transport.into_router();
8395
8396 let init_body = serde_json::json!({
8397 "jsonrpc": "2.0",
8398 "id": 1,
8399 "method": "initialize",
8400 "params": {
8401 "protocolVersion": "2025-11-25",
8402 "capabilities": {},
8403 "clientInfo": {"name": "test", "version": "0.1"}
8404 }
8405 })
8406 .to_string();
8407
8408 let request = Request::builder()
8409 .method("POST")
8410 .uri("/")
8411 .header("Content-Type", "application/json")
8412 .header("Accept", "application/json, text/event-stream")
8413 .body(Body::from(init_body))
8414 .unwrap();
8415
8416 let response = app.oneshot(request).await.unwrap();
8417 assert_eq!(response.status(), StatusCode::OK);
8418
8419 let ct = response
8420 .headers()
8421 .get(header::CONTENT_TYPE)
8422 .and_then(|v| v.to_str().ok())
8423 .unwrap_or("");
8424 assert!(
8425 ct.contains("text/event-stream"),
8426 "sse_responses(true) should return text/event-stream, got: {ct}"
8427 );
8428
8429 let bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
8430 .await
8431 .unwrap();
8432 let body_text = String::from_utf8_lossy(&bytes);
8433
8434 assert!(
8436 body_text.contains("event: message"),
8437 "SSE body missing 'event: message': {body_text}"
8438 );
8439 assert!(
8440 body_text.contains("data: "),
8441 "SSE body missing 'data: ' line: {body_text}"
8442 );
8443
8444 let data_line = body_text
8446 .lines()
8447 .find(|l| l.starts_with("data: "))
8448 .expect("no data: line in SSE body");
8449 let json_str = data_line.trim_start_matches("data: ");
8450 let val: serde_json::Value =
8451 serde_json::from_str(json_str).expect("data: line is not valid JSON");
8452
8453 assert_eq!(val["jsonrpc"], "2.0", "jsonrpc version mismatch: {val}");
8455 assert_eq!(val["id"], 1, "id mismatch: {val}");
8456 assert!(
8457 val["result"].is_object(),
8458 "result should be an object: {val}"
8459 );
8460 assert_eq!(
8462 val["result"]["protocolVersion"].as_str(),
8463 Some("2025-11-25"),
8464 "protocolVersion missing or wrong: {val}"
8465 );
8466 }
8467
8468 #[tokio::test]
8469 async fn sse_responses_true_tools_list_returns_valid_sse() {
8470 let transport = HttpTransport::new(create_test_router())
8472 .disable_origin_validation()
8473 .sse_responses(true);
8474 let app = transport.into_router();
8475
8476 let init_request = Request::builder()
8478 .method("POST")
8479 .uri("/")
8480 .header("Content-Type", "application/json")
8481 .header("Accept", "application/json, text/event-stream")
8482 .body(Body::from(
8483 serde_json::json!({
8484 "jsonrpc": "2.0",
8485 "id": 1,
8486 "method": "initialize",
8487 "params": {
8488 "protocolVersion": "2025-11-25",
8489 "capabilities": {},
8490 "clientInfo": {"name": "test", "version": "0.1"}
8491 }
8492 })
8493 .to_string(),
8494 ))
8495 .unwrap();
8496
8497 let init_response = app.clone().oneshot(init_request).await.unwrap();
8498 assert_eq!(init_response.status(), StatusCode::OK);
8499 let session_id = init_response
8500 .headers()
8501 .get(MCP_SESSION_ID_HEADER)
8502 .and_then(|v| v.to_str().ok())
8503 .map(|s| s.to_string())
8504 .expect("missing session ID from initialize");
8505
8506 let notif_request = Request::builder()
8508 .method("POST")
8509 .uri("/")
8510 .header("Content-Type", "application/json")
8511 .header("Accept", "application/json, text/event-stream")
8512 .header(MCP_SESSION_ID_HEADER, &session_id)
8513 .body(Body::from(
8514 serde_json::json!({
8515 "jsonrpc": "2.0",
8516 "method": "notifications/initialized"
8517 })
8518 .to_string(),
8519 ))
8520 .unwrap();
8521 app.clone().oneshot(notif_request).await.unwrap();
8522
8523 let list_request = Request::builder()
8525 .method("POST")
8526 .uri("/")
8527 .header("Content-Type", "application/json")
8528 .header("Accept", "application/json, text/event-stream")
8529 .header(MCP_SESSION_ID_HEADER, &session_id)
8530 .body(Body::from(
8531 serde_json::json!({
8532 "jsonrpc": "2.0",
8533 "id": 2,
8534 "method": "tools/list",
8535 "params": {}
8536 })
8537 .to_string(),
8538 ))
8539 .unwrap();
8540
8541 let list_response = app.oneshot(list_request).await.unwrap();
8542 assert_eq!(list_response.status(), StatusCode::OK);
8543
8544 let ct = list_response
8545 .headers()
8546 .get(header::CONTENT_TYPE)
8547 .and_then(|v| v.to_str().ok())
8548 .unwrap_or("");
8549 assert!(
8550 ct.contains("text/event-stream"),
8551 "tools/list with sse_responses(true) should return text/event-stream, got: {ct}"
8552 );
8553
8554 let bytes = axum::body::to_bytes(list_response.into_body(), usize::MAX)
8555 .await
8556 .unwrap();
8557 let body_text = String::from_utf8_lossy(&bytes);
8558 let data_line = body_text
8559 .lines()
8560 .find(|l| l.starts_with("data: "))
8561 .expect("no data: line in SSE body for tools/list");
8562 let json_str = data_line.trim_start_matches("data: ");
8563 let val: serde_json::Value =
8564 serde_json::from_str(json_str).expect("tools/list data: line is not valid JSON");
8565
8566 assert_eq!(val["jsonrpc"], "2.0");
8567 assert_eq!(val["id"], 2);
8568 assert!(
8570 val["result"]["tools"].is_array(),
8571 "tools/list result.tools should be an array: {val}"
8572 );
8573 }
8574
8575 async fn do_initialize(app: &axum::Router) -> String {
8581 do_initialize_for_revision(app, "2025-11-25").await
8582 }
8583
8584 async fn do_initialize_for_revision(app: &axum::Router, revision: &str) -> String {
8585 let init_request = Request::builder()
8586 .method("POST")
8587 .uri("/")
8588 .header("Content-Type", "application/json")
8589 .header("Accept", "application/json, text/event-stream")
8590 .body(Body::from(
8591 serde_json::json!({
8592 "jsonrpc": "2.0",
8593 "id": 1,
8594 "method": "initialize",
8595 "params": {
8596 "protocolVersion": revision,
8597 "capabilities": {},
8598 "clientInfo": { "name": "test-client", "version": "1.0.0" }
8599 }
8600 })
8601 .to_string(),
8602 ))
8603 .unwrap();
8604
8605 let response = app.clone().oneshot(init_request).await.unwrap();
8606 response
8607 .headers()
8608 .get(MCP_SESSION_ID_HEADER)
8609 .unwrap()
8610 .to_str()
8611 .unwrap()
8612 .to_string()
8613 }
8614
8615 async fn send_initialized(app: &axum::Router, session_id: &str) {
8616 let request = Request::builder()
8617 .method("POST")
8618 .uri("/")
8619 .header("Content-Type", "application/json")
8620 .header("Accept", "application/json, text/event-stream")
8621 .header(MCP_SESSION_ID_HEADER, session_id)
8622 .body(Body::from(
8623 serde_json::json!({
8624 "jsonrpc": "2.0",
8625 "method": "notifications/initialized"
8626 })
8627 .to_string(),
8628 ))
8629 .unwrap();
8630 let response = app.clone().oneshot(request).await.unwrap();
8631 assert_eq!(response.status(), StatusCode::ACCEPTED);
8632 }
8633
8634 async fn post_legacy_batch(app: &axum::Router, session_id: &str) -> serde_json::Value {
8635 let request = Request::builder()
8636 .method("POST")
8637 .uri("/")
8638 .header("Content-Type", "application/json")
8639 .header("Accept", "application/json")
8640 .header(MCP_SESSION_ID_HEADER, session_id)
8641 .body(Body::from(
8642 serde_json::json!([
8643 {"jsonrpc": "2.0", "id": 2, "method": "ping"},
8644 {"jsonrpc": "2.0", "id": 3, "method": "tools/list"}
8645 ])
8646 .to_string(),
8647 ))
8648 .unwrap();
8649 let response = app.clone().oneshot(request).await.unwrap();
8650 assert_eq!(response.status(), StatusCode::OK);
8651 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
8652 .await
8653 .unwrap();
8654 serde_json::from_slice(&body).unwrap()
8655 }
8656
8657 #[tokio::test]
8658 async fn http_batch_policy_uses_exact_session_revision() {
8659 let march_app = HttpTransport::new(create_test_router())
8660 .disable_origin_validation()
8661 .protocol_versions(["2025-03-26"])
8662 .unwrap()
8663 .into_router();
8664 let march_session = do_initialize_for_revision(&march_app, "2025-03-26").await;
8665 send_initialized(&march_app, &march_session).await;
8666 let march_response = post_legacy_batch(&march_app, &march_session).await;
8667 assert_eq!(march_response.as_array().map(Vec::len), Some(2));
8668
8669 let november_app = HttpTransport::new(create_test_router())
8670 .disable_origin_validation()
8671 .into_router();
8672 let november_session = do_initialize(&november_app).await;
8673 send_initialized(&november_app, &november_session).await;
8674 let november_response = post_legacy_batch(&november_app, &november_session).await;
8675 assert_eq!(november_response["error"]["code"], -32600);
8676 assert!(
8677 november_response["error"]["message"]
8678 .as_str()
8679 .unwrap()
8680 .contains("does not permit top-level JSON-RPC batches")
8681 );
8682 }
8683
8684 #[cfg(feature = "stateless")]
8685 #[tokio::test]
8686 async fn http_final_batch_is_rejected_before_object_routing() {
8687 let app = HttpTransport::new(create_test_router())
8688 .disable_origin_validation()
8689 .into_router();
8690 let request = Request::builder()
8691 .method("POST")
8692 .uri("/")
8693 .header("Content-Type", "application/json")
8694 .header("Accept", "application/json")
8695 .header(MCP_PROTOCOL_VERSION_HEADER, PROTOCOL_VERSION_2026_07_28)
8696 .body(Body::from(
8697 serde_json::json!([{
8698 "jsonrpc": "2.0",
8699 "id": 1,
8700 "method": "tools/list",
8701 "params": {
8702 "_meta": {
8703 "io.modelcontextprotocol/protocolVersion": "2026-07-28",
8704 "io.modelcontextprotocol/clientCapabilities": {}
8705 }
8706 }
8707 }])
8708 .to_string(),
8709 ))
8710 .unwrap();
8711 let response = app.oneshot(request).await.unwrap();
8712 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
8713 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
8714 .await
8715 .unwrap();
8716 let response: serde_json::Value = serde_json::from_slice(&body).unwrap();
8717 assert_eq!(response["error"]["code"], -32600);
8718 assert!(
8719 response["error"]["message"]
8720 .as_str()
8721 .unwrap()
8722 .contains("does not permit top-level JSON-RPC batches")
8723 );
8724 }
8725
8726 #[tokio::test]
8727 async fn tools_list_before_initialized_notification_returns_error() {
8728 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
8731 let app = transport.into_router();
8732
8733 let session_id = do_initialize(&app).await;
8734
8735 let list_request = Request::builder()
8737 .method("POST")
8738 .uri("/")
8739 .header("Content-Type", "application/json")
8740 .header("Accept", "application/json, text/event-stream")
8741 .header(MCP_SESSION_ID_HEADER, &session_id)
8742 .body(Body::from(
8743 serde_json::json!({
8744 "jsonrpc": "2.0",
8745 "id": 2,
8746 "method": "tools/list"
8747 })
8748 .to_string(),
8749 ))
8750 .unwrap();
8751
8752 let response = app.oneshot(list_request).await.unwrap();
8753 assert_eq!(response.status(), StatusCode::OK);
8754 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
8755 .await
8756 .unwrap();
8757 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
8758 assert!(
8759 json.get("error").is_some(),
8760 "expected error when notifications/initialized not sent, got: {json}"
8761 );
8762 assert_eq!(
8763 json["error"]["code"].as_i64().unwrap(),
8764 -32600,
8765 "expected InvalidRequest (-32600), got: {json}"
8766 );
8767 assert!(
8768 json["error"]["message"]
8769 .as_str()
8770 .unwrap_or("")
8771 .contains("notifications/initialized"),
8772 "error message should mention notifications/initialized, got: {json}"
8773 );
8774 }
8775
8776 #[tokio::test]
8777 async fn tools_list_after_initialized_notification_succeeds() {
8778 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
8780 let app = transport.into_router();
8781
8782 let session_id = do_initialize(&app).await;
8783
8784 let notif_request = Request::builder()
8786 .method("POST")
8787 .uri("/")
8788 .header("Content-Type", "application/json")
8789 .header("Accept", "application/json, text/event-stream")
8790 .header(MCP_SESSION_ID_HEADER, &session_id)
8791 .body(Body::from(
8792 serde_json::json!({
8793 "jsonrpc": "2.0",
8794 "method": "notifications/initialized"
8795 })
8796 .to_string(),
8797 ))
8798 .unwrap();
8799 app.clone().oneshot(notif_request).await.unwrap();
8800
8801 let list_request = Request::builder()
8803 .method("POST")
8804 .uri("/")
8805 .header("Content-Type", "application/json")
8806 .header("Accept", "application/json, text/event-stream")
8807 .header(MCP_SESSION_ID_HEADER, &session_id)
8808 .body(Body::from(
8809 serde_json::json!({
8810 "jsonrpc": "2.0",
8811 "id": 2,
8812 "method": "tools/list"
8813 })
8814 .to_string(),
8815 ))
8816 .unwrap();
8817
8818 let response = app.oneshot(list_request).await.unwrap();
8819 assert_eq!(response.status(), StatusCode::OK);
8820 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
8821 .await
8822 .unwrap();
8823 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
8824 assert!(
8825 json.get("result").is_some(),
8826 "expected success after notifications/initialized, got: {json}"
8827 );
8828 }
8829
8830 #[tokio::test]
8831 async fn notifications_initialized_itself_always_accepted() {
8832 let transport = HttpTransport::new(create_test_router()).disable_origin_validation();
8835 let app = transport.into_router();
8836
8837 let session_id = do_initialize(&app).await;
8838
8839 let notif_request = Request::builder()
8840 .method("POST")
8841 .uri("/")
8842 .header("Content-Type", "application/json")
8843 .header("Accept", "application/json, text/event-stream")
8844 .header(MCP_SESSION_ID_HEADER, &session_id)
8845 .body(Body::from(
8846 serde_json::json!({
8847 "jsonrpc": "2.0",
8848 "method": "notifications/initialized"
8849 })
8850 .to_string(),
8851 ))
8852 .unwrap();
8853
8854 let response = app.oneshot(notif_request).await.unwrap();
8855 assert_eq!(
8856 response.status(),
8857 StatusCode::ACCEPTED,
8858 "notifications/initialized must return 202 ACCEPTED"
8859 );
8860 }
8861
8862 #[tokio::test]
8863 async fn strict_initialization_false_allows_tools_list_without_notification() {
8864 let config = SessionConfig {
8867 strict_initialization: false,
8868 ..Default::default()
8869 };
8870 let transport = HttpTransport::new(create_test_router())
8871 .disable_origin_validation()
8872 .session_config(config);
8873 let app = transport.into_router();
8874
8875 let session_id = do_initialize(&app).await;
8876
8877 let list_request = Request::builder()
8879 .method("POST")
8880 .uri("/")
8881 .header("Content-Type", "application/json")
8882 .header("Accept", "application/json, text/event-stream")
8883 .header(MCP_SESSION_ID_HEADER, &session_id)
8884 .body(Body::from(
8885 serde_json::json!({
8886 "jsonrpc": "2.0",
8887 "id": 2,
8888 "method": "tools/list"
8889 })
8890 .to_string(),
8891 ))
8892 .unwrap();
8893
8894 let response = app.oneshot(list_request).await.unwrap();
8895 assert_eq!(response.status(), StatusCode::OK);
8896 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
8897 .await
8898 .unwrap();
8899 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
8900 assert!(
8901 json.get("result").is_some(),
8902 "expected success with strict_initialization=false, got: {json}"
8903 );
8904 }
8905}