use std::collections::HashMap;
use std::convert::Infallible;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
use std::time::{Duration, Instant};
use axum::{
Router,
extract::State,
http::{HeaderMap, HeaderValue, StatusCode, header},
response::{IntoResponse, Response, Sse, sse::Event},
routing::{delete, get, post},
};
#[cfg(feature = "stateless")]
use tokio::sync::mpsc;
use tokio::sync::{Mutex, RwLock, broadcast, oneshot};
use tokio_stream::StreamExt;
use tokio_stream::wrappers::BroadcastStream;
#[cfg(feature = "stateless")]
use crate::context::ServerNotification;
use crate::context::{
ChannelClientRequester, ClientRequesterHandle, NotificationReceiver, OutgoingRequest,
OutgoingRequestReceiver, notification_channel, outgoing_request_channel,
};
use crate::error::{Error, JsonRpcError, Result};
#[cfg(feature = "stateless")]
use crate::error::{ErrorCode, McpErrorCode};
use crate::inspection::{McpDirection, McpProtocolRevision};
use crate::jsonrpc::{JsonRpcService, apply_protocol_result_fields, inspect_runtime_value};
#[cfg(feature = "stateless")]
use crate::protocol::SubscriptionFilter;
use crate::protocol::{
ClientCapabilities, Implementation, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest,
JsonRpcResponse, LATEST_PROTOCOL_VERSION, McpNotification, PROTOCOL_VERSION_2026_07_28,
RequestId,
};
use crate::router::{McpRouter, RouterRequest, RouterResponse};
use crate::transport::service::{
CatchError, InjectAnnotations, McpBoxService, ServiceFactory, identity_factory,
};
#[cfg(feature = "stateless")]
use crate::transport::subscriptions::{
subscription_complete_response, subscription_matches, tagged_subscription_notification,
};
use crate::{ProtocolSupport, ProtocolSupportError};
use tower::util::BoxCloneService;
pub const MCP_SESSION_ID_HEADER: &str = "mcp-session-id";
pub const MCP_PROTOCOL_VERSION_HEADER: &str = "mcp-protocol-version";
pub const MCP_METHOD_HEADER: &str = "mcp-method";
pub const MCP_NAME_HEADER: &str = "mcp-name";
pub const MCP_PARAM_HEADER_PREFIX: &str = "mcp-param-";
pub const DEFAULT_MAX_BODY_SIZE: usize = 4 * 1024 * 1024;
const SSE_MESSAGE_EVENT: &str = "message";
const LAST_EVENT_ID_HEADER: &str = "last-event-id";
#[derive(Clone)]
enum ServiceSource {
Router {
router: McpRouter,
factory: ServiceFactory,
},
Service(Arc<std::sync::Mutex<McpBoxService>>),
}
struct AppState {
service_source: ServiceSource,
extension_bridges: Vec<crate::transport::extension_bridge::ExtensionBridge>,
protocol_support: ProtocolSupport,
sessions: Arc<SessionRegistry>,
validate_origin: bool,
allowed_origins: Vec<String>,
validate_host: bool,
allowed_hosts: Vec<String>,
optional_sessions: bool,
strict_initialization: bool,
#[cfg(feature = "stateless")]
stateless_config: Option<crate::stateless::StatelessConfig>,
#[cfg(feature = "stateless")]
stamp_server_info: bool,
#[cfg(feature = "stateless")]
modern_subscriptions: Arc<ModernSubscriptionRegistry>,
sse_responses: bool,
max_body_size: usize,
}
#[cfg(feature = "oauth")]
#[derive(Clone)]
pub(crate) struct OAuthConfig {
pub(crate) metadata: crate::oauth::ProtectedResourceMetadata,
}
pub struct HttpTransport {
service_source: ServiceSource,
extension_bridges: Vec<crate::transport::extension_bridge::ExtensionBridge>,
protocol_support: ProtocolSupport,
validate_origin: bool,
allowed_origins: Vec<String>,
validate_host: bool,
allowed_hosts: Vec<String>,
session_config: SessionConfig,
sampling_enabled: bool,
optional_sessions: bool,
session_store: Arc<dyn crate::session_store::SessionStore>,
event_store: Arc<dyn crate::event_store::EventStore>,
auto_reinit_sessions: bool,
external_notifications: Option<NotificationReceiver>,
#[cfg(feature = "stateless")]
stateless_config: Option<crate::stateless::StatelessConfig>,
#[cfg(feature = "stateless")]
stamp_server_info: bool,
#[cfg(feature = "oauth")]
oauth_config: Option<OAuthConfig>,
sse_responses: bool,
max_body_size: usize,
drain_timeout: Option<Duration>,
}
impl HttpTransport {
pub fn new(router: McpRouter) -> Self {
Self {
service_source: ServiceSource::Router {
router,
factory: identity_factory(),
},
protocol_support: ProtocolSupport::default(),
validate_origin: true,
allowed_origins: vec![],
validate_host: true,
allowed_hosts: vec![],
session_config: SessionConfig::default(),
sampling_enabled: false,
optional_sessions: true,
session_store: Arc::new(crate::session_store::MemorySessionStore::new()),
event_store: Arc::new(crate::event_store::MemoryEventStore::new()),
auto_reinit_sessions: false,
external_notifications: None,
#[cfg(feature = "stateless")]
stateless_config: None,
#[cfg(feature = "stateless")]
stamp_server_info: true,
#[cfg(feature = "oauth")]
oauth_config: None,
sse_responses: false,
extension_bridges: Vec::new(),
max_body_size: DEFAULT_MAX_BODY_SIZE,
drain_timeout: None,
}
}
pub fn bridge_extension<T>(mut self) -> Self
where
T: Clone + Send + Sync + 'static,
{
self.extension_bridges
.push(crate::transport::extension_bridge::extension_bridge::<T>());
self
}
pub fn from_service<S>(service: S) -> Self
where
S: tower::Service<
RouterRequest,
Response = RouterResponse,
Error = std::convert::Infallible,
> + Clone
+ Send
+ 'static,
S::Future: Send,
{
Self {
service_source: ServiceSource::Service(Arc::new(std::sync::Mutex::new(
BoxCloneService::new(service),
))),
protocol_support: ProtocolSupport::default(),
validate_origin: true,
allowed_origins: vec![],
validate_host: true,
allowed_hosts: vec![],
session_config: SessionConfig::default(),
sampling_enabled: false,
optional_sessions: true,
session_store: Arc::new(crate::session_store::MemorySessionStore::new()),
event_store: Arc::new(crate::event_store::MemoryEventStore::new()),
auto_reinit_sessions: false,
external_notifications: None,
#[cfg(feature = "stateless")]
stateless_config: None,
#[cfg(feature = "stateless")]
stamp_server_info: true,
#[cfg(feature = "oauth")]
oauth_config: None,
sse_responses: false,
extension_bridges: Vec::new(),
max_body_size: DEFAULT_MAX_BODY_SIZE,
drain_timeout: None,
}
}
pub fn with_notifications(router: McpRouter, notification_rx: NotificationReceiver) -> Self {
Self {
external_notifications: Some(notification_rx),
..Self::new(router)
}
}
pub fn external_notifications(mut self, notification_rx: NotificationReceiver) -> Self {
self.external_notifications = Some(notification_rx);
self
}
pub fn with_sampling(mut self) -> Self {
self.sampling_enabled = true;
self
}
pub fn require_sessions(mut self) -> Self {
self.optional_sessions = false;
self
}
pub fn protocol_support(mut self, support: ProtocolSupport) -> Self {
self.protocol_support = support;
self
}
pub fn protocol_versions<I, S>(
mut self,
versions: I,
) -> std::result::Result<Self, ProtocolSupportError>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.protocol_support = ProtocolSupport::try_new(versions)?;
Ok(self)
}
pub fn sse_responses(mut self, enabled: bool) -> Self {
self.sse_responses = enabled;
self
}
#[cfg(feature = "stateless")]
pub fn stamp_server_info(mut self, enabled: bool) -> Self {
self.stamp_server_info = enabled;
self
}
pub fn max_body_size(mut self, bytes: usize) -> Self {
self.max_body_size = bytes;
self
}
#[cfg(feature = "stateless")]
pub fn stateless(mut self, config: crate::stateless::StatelessConfig) -> Self {
self.stateless_config = Some(config);
self
}
pub fn disable_origin_validation(mut self) -> Self {
self.validate_origin = false;
self
}
pub fn allowed_origins(mut self, origins: Vec<String>) -> Self {
self.allowed_origins = origins;
self
}
pub fn disable_host_validation(mut self) -> Self {
self.validate_host = false;
self
}
pub fn allowed_hosts(mut self, hosts: Vec<String>) -> Self {
self.allowed_hosts = hosts;
self
}
pub fn session_config(mut self, config: SessionConfig) -> Self {
self.session_config = config;
self
}
pub fn session_ttl(mut self, ttl: Duration) -> Self {
self.session_config.ttl = ttl;
self
}
pub fn max_sessions(mut self, max: usize) -> Self {
self.session_config.max_sessions = Some(max);
self
}
pub fn session_store(mut self, store: Arc<dyn crate::session_store::SessionStore>) -> Self {
self.session_store = store;
self
}
pub fn event_store(mut self, store: Arc<dyn crate::event_store::EventStore>) -> Self {
self.event_store = store;
self
}
pub fn auto_reinitialize_sessions(mut self, enabled: bool) -> Self {
self.auto_reinit_sessions = enabled;
self
}
#[cfg(feature = "oauth")]
pub fn oauth(mut self, metadata: crate::oauth::ProtectedResourceMetadata) -> Self {
self.oauth_config = Some(OAuthConfig { metadata });
self
}
#[cfg(feature = "oauth")]
pub fn into_oauth_router<V>(
self,
validator: V,
metadata: crate::oauth::ProtectedResourceMetadata,
policy: crate::oauth::ScopePolicy,
) -> std::result::Result<Router, crate::oauth::ProtectedResourceMetadataError>
where
V: crate::oauth::TokenValidator,
{
let (router, _) = self.into_oauth_router_with_handle(validator, metadata, policy)?;
Ok(router)
}
#[cfg(feature = "oauth")]
pub fn into_oauth_router_with_handle<V>(
self,
validator: V,
metadata: crate::oauth::ProtectedResourceMetadata,
policy: crate::oauth::ScopePolicy,
) -> std::result::Result<(Router, SessionHandle), crate::oauth::ProtectedResourceMetadataError>
where
V: crate::oauth::TokenValidator,
{
metadata.validate()?;
let oauth_layer =
crate::oauth::OAuthLayer::new(validator, metadata.clone()).scope_policy(policy.clone());
let transport = self
.layer(crate::oauth::ScopeEnforcementLayer::new(policy))
.oauth(metadata);
let (router, handle) = transport.into_router_with_handle();
Ok((router.layer(oauth_layer), handle))
}
#[cfg(feature = "oauth")]
pub fn into_oauth_router_at<V>(
self,
path: &str,
validator: V,
metadata: crate::oauth::ProtectedResourceMetadata,
policy: crate::oauth::ScopePolicy,
) -> std::result::Result<Router, crate::oauth::ProtectedResourceMetadataError>
where
V: crate::oauth::TokenValidator,
{
let (router, _) =
self.into_oauth_router_at_with_handle(path, validator, metadata, policy)?;
Ok(router)
}
#[cfg(feature = "oauth")]
pub fn into_oauth_router_at_with_handle<V>(
self,
path: &str,
validator: V,
metadata: crate::oauth::ProtectedResourceMetadata,
policy: crate::oauth::ScopePolicy,
) -> std::result::Result<(Router, SessionHandle), crate::oauth::ProtectedResourceMetadataError>
where
V: crate::oauth::TokenValidator,
{
metadata.validate()?;
let oauth_layer =
crate::oauth::OAuthLayer::new(validator, metadata.clone()).scope_policy(policy.clone());
let transport = self
.layer(crate::oauth::ScopeEnforcementLayer::new(policy))
.oauth(metadata);
let (router, handle) = transport.into_router_at_with_handle(path);
Ok((router.layer(oauth_layer), handle))
}
pub fn layer<L>(mut self, layer: L) -> Self
where
L: tower::Layer<McpRouter> + Send + Sync + 'static,
L::Service:
tower::Service<RouterRequest, Response = RouterResponse> + Clone + Send + 'static,
<L::Service as tower::Service<RouterRequest>>::Error: std::fmt::Display + Send,
<L::Service as tower::Service<RouterRequest>>::Future: Send,
{
match &mut self.service_source {
ServiceSource::Router { factory, .. } => {
*factory = Arc::new(move |router: McpRouter| {
let annotations = router.tool_annotations_map();
let wrapped = layer.layer(router);
tower::util::BoxCloneService::new(InjectAnnotations::new(
CatchError::new(wrapped),
annotations,
))
});
}
ServiceSource::Service(_) => {
panic!(
"layer() cannot be used with from_service() — \
wrap the service with middleware before passing it in"
);
}
}
self
}
fn build_state(&self) -> Arc<AppState> {
#[cfg(feature = "stateless")]
let modern_subscriptions = Arc::new(ModernSubscriptionRegistry::new(
match &self.service_source {
ServiceSource::Router { router, .. } if self.stamp_server_info => {
Some(router.implementation())
}
_ => None,
},
match &self.service_source {
ServiceSource::Router { router, .. } => router.subscription_observer(),
ServiceSource::Service(_) => None,
},
));
#[cfg(feature = "stateless")]
let service_source = match &self.service_source {
ServiceSource::Router { router, factory } => {
let (tx, mut rx) = notification_channel(256);
let direct_subscriptions = modern_subscriptions.clone();
router.attach_modern_notification_sink(Arc::new(move |notification| {
direct_subscriptions.publish(notification)
}));
let subscriptions = modern_subscriptions.clone();
tokio::spawn(async move {
while let Some(notification) = rx.recv().await {
subscriptions.publish(¬ification);
}
});
ServiceSource::Router {
router: router.clone().with_notification_sender(tx),
factory: factory.clone(),
}
}
ServiceSource::Service(service) => ServiceSource::Service(service.clone()),
};
#[cfg(not(feature = "stateless"))]
let service_source = self.service_source.clone();
let sessions = Arc::new(SessionRegistry::new(
self.session_config.clone(),
self.sampling_enabled,
self.session_store.clone(),
self.event_store.clone(),
service_source.clone(),
self.auto_reinit_sessions,
));
let cleanup_sessions = sessions.clone();
let cleanup_interval = self.session_config.cleanup_interval;
tokio::spawn(async move {
loop {
tokio::time::sleep(cleanup_interval).await;
cleanup_sessions.cleanup_expired().await;
}
});
Arc::new(AppState {
service_source,
protocol_support: self.protocol_support.clone(),
sessions,
validate_origin: self.validate_origin,
allowed_origins: self.allowed_origins.clone(),
validate_host: self.validate_host,
allowed_hosts: self.allowed_hosts.clone(),
optional_sessions: self.optional_sessions,
strict_initialization: self.session_config.strict_initialization,
#[cfg(feature = "stateless")]
stateless_config: self.stateless_config.clone(),
#[cfg(feature = "stateless")]
stamp_server_info: self.stamp_server_info,
#[cfg(feature = "stateless")]
modern_subscriptions,
sse_responses: self.sse_responses,
extension_bridges: self.extension_bridges.clone(),
max_body_size: self.max_body_size,
})
}
pub fn into_router(self) -> Router {
let (router, _handle) = self.into_router_with_handle();
router
}
pub fn into_router_with_handle(mut self) -> (Router, SessionHandle) {
let external_rx = self.external_notifications.take();
let state = self.build_state();
let handle = SessionHandle {
store: state.sessions.clone(),
#[cfg(feature = "stateless")]
modern_subscriptions: state.modern_subscriptions.clone(),
};
spawn_external_notification_fanout(
external_rx,
state.sessions.clone(),
#[cfg(feature = "stateless")]
state.modern_subscriptions.clone(),
);
let router = Router::new()
.route("/", post(handle_post))
.route("/", get(handle_get))
.route("/", delete(handle_delete))
.route("/health", get(handle_health))
.with_state(state);
#[cfg(feature = "oauth")]
let router = self.add_oauth_route(router, "");
(router, handle)
}
pub fn into_router_at(self, path: &str) -> Router {
let (router, _handle) = self.into_router_at_with_handle(path);
router
}
pub fn into_router_at_with_handle(mut self, path: &str) -> (Router, SessionHandle) {
let external_rx = self.external_notifications.take();
let state = self.build_state();
let handle = SessionHandle {
store: state.sessions.clone(),
#[cfg(feature = "stateless")]
modern_subscriptions: state.modern_subscriptions.clone(),
};
spawn_external_notification_fanout(
external_rx,
state.sessions.clone(),
#[cfg(feature = "stateless")]
state.modern_subscriptions.clone(),
);
let mcp_router = Router::new()
.route("/", post(handle_post))
.route("/", get(handle_get))
.route("/", delete(handle_delete))
.route("/health", get(handle_health))
.with_state(state);
let router = Router::new().nest(path, mcp_router);
#[cfg(feature = "oauth")]
let router = self.add_oauth_route(router, path);
(router, handle)
}
pub fn drain_timeout(mut self, timeout: Duration) -> Self {
self.drain_timeout = Some(timeout);
self
}
pub async fn serve(self, addr: &str) -> Result<()> {
self.serve_with_shutdown(addr, std::future::pending::<()>())
.await
}
pub async fn serve_with_shutdown<F>(self, addr: &str, signal: F) -> Result<()>
where
F: Future<Output = ()> + Send + 'static,
{
let listener = tokio::net::TcpListener::bind(addr)
.await
.map_err(|e| Error::Transport(format!("Failed to bind to {}: {}", addr, e)))?;
tracing::info!("MCP HTTP transport listening on {}", addr);
let drain_timeout = self.drain_timeout;
let router = self.into_router();
crate::transport::graceful::serve_with_shutdown(listener, router, signal, drain_timeout)
.await
}
#[cfg(feature = "oauth")]
fn add_oauth_route(&self, router: Router, _base_path: &str) -> Router {
if let Some(ref config) = self.oauth_config {
let metadata = config.metadata.clone();
let well_known_path =
crate::oauth::ProtectedResourceMetadata::well_known_path_for_resource(
&metadata.resource,
)
.unwrap_or_else(|_| {
crate::oauth::ProtectedResourceMetadata::well_known_path().to_string()
});
router.route(
&well_known_path,
get(move || {
let m = metadata.clone();
async move { axum::Json(m) }
}),
)
} else {
router
}
}
}
fn spawn_external_notification_fanout(
rx: Option<NotificationReceiver>,
sessions: Arc<SessionRegistry>,
#[cfg(feature = "stateless")] modern_subscriptions: Arc<ModernSubscriptionRegistry>,
) {
let Some(mut rx) = rx else {
return;
};
tokio::spawn(async move {
while let Some(notification) = rx.recv().await {
#[cfg(feature = "stateless")]
modern_subscriptions.publish(¬ification);
if let Some(json) = crate::transport::stdio::serialize_notification(¬ification) {
sessions.broadcast_to_all(&json).await;
}
}
tracing::debug!("External notification channel closed; fan-out task exiting");
});
}
mod handlers;
mod session;
#[cfg(feature = "stateless")]
mod stateless_dispatch;
use handlers::{handle_delete, handle_get, handle_health, handle_post};
pub use session::{DEFAULT_SESSION_TTL, SessionConfig, SessionHandle, SessionInfo};
#[cfg(feature = "stateless")]
use handlers::{extract_request_id, json_rpc_error_response_with_status};
use session::{Session, SessionRegistry};
#[cfg(feature = "stateless")]
use stateless_dispatch::{
CancelOnDisconnect, ModernSubscriptionRegistry, StatelessSseContext,
handle_modern_subscriptions_listen_sse, is_stateless_protocol_version, modern_response_status,
stamp_server_info, stash_per_request_meta, stateless_sse_with_notifications,
};
#[cfg(test)]
mod tests;