use std::collections::HashMap;
use std::sync::Arc;
use tracing::{debug, info};
use turul_http_mcp_server::{ServerConfig, StreamConfig, StreamManager};
use turul_mcp_protocol::{Implementation, ServerCapabilities};
use turul_mcp_server::{
McpCompletion, McpElicitation, McpLogger, McpNotification, McpPrompt, McpResource, McpRoot,
McpSampling, McpTool, handlers::McpHandler, session::SessionManager,
};
use turul_mcp_session_storage::BoxedSessionStorage;
use crate::error::Result;
use crate::handler::LambdaMcpHandler;
#[cfg(feature = "cors")]
use crate::cors::CorsConfig;
#[allow(dead_code)]
pub struct LambdaMcpServer {
pub implementation: Implementation,
pub capabilities: ServerCapabilities,
tools: HashMap<String, Arc<dyn McpTool>>,
resources: HashMap<String, Arc<dyn McpResource>>,
prompts: HashMap<String, Arc<dyn McpPrompt>>,
elicitations: HashMap<String, Arc<dyn McpElicitation>>,
sampling: HashMap<String, Arc<dyn McpSampling>>,
completions: HashMap<String, Arc<dyn McpCompletion>>,
loggers: HashMap<String, Arc<dyn McpLogger>>,
root_providers: HashMap<String, Arc<dyn McpRoot>>,
notifications: HashMap<String, Arc<dyn McpNotification>>,
handlers: HashMap<String, Arc<dyn McpHandler>>,
roots: Vec<turul_mcp_protocol::roots::Root>,
instructions: Option<String>,
session_manager: Arc<SessionManager>,
session_storage: Arc<BoxedSessionStorage>,
strict_lifecycle: bool,
server_config: ServerConfig,
enable_sse: bool,
stream_config: StreamConfig,
#[cfg(feature = "cors")]
cors_config: Option<CorsConfig>,
middleware_stack: turul_http_mcp_server::middleware::MiddlewareStack,
route_registry: Arc<turul_http_mcp_server::RouteRegistry>,
task_runtime: Option<Arc<turul_mcp_server::TaskRuntime>>,
tool_fingerprint: String,
#[cfg(feature = "dynamic-tools")]
tool_registry: Option<Arc<turul_mcp_server::ToolRegistry>>,
#[cfg(feature = "dynamic-tools")]
coordination_enabled: bool,
}
impl LambdaMcpServer {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
implementation: Implementation,
capabilities: ServerCapabilities,
tools: HashMap<String, Arc<dyn McpTool>>,
resources: HashMap<String, Arc<dyn McpResource>>,
prompts: HashMap<String, Arc<dyn McpPrompt>>,
elicitations: HashMap<String, Arc<dyn McpElicitation>>,
sampling: HashMap<String, Arc<dyn McpSampling>>,
completions: HashMap<String, Arc<dyn McpCompletion>>,
loggers: HashMap<String, Arc<dyn McpLogger>>,
root_providers: HashMap<String, Arc<dyn McpRoot>>,
notifications: HashMap<String, Arc<dyn McpNotification>>,
handlers: HashMap<String, Arc<dyn McpHandler>>,
roots: Vec<turul_mcp_protocol::roots::Root>,
instructions: Option<String>,
session_storage: Arc<BoxedSessionStorage>,
strict_lifecycle: bool,
server_config: ServerConfig,
enable_sse: bool,
stream_config: StreamConfig,
#[cfg(feature = "cors")] cors_config: Option<CorsConfig>,
middleware_stack: turul_http_mcp_server::middleware::MiddlewareStack,
route_registry: Arc<turul_http_mcp_server::RouteRegistry>,
task_runtime: Option<Arc<turul_mcp_server::TaskRuntime>>,
tool_fingerprint: String,
#[cfg(feature = "dynamic-tools")] dynamic_tools: bool,
#[cfg(feature = "dynamic-tools")]
server_state_storage: Option<Arc<dyn turul_mcp_server_state_storage::ServerStateStorage>>,
) -> Self {
let session_manager = Arc::new(SessionManager::with_storage_and_timeouts(
Arc::clone(&session_storage),
capabilities.clone(),
std::time::Duration::from_secs(30 * 60), std::time::Duration::from_secs(60), ));
#[cfg(feature = "dynamic-tools")]
let coordination_enabled = server_state_storage
.as_ref()
.map(|s| matches!(s.backend_name(), "PostgreSQL" | "DynamoDB"))
.unwrap_or(false);
#[cfg(feature = "dynamic-tools")]
let tool_registry = if dynamic_tools {
let storage = server_state_storage.unwrap_or_else(|| {
Arc::new(turul_mcp_server_state_storage::InMemoryServerStateStorage::new())
});
Some(Arc::new(turul_mcp_server::ToolRegistry::new(
tools.clone(),
session_manager.clone(),
storage,
)))
} else {
None
};
Self {
implementation,
capabilities,
tools,
resources,
prompts,
elicitations,
sampling,
completions,
loggers,
root_providers,
notifications,
handlers,
roots,
instructions,
session_manager,
session_storage,
strict_lifecycle,
server_config,
enable_sse,
stream_config,
#[cfg(feature = "cors")]
cors_config,
middleware_stack,
route_registry,
task_runtime,
tool_fingerprint,
#[cfg(feature = "dynamic-tools")]
tool_registry,
#[cfg(feature = "dynamic-tools")]
coordination_enabled,
}
}
pub fn capabilities(&self) -> &ServerCapabilities {
&self.capabilities
}
pub async fn handler(&self) -> Result<LambdaMcpHandler> {
info!(
"Creating Lambda MCP handler: {} v{}",
self.implementation.name, self.implementation.version
);
info!("Session management: enabled with automatic cleanup");
if self.enable_sse {
info!("SSE notifications: enabled for Lambda responses");
#[cfg(not(feature = "streaming"))]
{
use tracing::warn;
warn!("⚠️ SSE is enabled but 'streaming' feature is not available!");
warn!(
" For real SSE streaming, use handle_streaming() with run_with_streaming_response"
);
warn!(
" Current handle() method will return SSE snapshots, not real-time streams"
);
warn!(" To enable streaming: add 'streaming' feature to turul-mcp-aws-lambda");
}
}
let _cleanup_task = self.session_manager.clone().start_cleanup_task();
#[cfg(feature = "dynamic-tools")]
if self.coordination_enabled {
if let Some(ref registry) = self.tool_registry {
use tracing::warn;
match registry.sync_from_storage().await {
Ok(_) => {
info!("Dynamic: synced tool registry with shared storage");
}
Err(e) => {
warn!(error = %e, "Dynamic: failed to sync with shared storage on cold start");
}
}
}
}
if let Some(ref runtime) = self.task_runtime {
match runtime.recover_stuck_tasks().await {
Ok(recovered) if !recovered.is_empty() => {
info!(
count = recovered.len(),
"Recovered stuck tasks from previous invocations"
);
}
Err(e) => {
use tracing::warn;
warn!(error = %e, "Failed to recover stuck tasks on startup");
}
_ => {}
}
}
let stream_manager = Arc::new(StreamManager::with_config(
self.session_storage.clone(),
self.stream_config.clone(),
));
{
let bridge_stream_manager = Arc::clone(&stream_manager);
let mut global_events = self.session_manager.subscribe_all_session_events();
tokio::spawn(async move {
debug!("Lambda SSE Event Bridge: started");
while let Ok((session_id, event)) = global_events.recv().await {
if let turul_mcp_server::session::SessionEvent::Custom {
event_type,
data,
} = event
{
if let Err(e) = bridge_stream_manager
.broadcast_to_session(&session_id, event_type, data)
.await
{
debug!(
"Lambda SSE Bridge: broadcast to session {} failed: {} (normal if no active connections)",
session_id, e
);
}
}
}
debug!("Lambda SSE Event Bridge: stopped");
});
}
use turul_mcp_json_rpc_server::JsonRpcDispatcher;
let mut dispatcher = JsonRpcDispatcher::new();
use turul_mcp_server::SessionAwareInitializeHandler;
let init_handler = SessionAwareInitializeHandler::new(
self.implementation.clone(),
self.capabilities.clone(),
self.instructions.clone(),
self.session_manager.clone(),
self.strict_lifecycle,
self.tool_fingerprint.clone(),
);
dispatcher.register_method("initialize".to_string(), init_handler);
use turul_mcp_server::ListToolsHandler;
let mut list_handler = ListToolsHandler::new_with_session_manager(
self.tools.clone(),
self.session_manager.clone(),
self.strict_lifecycle,
self.task_runtime.is_some(),
);
#[cfg(feature = "dynamic-tools")]
if let Some(ref registry) = self.tool_registry {
list_handler = list_handler.with_tool_registry(Arc::clone(registry));
}
dispatcher.register_method("tools/list".to_string(), list_handler);
use turul_mcp_server::SessionAwareToolHandler;
let mut tool_handler = SessionAwareToolHandler::new(
self.tools.clone(),
self.session_manager.clone(),
self.strict_lifecycle,
);
if let Some(ref runtime) = self.task_runtime {
tool_handler = tool_handler.with_task_runtime(Arc::clone(runtime));
}
#[cfg(feature = "dynamic-tools")]
if let Some(ref registry) = self.tool_registry {
tool_handler = tool_handler.with_tool_registry(Arc::clone(registry));
}
dispatcher.register_method("tools/call".to_string(), tool_handler);
use turul_mcp_server::SessionAwareMcpHandlerBridge;
for (method, handler) in &self.handlers {
let bridge_handler = SessionAwareMcpHandlerBridge::new(
handler.clone(),
self.session_manager.clone(),
self.strict_lifecycle,
);
dispatcher.register_method(method.clone(), bridge_handler);
}
use turul_mcp_server::handlers::InitializedNotificationHandler;
let initialized_handler = InitializedNotificationHandler::new(self.session_manager.clone());
let initialized_bridge = SessionAwareMcpHandlerBridge::new(
Arc::new(initialized_handler),
self.session_manager.clone(),
self.strict_lifecycle,
);
dispatcher.register_method("notifications/initialized".to_string(), initialized_bridge);
let middleware_stack = Arc::new(self.middleware_stack.clone());
let handler = LambdaMcpHandler::with_middleware_and_fingerprint(
self.server_config.clone(),
Arc::new(dispatcher),
self.session_storage.clone(),
stream_manager,
self.stream_config.clone(),
self.capabilities.clone(),
middleware_stack,
self.enable_sse,
Arc::clone(&self.route_registry),
Some(self.tool_fingerprint.clone()),
);
#[cfg(feature = "dynamic-tools")]
let handler = if let Some(ref registry) = self.tool_registry {
handler.with_tool_registry(Arc::clone(registry))
} else {
handler
};
Ok(handler)
}
pub fn session_storage_info(&self) -> &str {
"Session storage configured"
}
}