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, McpNotification, McpPrompt, McpResource, McpTool, handlers::McpHandler,
15 session::SessionManager,
16};
17#[cfg(feature = "protocol-2025-11-25")]
18use turul_mcp_server::{McpElicitation, McpLogger, McpSampling};
19use turul_mcp_session_storage::BoxedSessionStorage;
20
21use crate::error::Result;
22use crate::handler::LambdaMcpHandler;
23
24#[cfg(feature = "cors")]
25use crate::cors::CorsConfig;
26
27#[allow(dead_code)]
32pub struct LambdaMcpServer {
33 pub implementation: Implementation,
35 pub capabilities: ServerCapabilities,
37 tools: HashMap<String, Arc<dyn McpTool>>,
39 resources: HashMap<String, Arc<dyn McpResource>>,
41 prompts: HashMap<String, Arc<dyn McpPrompt>>,
43 #[cfg(feature = "protocol-2025-11-25")]
45 elicitations: HashMap<String, Arc<dyn McpElicitation>>,
46 #[cfg(feature = "protocol-2025-11-25")]
48 sampling: HashMap<String, Arc<dyn McpSampling>>,
49 completions: Vec<Arc<dyn McpCompletion>>,
51 #[cfg(feature = "protocol-2025-11-25")]
53 loggers: HashMap<String, Arc<dyn McpLogger>>,
54 notifications: HashMap<String, Arc<dyn McpNotification>>,
57 handlers: HashMap<String, Arc<dyn McpHandler>>,
59 #[allow(deprecated)]
62 roots: Vec<turul_mcp_protocol::roots::Root>,
63 instructions: Option<String>,
65 session_manager: Arc<SessionManager>,
67 session_storage: Arc<BoxedSessionStorage>,
69 strict_lifecycle: bool,
71 server_config: ServerConfig,
73 enable_sse: bool,
75 stream_config: StreamConfig,
77 #[cfg(feature = "cors")]
79 cors_config: Option<CorsConfig>,
80 middleware_stack: turul_http_mcp_server::middleware::MiddlewareStack,
82 route_registry: Arc<turul_http_mcp_server::RouteRegistry>,
84 #[cfg(feature = "protocol-2025-11-25")]
86 task_runtime: Option<Arc<turul_mcp_server::TaskRuntime>>,
87 tool_fingerprint: String,
89 #[cfg(feature = "dynamic-tools")]
91 tool_registry: Option<Arc<turul_mcp_server::ToolRegistry>>,
92 #[cfg(feature = "dynamic-tools")]
94 coordination_enabled: bool,
95}
96
97impl LambdaMcpServer {
98 #[allow(clippy::too_many_arguments)]
100 #[allow(deprecated)]
102 pub(crate) fn new(
103 implementation: Implementation,
104 capabilities: ServerCapabilities,
105 tools: HashMap<String, Arc<dyn McpTool>>,
106 resources: HashMap<String, Arc<dyn McpResource>>,
107 prompts: HashMap<String, Arc<dyn McpPrompt>>,
108 #[cfg(feature = "protocol-2025-11-25")] elicitations: HashMap<
109 String,
110 Arc<dyn McpElicitation>,
111 >,
112 #[cfg(feature = "protocol-2025-11-25")] sampling: HashMap<String, Arc<dyn McpSampling>>,
113 completions: Vec<Arc<dyn McpCompletion>>,
114 #[cfg(feature = "protocol-2025-11-25")] loggers: HashMap<String, Arc<dyn McpLogger>>,
115 notifications: HashMap<String, Arc<dyn McpNotification>>,
116 handlers: HashMap<String, Arc<dyn McpHandler>>,
117 roots: Vec<turul_mcp_protocol::roots::Root>,
118 instructions: Option<String>,
119 session_storage: Arc<BoxedSessionStorage>,
120 strict_lifecycle: bool,
121 server_config: ServerConfig,
122 enable_sse: bool,
123 stream_config: StreamConfig,
124 #[cfg(feature = "cors")] cors_config: Option<CorsConfig>,
125 middleware_stack: turul_http_mcp_server::middleware::MiddlewareStack,
126 route_registry: Arc<turul_http_mcp_server::RouteRegistry>,
127 #[cfg(feature = "protocol-2025-11-25")] task_runtime: Option<
128 Arc<turul_mcp_server::TaskRuntime>,
129 >,
130 tool_fingerprint: String,
131 #[cfg(feature = "dynamic-tools")] dynamic_tools: bool,
132 #[cfg(feature = "dynamic-tools")] server_state_storage: Option<
133 Arc<dyn turul_mcp_server_state_storage::ServerStateStorage>,
134 >,
135 ) -> Self {
136 let session_manager = Arc::new(SessionManager::with_storage_and_timeouts(
138 Arc::clone(&session_storage),
139 capabilities.clone(),
140 std::time::Duration::from_secs(30 * 60), std::time::Duration::from_secs(60), ));
143
144 #[cfg(feature = "dynamic-tools")]
147 let coordination_enabled = server_state_storage
148 .as_ref()
149 .map(|s| matches!(s.backend_name(), "PostgreSQL" | "DynamoDB"))
150 .unwrap_or(false);
151
152 #[cfg(feature = "dynamic-tools")]
154 let tool_registry = if dynamic_tools {
155 let storage = server_state_storage.unwrap_or_else(|| {
156 Arc::new(turul_mcp_server_state_storage::InMemoryServerStateStorage::new())
157 });
158 Some(Arc::new(turul_mcp_server::ToolRegistry::new(
159 tools.clone(),
160 session_manager.clone(),
161 storage,
162 )))
163 } else {
164 None
165 };
166
167 Self {
168 implementation,
169 capabilities,
170 tools,
171 resources,
172 prompts,
173 #[cfg(feature = "protocol-2025-11-25")]
174 elicitations,
175 #[cfg(feature = "protocol-2025-11-25")]
176 sampling,
177 completions,
178 #[cfg(feature = "protocol-2025-11-25")]
179 loggers,
180 notifications,
181 handlers,
182 roots,
183 instructions,
184 session_manager,
185 session_storage,
186 strict_lifecycle,
187 server_config,
188 enable_sse,
189 stream_config,
190 #[cfg(feature = "cors")]
191 cors_config,
192 middleware_stack,
193 route_registry,
194 #[cfg(feature = "protocol-2025-11-25")]
195 task_runtime,
196 tool_fingerprint,
197 #[cfg(feature = "dynamic-tools")]
198 tool_registry,
199 #[cfg(feature = "dynamic-tools")]
200 coordination_enabled,
201 }
202 }
203
204 pub fn capabilities(&self) -> &ServerCapabilities {
206 &self.capabilities
207 }
208
209 fn has_task_runtime(&self) -> bool {
211 #[cfg(feature = "protocol-2025-11-25")]
212 {
213 self.task_runtime.is_some()
214 }
215 #[cfg(not(feature = "protocol-2025-11-25"))]
216 {
217 false
218 }
219 }
220
221 pub async fn handler(&self) -> Result<LambdaMcpHandler> {
225 info!(
226 "Creating Lambda MCP handler: {} v{}",
227 self.implementation.name, self.implementation.version
228 );
229 info!("Session management: enabled with automatic cleanup");
230
231 if self.enable_sse {
232 info!("SSE notifications: enabled for Lambda responses");
233
234 #[cfg(not(feature = "streaming"))]
236 {
237 use tracing::warn;
238 warn!("⚠️ SSE is enabled but 'streaming' feature is not available!");
239 warn!(
240 " For real SSE streaming, use handle_streaming() with run_with_streaming_response"
241 );
242 warn!(
243 " Current handle() method will return SSE snapshots, not real-time streams"
244 );
245 warn!(" To enable streaming: add 'streaming' feature to turul-mcp-aws-lambda");
246 }
247 }
248
249 let _cleanup_task = self.session_manager.clone().start_cleanup_task();
251
252 #[cfg(feature = "dynamic-tools")]
254 if self.coordination_enabled
255 && let Some(ref registry) = self.tool_registry
256 {
257 use tracing::warn;
258 match registry.sync_from_storage().await {
259 Ok(_) => {
260 info!("Dynamic: synced tool registry with shared storage");
261 }
262 Err(e) => {
263 warn!(error = %e, "Dynamic: failed to sync with shared storage on cold start");
264 }
265 }
266 }
267
268 #[cfg(feature = "protocol-2025-11-25")]
271 if let Some(ref runtime) = self.task_runtime {
272 match runtime.recover_stuck_tasks().await {
273 Ok(recovered) if !recovered.is_empty() => {
274 info!(
275 count = recovered.len(),
276 "Recovered stuck tasks from previous invocations"
277 );
278 }
279 Err(e) => {
280 use tracing::warn;
281 warn!(error = %e, "Failed to recover stuck tasks on startup");
282 }
283 _ => {}
284 }
285 }
286
287 let stream_manager = Arc::new(StreamManager::with_config(
289 self.session_storage.clone(),
290 self.stream_config.clone(),
291 ));
292
293 {
297 use turul_mcp_server::SessionEventDispatcher;
298
299 struct LambdaEventDispatcher {
300 stream_manager: Arc<StreamManager>,
301 }
302
303 #[async_trait::async_trait]
304 impl SessionEventDispatcher for LambdaEventDispatcher {
305 async fn dispatch_to_session(
306 &self,
307 session_id: &str,
308 event_type: String,
309 data: serde_json::Value,
310 ) -> std::result::Result<(), String> {
311 self.stream_manager
312 .broadcast_to_session(session_id, event_type, data)
313 .await
314 .map(|_| ())
315 .map_err(|e| e.to_string())
316 }
317 }
318
319 let dispatcher = Arc::new(LambdaEventDispatcher {
320 stream_manager: Arc::clone(&stream_manager),
321 });
322 self.session_manager.set_event_dispatcher(dispatcher).await;
323 debug!("Lambda event dispatcher installed (guaranteed persistence for Custom events)");
324 }
325
326 {
329 let mut global_events = self.session_manager.subscribe_all_session_events();
330
331 tokio::spawn(async move {
332 debug!("Lambda SSE Event Bridge: started (observer-only for Custom events)");
333
334 while let Ok((session_id, event)) = global_events.recv().await {
335 match event {
336 turul_mcp_server::session::SessionEvent::Custom {
337 ref event_type, ..
338 } => {
339 debug!(
340 "Lambda SSE Bridge: observed custom event '{}' for session {} (dispatcher handles persistence)",
341 event_type, session_id
342 );
343 }
344 _ => {
345 debug!(
346 "Lambda SSE Bridge: non-custom event for session {}",
347 session_id
348 );
349 }
350 }
351 }
352
353 debug!("Lambda SSE Event Bridge: stopped");
354 });
355 }
356
357 use turul_rpc::JsonRpcDispatcher;
359 let mut dispatcher = JsonRpcDispatcher::new();
360
361 #[cfg(feature = "protocol-2025-11-25")]
364 {
365 use turul_mcp_server::SessionAwareInitializeHandler;
366 #[cfg_attr(not(feature = "dynamic-tools"), allow(unused_mut))]
367 let mut init_handler = SessionAwareInitializeHandler::new(
368 self.implementation.clone(),
369 self.capabilities.clone(),
370 self.instructions.clone(),
371 self.session_manager.clone(),
372 self.strict_lifecycle,
373 self.tool_fingerprint.clone(),
374 );
375 #[cfg(feature = "dynamic-tools")]
376 if let Some(ref registry) = self.tool_registry {
377 init_handler = init_handler.with_tool_registry(Arc::clone(registry));
378 }
379 dispatcher.register_method("initialize".to_string(), init_handler);
380 }
381
382 use turul_mcp_server::ListToolsHandler;
384 #[allow(unused_mut)]
385 let mut list_handler = ListToolsHandler::new_with_session_manager(
386 self.tools.clone(),
387 self.session_manager.clone(),
388 self.strict_lifecycle,
389 self.has_task_runtime(),
390 );
391 #[cfg(feature = "dynamic-tools")]
392 if let Some(ref registry) = self.tool_registry {
393 list_handler = list_handler.with_tool_registry(Arc::clone(registry));
394 }
395 dispatcher.register_method("tools/list".to_string(), list_handler);
396
397 use turul_mcp_server::SessionAwareToolHandler;
399 #[cfg_attr(
400 not(any(feature = "protocol-2025-11-25", feature = "dynamic-tools")),
401 allow(unused_mut)
402 )]
403 let mut tool_handler = SessionAwareToolHandler::new(
404 self.tools.clone(),
405 self.session_manager.clone(),
406 self.strict_lifecycle,
407 );
408 #[cfg(feature = "protocol-2025-11-25")]
409 if let Some(ref runtime) = self.task_runtime {
410 tool_handler = tool_handler.with_task_runtime(Arc::clone(runtime));
411 }
412 #[cfg(feature = "dynamic-tools")]
413 if let Some(ref registry) = self.tool_registry {
414 tool_handler = tool_handler.with_tool_registry(Arc::clone(registry));
415 }
416 dispatcher.register_method("tools/call".to_string(), tool_handler);
417
418 #[cfg(feature = "protocol-2026-07-28")]
419 {
420 use turul_mcp_protocol::SERVER_DISCOVER_METHOD;
421 use turul_mcp_server::DiscoverHandler;
422 dispatcher.register_method(
423 SERVER_DISCOVER_METHOD.to_string(),
424 DiscoverHandler::new(self.capabilities.clone(), self.instructions.clone()),
425 );
426 }
427
428 use turul_mcp_server::SessionAwareMcpHandlerBridge;
430 for (method, handler) in &self.handlers {
431 let bridge_handler = SessionAwareMcpHandlerBridge::new(
432 handler.clone(),
433 self.session_manager.clone(),
434 self.strict_lifecycle,
435 );
436 dispatcher.register_method(method.clone(), bridge_handler);
437 }
438
439 #[cfg(feature = "protocol-2025-11-25")]
442 {
443 use turul_mcp_server::handlers::InitializedNotificationHandler;
444 let initialized_handler =
445 InitializedNotificationHandler::new(self.session_manager.clone());
446 let initialized_bridge = SessionAwareMcpHandlerBridge::new(
447 Arc::new(initialized_handler),
448 self.session_manager.clone(),
449 self.strict_lifecycle,
450 );
451 dispatcher.register_method("notifications/initialized".to_string(), initialized_bridge);
452 }
453
454 let middleware_stack = Arc::new(self.middleware_stack.clone());
456
457 let handler = LambdaMcpHandler::with_middleware_and_fingerprint(
458 self.server_config.clone(),
459 Arc::new(dispatcher),
460 self.session_storage.clone(),
461 stream_manager,
462 self.stream_config.clone(),
463 self.capabilities.clone(),
464 middleware_stack,
465 self.enable_sse,
466 Arc::clone(&self.route_registry),
467 Some(self.tool_fingerprint.clone()),
468 )
469 .with_server_info(self.implementation.clone());
470
471 let handler = {
475 struct LambdaToolNotifier {
476 session_manager: Arc<turul_mcp_server::SessionManager>,
477 }
478 #[async_trait::async_trait]
479 impl turul_http_mcp_server::ToolChangeNotifier for LambdaToolNotifier {
480 async fn notify_tools_changed(
481 &self,
482 session_id: &str,
483 ) -> std::result::Result<(), String> {
484 let notification = turul_rpc::JsonRpcNotification::new_no_params(
485 "notifications/tools/list_changed".to_string(),
486 );
487 let data = serde_json::to_value(¬ification).map_err(|e| e.to_string())?;
488 self.session_manager
489 .dispatch_custom_event(
490 session_id,
491 "notifications/tools/list_changed".to_string(),
492 data,
493 )
494 .await
495 }
496 }
497 handler.with_tool_notifier(Arc::new(LambdaToolNotifier {
498 session_manager: Arc::clone(&self.session_manager),
499 }))
500 };
501
502 #[cfg(feature = "dynamic-tools")]
503 let handler = if let Some(ref registry) = self.tool_registry {
504 handler.with_tool_registry(Arc::clone(registry))
505 } else {
506 handler
507 };
508
509 #[cfg(feature = "cors")]
516 let handler = match self.cors_config.clone() {
517 Some(cors) => handler.with_cors(cors),
518 None => handler,
519 };
520
521 Ok(handler)
522 }
523
524 pub fn session_storage_info(&self) -> &str {
526 "Session storage configured"
527 }
528}