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