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")]
120 server_state_storage: Option<Arc<dyn turul_mcp_server_state_storage::ServerStateStorage>>,
121 ) -> Self {
122 let session_manager = Arc::new(SessionManager::with_storage_and_timeouts(
124 Arc::clone(&session_storage),
125 capabilities.clone(),
126 std::time::Duration::from_secs(30 * 60), std::time::Duration::from_secs(60), ));
129
130 #[cfg(feature = "dynamic-tools")]
133 let coordination_enabled = server_state_storage
134 .as_ref()
135 .map(|s| matches!(s.backend_name(), "PostgreSQL" | "DynamoDB"))
136 .unwrap_or(false);
137
138 #[cfg(feature = "dynamic-tools")]
140 let tool_registry = if dynamic_tools {
141 let storage = server_state_storage.unwrap_or_else(|| {
142 Arc::new(turul_mcp_server_state_storage::InMemoryServerStateStorage::new())
143 });
144 Some(Arc::new(turul_mcp_server::ToolRegistry::new(
145 tools.clone(),
146 session_manager.clone(),
147 storage,
148 )))
149 } else {
150 None
151 };
152
153 Self {
154 implementation,
155 capabilities,
156 tools,
157 resources,
158 prompts,
159 elicitations,
160 sampling,
161 completions,
162 loggers,
163 root_providers,
164 notifications,
165 handlers,
166 roots,
167 instructions,
168 session_manager,
169 session_storage,
170 strict_lifecycle,
171 server_config,
172 enable_sse,
173 stream_config,
174 #[cfg(feature = "cors")]
175 cors_config,
176 middleware_stack,
177 route_registry,
178 task_runtime,
179 tool_fingerprint,
180 #[cfg(feature = "dynamic-tools")]
181 tool_registry,
182 #[cfg(feature = "dynamic-tools")]
183 coordination_enabled,
184 }
185 }
186
187 pub fn capabilities(&self) -> &ServerCapabilities {
189 &self.capabilities
190 }
191
192 pub async fn handler(&self) -> Result<LambdaMcpHandler> {
196 info!(
197 "Creating Lambda MCP handler: {} v{}",
198 self.implementation.name, self.implementation.version
199 );
200 info!("Session management: enabled with automatic cleanup");
201
202 if self.enable_sse {
203 info!("SSE notifications: enabled for Lambda responses");
204
205 #[cfg(not(feature = "streaming"))]
207 {
208 use tracing::warn;
209 warn!("⚠️ SSE is enabled but 'streaming' feature is not available!");
210 warn!(
211 " For real SSE streaming, use handle_streaming() with run_with_streaming_response"
212 );
213 warn!(
214 " Current handle() method will return SSE snapshots, not real-time streams"
215 );
216 warn!(" To enable streaming: add 'streaming' feature to turul-mcp-aws-lambda");
217 }
218 }
219
220 let _cleanup_task = self.session_manager.clone().start_cleanup_task();
222
223 #[cfg(feature = "dynamic-tools")]
225 if self.coordination_enabled {
226 if let Some(ref registry) = self.tool_registry {
227 use tracing::warn;
228 match registry.sync_from_storage().await {
229 Ok(_) => {
230 info!("Dynamic: synced tool registry with shared storage");
231 }
232 Err(e) => {
233 warn!(error = %e, "Dynamic: failed to sync with shared storage on cold start");
234 }
235 }
236 }
237 }
238
239 if let Some(ref runtime) = self.task_runtime {
242 match runtime.recover_stuck_tasks().await {
243 Ok(recovered) if !recovered.is_empty() => {
244 info!(
245 count = recovered.len(),
246 "Recovered stuck tasks from previous invocations"
247 );
248 }
249 Err(e) => {
250 use tracing::warn;
251 warn!(error = %e, "Failed to recover stuck tasks on startup");
252 }
253 _ => {}
254 }
255 }
256
257 let stream_manager = Arc::new(StreamManager::with_config(
259 self.session_storage.clone(),
260 self.stream_config.clone(),
261 ));
262
263 {
267 use turul_mcp_server::SessionEventDispatcher;
268
269 struct LambdaEventDispatcher {
270 stream_manager: Arc<StreamManager>,
271 }
272
273 #[async_trait::async_trait]
274 impl SessionEventDispatcher for LambdaEventDispatcher {
275 async fn dispatch_to_session(
276 &self,
277 session_id: &str,
278 event_type: String,
279 data: serde_json::Value,
280 ) -> std::result::Result<(), String> {
281 self.stream_manager
282 .broadcast_to_session(session_id, event_type, data)
283 .await
284 .map(|_| ())
285 .map_err(|e| e.to_string())
286 }
287 }
288
289 let dispatcher = Arc::new(LambdaEventDispatcher {
290 stream_manager: Arc::clone(&stream_manager),
291 });
292 self.session_manager.set_event_dispatcher(dispatcher).await;
293 debug!("Lambda event dispatcher installed (guaranteed persistence for Custom events)");
294 }
295
296 {
299 let mut global_events = self.session_manager.subscribe_all_session_events();
300
301 tokio::spawn(async move {
302 debug!("Lambda SSE Event Bridge: started (observer-only for Custom events)");
303
304 while let Ok((session_id, event)) = global_events.recv().await {
305 match event {
306 turul_mcp_server::session::SessionEvent::Custom {
307 ref event_type, ..
308 } => {
309 debug!(
310 "Lambda SSE Bridge: observed custom event '{}' for session {} (dispatcher handles persistence)",
311 event_type, session_id
312 );
313 }
314 _ => {
315 debug!("Lambda SSE Bridge: non-custom event for session {}", session_id);
316 }
317 }
318 }
319
320 debug!("Lambda SSE Event Bridge: stopped");
321 });
322 }
323
324 use turul_mcp_json_rpc_server::JsonRpcDispatcher;
326 let mut dispatcher = JsonRpcDispatcher::new();
327
328 use turul_mcp_server::SessionAwareInitializeHandler;
330 #[allow(unused_mut)]
331 let mut init_handler = SessionAwareInitializeHandler::new(
332 self.implementation.clone(),
333 self.capabilities.clone(),
334 self.instructions.clone(),
335 self.session_manager.clone(),
336 self.strict_lifecycle,
337 self.tool_fingerprint.clone(),
338 );
339 #[cfg(feature = "dynamic-tools")]
340 if let Some(ref registry) = self.tool_registry {
341 init_handler = init_handler.with_tool_registry(Arc::clone(registry));
342 }
343 dispatcher.register_method("initialize".to_string(), init_handler);
344
345 use turul_mcp_server::ListToolsHandler;
347 #[allow(unused_mut)]
348 let mut list_handler = ListToolsHandler::new_with_session_manager(
349 self.tools.clone(),
350 self.session_manager.clone(),
351 self.strict_lifecycle,
352 self.task_runtime.is_some(),
353 );
354 #[cfg(feature = "dynamic-tools")]
355 if let Some(ref registry) = self.tool_registry {
356 list_handler = list_handler.with_tool_registry(Arc::clone(registry));
357 }
358 dispatcher.register_method("tools/list".to_string(), list_handler);
359
360 use turul_mcp_server::SessionAwareToolHandler;
362 let mut tool_handler = SessionAwareToolHandler::new(
363 self.tools.clone(),
364 self.session_manager.clone(),
365 self.strict_lifecycle,
366 );
367 if let Some(ref runtime) = self.task_runtime {
368 tool_handler = tool_handler.with_task_runtime(Arc::clone(runtime));
369 }
370 #[cfg(feature = "dynamic-tools")]
371 if let Some(ref registry) = self.tool_registry {
372 tool_handler = tool_handler.with_tool_registry(Arc::clone(registry));
373 }
374 dispatcher.register_method("tools/call".to_string(), tool_handler);
375
376 use turul_mcp_server::SessionAwareMcpHandlerBridge;
378 for (method, handler) in &self.handlers {
379 let bridge_handler = SessionAwareMcpHandlerBridge::new(
380 handler.clone(),
381 self.session_manager.clone(),
382 self.strict_lifecycle,
383 );
384 dispatcher.register_method(method.clone(), bridge_handler);
385 }
386
387 use turul_mcp_server::handlers::InitializedNotificationHandler;
390 let initialized_handler = InitializedNotificationHandler::new(self.session_manager.clone());
391 let initialized_bridge = SessionAwareMcpHandlerBridge::new(
392 Arc::new(initialized_handler),
393 self.session_manager.clone(),
394 self.strict_lifecycle,
395 );
396 dispatcher.register_method("notifications/initialized".to_string(), initialized_bridge);
397
398 let middleware_stack = Arc::new(self.middleware_stack.clone());
400
401 let handler = LambdaMcpHandler::with_middleware_and_fingerprint(
402 self.server_config.clone(),
403 Arc::new(dispatcher),
404 self.session_storage.clone(),
405 stream_manager,
406 self.stream_config.clone(),
407 self.capabilities.clone(),
408 middleware_stack,
409 self.enable_sse,
410 Arc::clone(&self.route_registry),
411 Some(self.tool_fingerprint.clone()),
412 );
413
414 #[cfg(feature = "dynamic-tools")]
415 let handler = if let Some(ref registry) = self.tool_registry {
416 handler.with_tool_registry(Arc::clone(registry))
417 } else {
418 handler
419 };
420
421 Ok(handler)
422 }
423
424 pub fn session_storage_info(&self) -> &str {
426 "Session storage configured"
427 }
428}