use bytes::Bytes;
use http_body_util::{BodyExt, Full};
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::TcpListener;
use tracing::{debug, error, info, warn};
use turul_mcp_json_rpc_server::{JsonRpcDispatcher, JsonRpcHandler};
use turul_mcp_protocol::McpError;
use turul_mcp_session_storage::InMemorySessionStorage;
use crate::streamable_http::{McpProtocolVersion, StreamableHttpHandler};
use crate::{CorsLayer, Result, SessionMcpHandler, StreamConfig, StreamManager};
#[derive(Debug, Clone)]
pub struct ServerConfig {
pub bind_address: SocketAddr,
pub mcp_path: String,
pub enable_cors: bool,
pub max_body_size: usize,
pub enable_get_sse: bool,
pub enable_post_sse: bool,
pub session_expiry_minutes: u64,
pub allow_unauthenticated_ping: bool,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
bind_address: "127.0.0.1:8000".parse().unwrap(),
mcp_path: "/mcp".to_string(),
enable_cors: true,
max_body_size: 1024 * 1024, enable_get_sse: cfg!(feature = "sse"), enable_post_sse: false, session_expiry_minutes: 30, allow_unauthenticated_ping: true, }
}
}
pub struct HttpMcpServerBuilder {
config: ServerConfig,
dispatcher: JsonRpcDispatcher<McpError>,
session_storage: Option<Arc<turul_mcp_session_storage::BoxedSessionStorage>>,
stream_config: StreamConfig,
server_capabilities: Option<turul_mcp_protocol::ServerCapabilities>,
middleware_stack: Arc<crate::middleware::MiddlewareStack>,
route_registry: Arc<crate::routes::RouteRegistry>,
tool_fingerprint: Option<String>,
tool_notifier: Option<Arc<dyn crate::ToolChangeNotifier>>,
}
impl HttpMcpServerBuilder {
pub fn new() -> Self {
Self {
config: ServerConfig::default(),
dispatcher: JsonRpcDispatcher::<McpError>::new(),
session_storage: Some(Arc::new(InMemorySessionStorage::new())),
stream_config: StreamConfig::default(),
server_capabilities: None,
middleware_stack: Arc::new(crate::middleware::MiddlewareStack::new()),
route_registry: Arc::new(crate::routes::RouteRegistry::new()),
tool_fingerprint: None,
tool_notifier: None,
}
}
}
impl HttpMcpServerBuilder {
pub fn with_storage(
session_storage: Arc<turul_mcp_session_storage::BoxedSessionStorage>,
) -> Self {
Self {
config: ServerConfig::default(),
dispatcher: JsonRpcDispatcher::<McpError>::new(),
session_storage: Some(session_storage),
stream_config: StreamConfig::default(),
server_capabilities: None,
middleware_stack: Arc::new(crate::middleware::MiddlewareStack::new()),
route_registry: Arc::new(crate::routes::RouteRegistry::new()),
tool_fingerprint: None,
tool_notifier: None,
}
}
pub fn with_middleware_stack(
mut self,
middleware_stack: Arc<crate::middleware::MiddlewareStack>,
) -> Self {
self.middleware_stack = middleware_stack;
self
}
pub fn route_registry(mut self, registry: Arc<crate::routes::RouteRegistry>) -> Self {
self.route_registry = registry;
self
}
pub fn tool_fingerprint(mut self, fingerprint: String) -> Self {
if fingerprint.is_empty() {
self.tool_fingerprint = None; } else {
self.tool_fingerprint = Some(fingerprint);
}
self
}
pub fn tool_notifier(mut self, notifier: Arc<dyn crate::ToolChangeNotifier>) -> Self {
self.tool_notifier = Some(notifier);
self
}
pub fn bind_address(mut self, addr: SocketAddr) -> Self {
self.config.bind_address = addr;
self
}
pub fn mcp_path(mut self, path: impl Into<String>) -> Self {
self.config.mcp_path = path.into();
self
}
pub fn cors(mut self, enable: bool) -> Self {
self.config.enable_cors = enable;
self
}
pub fn max_body_size(mut self, size: usize) -> Self {
self.config.max_body_size = size;
self
}
pub fn get_sse(mut self, enable: bool) -> Self {
self.config.enable_get_sse = enable;
self
}
pub fn post_sse(mut self, enable: bool) -> Self {
self.config.enable_post_sse = enable;
self
}
pub fn sse(mut self, enable: bool) -> Self {
self.config.enable_get_sse = enable;
self.config.enable_post_sse = enable;
self
}
pub fn session_expiry_minutes(mut self, minutes: u64) -> Self {
self.config.session_expiry_minutes = minutes;
self
}
pub fn allow_unauthenticated_ping(mut self, allow: bool) -> Self {
self.config.allow_unauthenticated_ping = allow;
self
}
pub fn stream_config(mut self, config: StreamConfig) -> Self {
self.stream_config = config;
self
}
pub fn register_handler<H>(mut self, methods: Vec<String>, handler: H) -> Self
where
H: JsonRpcHandler<Error = McpError> + 'static,
{
self.dispatcher.register_methods(methods, handler);
self
}
pub fn default_handler<H>(mut self, handler: H) -> Self
where
H: JsonRpcHandler<Error = McpError> + 'static,
{
self.dispatcher.set_default_handler(handler);
self
}
pub fn server_capabilities(
mut self,
capabilities: turul_mcp_protocol::ServerCapabilities,
) -> Self {
self.server_capabilities = Some(capabilities);
self
}
pub fn build(self) -> HttpMcpServer {
let session_storage = self
.session_storage
.expect("Session storage must be provided");
let stream_manager = Arc::new(StreamManager::with_config(
Arc::clone(&session_storage),
self.stream_config.clone(),
));
let dispatcher = Arc::new(self.dispatcher);
let middleware_stack = self.middleware_stack;
let mut streamable_handler = StreamableHttpHandler::new(
Arc::new(self.config.clone()),
Arc::clone(&dispatcher),
Arc::clone(&session_storage),
Arc::clone(&stream_manager),
self.server_capabilities.unwrap_or_default(),
Arc::clone(&middleware_stack),
self.tool_fingerprint.clone(),
);
if let Some(ref notifier) = self.tool_notifier {
streamable_handler = streamable_handler.with_tool_notifier(Arc::clone(notifier));
}
HttpMcpServer {
config: self.config,
dispatcher,
session_storage,
stream_config: self.stream_config,
stream_manager,
streamable_handler,
route_registry: self.route_registry,
tool_fingerprint: self.tool_fingerprint,
tool_notifier: self.tool_notifier,
}
}
}
impl Default for HttpMcpServerBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct HttpMcpServer {
config: ServerConfig,
dispatcher: Arc<JsonRpcDispatcher<McpError>>,
session_storage: Arc<turul_mcp_session_storage::BoxedSessionStorage>,
stream_config: StreamConfig,
stream_manager: Arc<StreamManager>,
streamable_handler: StreamableHttpHandler,
route_registry: Arc<crate::routes::RouteRegistry>,
tool_fingerprint: Option<String>,
tool_notifier: Option<Arc<dyn crate::ToolChangeNotifier>>,
}
impl HttpMcpServer {
pub fn builder() -> HttpMcpServerBuilder {
HttpMcpServerBuilder::new()
}
}
impl HttpMcpServer {
pub fn builder_with_storage(
session_storage: Arc<turul_mcp_session_storage::BoxedSessionStorage>,
) -> HttpMcpServerBuilder {
HttpMcpServerBuilder::with_storage(session_storage)
}
pub fn get_stream_manager(&self) -> Arc<crate::StreamManager> {
Arc::clone(&self.stream_manager)
}
pub async fn run(&self) -> Result<()> {
self.start_session_cleanup().await;
let listener = TcpListener::bind(&self.config.bind_address).await?;
info!("HTTP MCP server listening on {}", self.config.bind_address);
info!("MCP endpoint available at: {}", self.config.mcp_path);
info!("Session storage: {}", self.session_storage.backend_name());
let mut session_handler = SessionMcpHandler::with_shared_stream_manager(
self.config.clone(),
Arc::clone(&self.dispatcher),
Arc::clone(&self.session_storage),
self.stream_config.clone(),
Arc::clone(&self.stream_manager),
Arc::clone(&self.streamable_handler.middleware_stack),
)
.with_tool_fingerprint(self.tool_fingerprint.clone());
if let Some(ref notifier) = self.tool_notifier {
session_handler = session_handler.with_tool_notifier(Arc::clone(notifier));
}
let handler = McpRequestHandler {
session_handler,
streamable_handler: self.streamable_handler.clone(),
route_registry: Arc::clone(&self.route_registry),
};
loop {
let (stream, peer_addr) = listener.accept().await?;
debug!("New connection from {}", peer_addr);
let handler_clone = handler.clone();
tokio::spawn(async move {
let io = TokioIo::new(stream);
let service = service_fn(move |req| handle_request(req, handler_clone.clone()));
if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
let err_str = err.to_string();
if err_str.contains("connection closed before message completed") {
debug!("Client disconnected (normal): {}", err);
} else {
error!("Error serving connection: {}", err);
}
}
});
}
}
async fn start_session_cleanup(&self) {
let storage = Arc::clone(&self.session_storage);
let session_expiry_minutes = self.config.session_expiry_minutes;
tokio::spawn(async move {
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(60));
loop {
interval.tick().await;
let expire_time = std::time::SystemTime::now()
- std::time::Duration::from_secs(session_expiry_minutes * 60);
match storage.expire_sessions(expire_time).await {
Ok(expired) => {
if !expired.is_empty() {
info!("Expired {} sessions", expired.len());
for session_id in expired {
debug!("Expired session: {}", session_id);
}
}
}
Err(err) => {
error!("Session cleanup error: {}", err);
}
}
}
});
}
pub async fn get_stats(&self) -> ServerStats {
let session_count = self.session_storage.session_count().await.unwrap_or(0);
let event_count = self.session_storage.event_count().await.unwrap_or(0);
ServerStats {
sessions: session_count,
events: event_count,
storage_type: self.session_storage.backend_name().to_string(),
}
}
}
#[derive(Clone)]
struct McpRequestHandler {
session_handler: SessionMcpHandler,
streamable_handler: StreamableHttpHandler,
route_registry: Arc<crate::routes::RouteRegistry>,
}
async fn handle_request(
req: Request<hyper::body::Incoming>,
handler: McpRequestHandler,
) -> std::result::Result<
Response<http_body_util::combinators::UnsyncBoxBody<Bytes, hyper::Error>>,
hyper::Error,
> {
let method = req.method().clone();
let uri = req.uri().clone();
let path = uri.path();
debug!("Handling {} {}", method, path);
debug!(
"HTTP server dispatch: path={}, expected_mcp_path={}",
path, handler.session_handler.config.mcp_path
);
let response = if path == handler.session_handler.config.mcp_path {
debug!("Path match: Request routed to MCP handler");
let protocol_version_str = req
.headers()
.get("MCP-Protocol-Version")
.and_then(|h| h.to_str().ok())
.unwrap_or("2025-11-25"); debug!("Protocol version: {}", protocol_version_str);
let protocol_version = McpProtocolVersion::parse_version(protocol_version_str)
.unwrap_or(McpProtocolVersion::V2025_11_25);
debug!(
"MCP request: protocol_version={}, method={}",
protocol_version.as_str(),
method
);
debug!(
"Routing decision: protocol_version={}, method={}, supports_streamable={}, handler={}",
protocol_version.as_str(),
method,
protocol_version.supports_streamable_http(),
if protocol_version.supports_streamable_http() {
"StreamableHttpHandler"
} else {
"SessionMcpHandler"
}
);
if protocol_version.supports_streamable_http() {
debug!(
"Calling streamable handler for protocol {}",
protocol_version.as_str()
);
let streamable_response = handler.streamable_handler.handle_request(req).await;
debug!("Streamable handler completed");
Ok(streamable_response)
} else {
match handler.session_handler.handle_mcp_request(req).await {
Ok(mcp_response) => Ok(mcp_response),
Err(err) => {
error!("Request handling error: {}", err);
Ok(Response::builder()
.status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
.body(
Full::new(Bytes::from(format!("Internal Server Error: {}", err)))
.map_err(|never| match never {})
.boxed_unsync(),
)
.unwrap())
}
}
}
} else {
match handler.route_registry.match_route(path) {
Ok(Some(route_handler)) => {
debug!("Custom route matched: {}", path);
let (parts, body) = req.into_parts();
let boxed_req = Request::from_parts(parts, body.boxed_unsync());
Ok(route_handler.handle(boxed_req).await)
}
Ok(None) => {
Ok(Response::builder()
.status(hyper::StatusCode::NOT_FOUND)
.body(
Full::new(Bytes::from("Not Found"))
.map_err(|never| match never {})
.boxed_unsync(),
)
.unwrap())
}
Err(validation_err) => {
warn!(
"Route validation failed for path '{}': {}",
path, validation_err
);
Ok(validation_err.into_response())
}
}
};
match response {
Ok(mut final_response) => {
if handler.session_handler.config.enable_cors {
CorsLayer::apply_cors_headers(final_response.headers_mut());
}
Ok(final_response)
}
Err(e) => Err(e),
}
}
#[derive(Debug, Clone)]
pub struct ServerStats {
pub sessions: usize,
pub events: usize,
pub storage_type: String,
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc;
use turul_mcp_session_storage::InMemorySessionStorage;
#[test]
fn test_server_config_default() {
let config = ServerConfig::default();
assert_eq!(config.mcp_path, "/mcp");
assert!(config.enable_cors);
assert_eq!(config.max_body_size, 1024 * 1024);
}
#[test]
fn test_builder() {
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 3000);
let session_storage = Arc::new(InMemorySessionStorage::new());
let server = HttpMcpServer::builder_with_storage(session_storage)
.bind_address(addr)
.mcp_path("/api/mcp")
.cors(false)
.max_body_size(2048)
.build();
assert_eq!(server.config.bind_address, addr);
assert_eq!(server.config.mcp_path, "/api/mcp");
assert!(!server.config.enable_cors);
assert_eq!(server.config.max_body_size, 2048);
}
#[tokio::test]
async fn test_server_stats() {
let session_storage = Arc::new(InMemorySessionStorage::new());
let server = HttpMcpServer::builder_with_storage(session_storage).build();
let stats = server.get_stats().await;
assert_eq!(stats.sessions, 0);
assert_eq!(stats.events, 0);
assert_eq!(stats.storage_type, "InMemory");
}
}