1use std::collections::HashMap;
7use std::sync::Arc;
8
9use tracing::{debug, info};
10
11use turul_http_mcp_server::{ServerConfig, StreamConfig, StreamManager};
12use turul_mcp_protocol::{Implementation, ServerCapabilities};
13use turul_mcp_server::{
14 McpCompletion, McpElicitation, McpLogger, McpNotification, McpPrompt, McpResource, McpRoot,
15 McpSampling, McpTool, handlers::McpHandler, session::SessionManager,
16};
17use turul_mcp_session_storage::BoxedSessionStorage;
18
19use crate::error::Result;
20use crate::handler::LambdaMcpHandler;
21
22#[cfg(feature = "cors")]
23use crate::cors::CorsConfig;
24
25#[allow(dead_code)]
30pub struct LambdaMcpServer {
31 pub implementation: Implementation,
33 pub capabilities: ServerCapabilities,
35 tools: HashMap<String, Arc<dyn McpTool>>,
37 resources: HashMap<String, Arc<dyn McpResource>>,
39 prompts: HashMap<String, Arc<dyn McpPrompt>>,
41 elicitations: HashMap<String, Arc<dyn McpElicitation>>,
43 sampling: HashMap<String, Arc<dyn McpSampling>>,
45 completions: HashMap<String, Arc<dyn McpCompletion>>,
47 loggers: HashMap<String, Arc<dyn McpLogger>>,
49 root_providers: HashMap<String, Arc<dyn McpRoot>>,
51 notifications: HashMap<String, Arc<dyn McpNotification>>,
53 handlers: HashMap<String, Arc<dyn McpHandler>>,
55 roots: Vec<turul_mcp_protocol::roots::Root>,
57 instructions: Option<String>,
59 session_manager: Arc<SessionManager>,
61 session_storage: Arc<BoxedSessionStorage>,
63 strict_lifecycle: bool,
65 server_config: ServerConfig,
67 enable_sse: bool,
69 stream_config: StreamConfig,
71 #[cfg(feature = "cors")]
73 cors_config: Option<CorsConfig>,
74 middleware_stack: turul_http_mcp_server::middleware::MiddlewareStack,
76 route_registry: Arc<turul_http_mcp_server::RouteRegistry>,
78 task_runtime: Option<Arc<turul_mcp_server::TaskRuntime>>,
80 tool_fingerprint: String,
82 #[cfg(feature = "dynamic-tools")]
84 tool_registry: Option<Arc<turul_mcp_server::ToolRegistry>>,
85 #[cfg(feature = "dynamic-tools")]
87 coordination_enabled: bool,
88}
89
90impl LambdaMcpServer {
91 #[allow(clippy::too_many_arguments)]
93 pub(crate) fn new(
94 implementation: Implementation,
95 capabilities: ServerCapabilities,
96 tools: HashMap<String, Arc<dyn McpTool>>,
97 resources: HashMap<String, Arc<dyn McpResource>>,
98 prompts: HashMap<String, Arc<dyn McpPrompt>>,
99 elicitations: HashMap<String, Arc<dyn McpElicitation>>,
100 sampling: HashMap<String, Arc<dyn McpSampling>>,
101 completions: HashMap<String, Arc<dyn McpCompletion>>,
102 loggers: HashMap<String, Arc<dyn McpLogger>>,
103 root_providers: HashMap<String, Arc<dyn McpRoot>>,
104 notifications: HashMap<String, Arc<dyn McpNotification>>,
105 handlers: HashMap<String, Arc<dyn McpHandler>>,
106 roots: Vec<turul_mcp_protocol::roots::Root>,
107 instructions: Option<String>,
108 session_storage: Arc<BoxedSessionStorage>,
109 strict_lifecycle: bool,
110 server_config: ServerConfig,
111 enable_sse: bool,
112 stream_config: StreamConfig,
113 #[cfg(feature = "cors")] cors_config: Option<CorsConfig>,
114 middleware_stack: turul_http_mcp_server::middleware::MiddlewareStack,
115 route_registry: Arc<turul_http_mcp_server::RouteRegistry>,
116 task_runtime: Option<Arc<turul_mcp_server::TaskRuntime>>,
117 tool_fingerprint: String,
118 #[cfg(feature = "dynamic-tools")] dynamic_tools: bool,
119 #[cfg(feature = "dynamic-tools")] server_state_storage: Option<
120 Arc<dyn turul_mcp_server_state_storage::ServerStateStorage>,
121 >,
122 ) -> Self {
123 let session_manager = Arc::new(SessionManager::with_storage_and_timeouts(
125 Arc::clone(&session_storage),
126 capabilities.clone(),
127 std::time::Duration::from_secs(30 * 60), std::time::Duration::from_secs(60), ));
130
131 #[cfg(feature = "dynamic-tools")]
134 let coordination_enabled = server_state_storage
135 .as_ref()
136 .map(|s| matches!(s.backend_name(), "PostgreSQL" | "DynamoDB"))
137 .unwrap_or(false);
138
139 #[cfg(feature = "dynamic-tools")]
141 let tool_registry = if dynamic_tools {
142 let storage = server_state_storage.unwrap_or_else(|| {
143 Arc::new(turul_mcp_server_state_storage::InMemoryServerStateStorage::new())
144 });
145 Some(Arc::new(turul_mcp_server::ToolRegistry::new(
146 tools.clone(),
147 session_manager.clone(),
148 storage,
149 )))
150 } else {
151 None
152 };
153
154 Self {
155 implementation,
156 capabilities,
157 tools,
158 resources,
159 prompts,
160 elicitations,
161 sampling,
162 completions,
163 loggers,
164 root_providers,
165 notifications,
166 handlers,
167 roots,
168 instructions,
169 session_manager,
170 session_storage,
171 strict_lifecycle,
172 server_config,
173 enable_sse,
174 stream_config,
175 #[cfg(feature = "cors")]
176 cors_config,
177 middleware_stack,
178 route_registry,
179 task_runtime,
180 tool_fingerprint,
181 #[cfg(feature = "dynamic-tools")]
182 tool_registry,
183 #[cfg(feature = "dynamic-tools")]
184 coordination_enabled,
185 }
186 }
187
188 pub fn capabilities(&self) -> &ServerCapabilities {
190 &self.capabilities
191 }
192
193 pub async fn handler(&self) -> Result<LambdaMcpHandler> {
197 info!(
198 "Creating Lambda MCP handler: {} v{}",
199 self.implementation.name, self.implementation.version
200 );
201 info!("Session management: enabled with automatic cleanup");
202
203 if self.enable_sse {
204 info!("SSE notifications: enabled for Lambda responses");
205
206 #[cfg(not(feature = "streaming"))]
208 {
209 use tracing::warn;
210 warn!("⚠️ SSE is enabled but 'streaming' feature is not available!");
211 warn!(
212 " For real SSE streaming, use handle_streaming() with run_with_streaming_response"
213 );
214 warn!(
215 " Current handle() method will return SSE snapshots, not real-time streams"
216 );
217 warn!(" To enable streaming: add 'streaming' feature to turul-mcp-aws-lambda");
218 }
219 }
220
221 let _cleanup_task = self.session_manager.clone().start_cleanup_task();
223
224 #[cfg(feature = "dynamic-tools")]
226 if self.coordination_enabled {
227 if let Some(ref registry) = self.tool_registry {
228 use tracing::warn;
229 match registry.sync_from_storage().await {
230 Ok(_) => {
231 info!("Dynamic: synced tool registry with shared storage");
232 }
233 Err(e) => {
234 warn!(error = %e, "Dynamic: failed to sync with shared storage on cold start");
235 }
236 }
237 }
238 }
239
240 if let Some(ref runtime) = self.task_runtime {
243 match runtime.recover_stuck_tasks().await {
244 Ok(recovered) if !recovered.is_empty() => {
245 info!(
246 count = recovered.len(),
247 "Recovered stuck tasks from previous invocations"
248 );
249 }
250 Err(e) => {
251 use tracing::warn;
252 warn!(error = %e, "Failed to recover stuck tasks on startup");
253 }
254 _ => {}
255 }
256 }
257
258 let stream_manager = Arc::new(StreamManager::with_config(
260 self.session_storage.clone(),
261 self.stream_config.clone(),
262 ));
263
264 {
268 use turul_mcp_server::SessionEventDispatcher;
269
270 struct LambdaEventDispatcher {
271 stream_manager: Arc<StreamManager>,
272 }
273
274 #[async_trait::async_trait]
275 impl SessionEventDispatcher for LambdaEventDispatcher {
276 async fn dispatch_to_session(
277 &self,
278 session_id: &str,
279 event_type: String,
280 data: serde_json::Value,
281 ) -> std::result::Result<(), String> {
282 self.stream_manager
283 .broadcast_to_session(session_id, event_type, data)
284 .await
285 .map(|_| ())
286 .map_err(|e| e.to_string())
287 }
288 }
289
290 let dispatcher = Arc::new(LambdaEventDispatcher {
291 stream_manager: Arc::clone(&stream_manager),
292 });
293 self.session_manager.set_event_dispatcher(dispatcher).await;
294 debug!("Lambda event dispatcher installed (guaranteed persistence for Custom events)");
295 }
296
297 {
300 let mut global_events = self.session_manager.subscribe_all_session_events();
301
302 tokio::spawn(async move {
303 debug!("Lambda SSE Event Bridge: started (observer-only for Custom events)");
304
305 while let Ok((session_id, event)) = global_events.recv().await {
306 match event {
307 turul_mcp_server::session::SessionEvent::Custom {
308 ref event_type, ..
309 } => {
310 debug!(
311 "Lambda SSE Bridge: observed custom event '{}' for session {} (dispatcher handles persistence)",
312 event_type, session_id
313 );
314 }
315 _ => {
316 debug!(
317 "Lambda SSE Bridge: non-custom event for session {}",
318 session_id
319 );
320 }
321 }
322 }
323
324 debug!("Lambda SSE Event Bridge: stopped");
325 });
326 }
327
328 use turul_mcp_json_rpc_server::JsonRpcDispatcher;
330 let mut dispatcher = JsonRpcDispatcher::new();
331
332 use turul_mcp_server::SessionAwareInitializeHandler;
334 #[allow(unused_mut)]
335 let mut init_handler = SessionAwareInitializeHandler::new(
336 self.implementation.clone(),
337 self.capabilities.clone(),
338 self.instructions.clone(),
339 self.session_manager.clone(),
340 self.strict_lifecycle,
341 self.tool_fingerprint.clone(),
342 );
343 #[cfg(feature = "dynamic-tools")]
344 if let Some(ref registry) = self.tool_registry {
345 init_handler = init_handler.with_tool_registry(Arc::clone(registry));
346 }
347 dispatcher.register_method("initialize".to_string(), init_handler);
348
349 use turul_mcp_server::ListToolsHandler;
351 #[allow(unused_mut)]
352 let mut list_handler = ListToolsHandler::new_with_session_manager(
353 self.tools.clone(),
354 self.session_manager.clone(),
355 self.strict_lifecycle,
356 self.task_runtime.is_some(),
357 );
358 #[cfg(feature = "dynamic-tools")]
359 if let Some(ref registry) = self.tool_registry {
360 list_handler = list_handler.with_tool_registry(Arc::clone(registry));
361 }
362 dispatcher.register_method("tools/list".to_string(), list_handler);
363
364 use turul_mcp_server::SessionAwareToolHandler;
366 let mut tool_handler = SessionAwareToolHandler::new(
367 self.tools.clone(),
368 self.session_manager.clone(),
369 self.strict_lifecycle,
370 );
371 if let Some(ref runtime) = self.task_runtime {
372 tool_handler = tool_handler.with_task_runtime(Arc::clone(runtime));
373 }
374 #[cfg(feature = "dynamic-tools")]
375 if let Some(ref registry) = self.tool_registry {
376 tool_handler = tool_handler.with_tool_registry(Arc::clone(registry));
377 }
378 dispatcher.register_method("tools/call".to_string(), tool_handler);
379
380 use turul_mcp_server::SessionAwareMcpHandlerBridge;
382 for (method, handler) in &self.handlers {
383 let bridge_handler = SessionAwareMcpHandlerBridge::new(
384 handler.clone(),
385 self.session_manager.clone(),
386 self.strict_lifecycle,
387 );
388 dispatcher.register_method(method.clone(), bridge_handler);
389 }
390
391 use turul_mcp_server::handlers::InitializedNotificationHandler;
394 let initialized_handler = InitializedNotificationHandler::new(self.session_manager.clone());
395 let initialized_bridge = SessionAwareMcpHandlerBridge::new(
396 Arc::new(initialized_handler),
397 self.session_manager.clone(),
398 self.strict_lifecycle,
399 );
400 dispatcher.register_method("notifications/initialized".to_string(), initialized_bridge);
401
402 let middleware_stack = Arc::new(self.middleware_stack.clone());
404
405 let handler = LambdaMcpHandler::with_middleware_and_fingerprint(
406 self.server_config.clone(),
407 Arc::new(dispatcher),
408 self.session_storage.clone(),
409 stream_manager,
410 self.stream_config.clone(),
411 self.capabilities.clone(),
412 middleware_stack,
413 self.enable_sse,
414 Arc::clone(&self.route_registry),
415 Some(self.tool_fingerprint.clone()),
416 );
417
418 let handler = {
422 struct LambdaToolNotifier {
423 session_manager: Arc<turul_mcp_server::SessionManager>,
424 }
425 #[async_trait::async_trait]
426 impl turul_http_mcp_server::ToolChangeNotifier for LambdaToolNotifier {
427 async fn notify_tools_changed(
428 &self,
429 session_id: &str,
430 ) -> std::result::Result<(), String> {
431 let notification = turul_mcp_protocol::JsonRpcNotification::new(
432 "notifications/tools/list_changed".to_string(),
433 );
434 let data = serde_json::to_value(¬ification).map_err(|e| e.to_string())?;
435 self.session_manager
436 .dispatch_custom_event(
437 session_id,
438 "notifications/tools/list_changed".to_string(),
439 data,
440 )
441 .await
442 }
443 }
444 handler.with_tool_notifier(Arc::new(LambdaToolNotifier {
445 session_manager: Arc::clone(&self.session_manager),
446 }))
447 };
448
449 #[cfg(feature = "dynamic-tools")]
450 let handler = if let Some(ref registry) = self.tool_registry {
451 handler.with_tool_registry(Arc::clone(registry))
452 } else {
453 handler
454 };
455
456 Ok(handler)
457 }
458
459 pub fn session_storage_info(&self) -> &str {
461 "Session storage configured"
462 }
463}