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 {
268 let bridge_stream_manager = Arc::clone(&stream_manager);
269 let mut global_events = self.session_manager.subscribe_all_session_events();
270
271 tokio::spawn(async move {
272 debug!("Lambda SSE Event Bridge: started");
273
274 while let Ok((session_id, event)) = global_events.recv().await {
275 if let turul_mcp_server::session::SessionEvent::Custom {
276 event_type,
277 data,
278 } = event
279 {
280 if let Err(e) = bridge_stream_manager
281 .broadcast_to_session(&session_id, event_type, data)
282 .await
283 {
284 debug!(
285 "Lambda SSE Bridge: broadcast to session {} failed: {} (normal if no active connections)",
286 session_id, e
287 );
288 }
289 }
290 }
291
292 debug!("Lambda SSE Event Bridge: stopped");
293 });
294 }
295
296 use turul_mcp_json_rpc_server::JsonRpcDispatcher;
298 let mut dispatcher = JsonRpcDispatcher::new();
299
300 use turul_mcp_server::SessionAwareInitializeHandler;
302 let init_handler = SessionAwareInitializeHandler::new(
303 self.implementation.clone(),
304 self.capabilities.clone(),
305 self.instructions.clone(),
306 self.session_manager.clone(),
307 self.strict_lifecycle,
308 self.tool_fingerprint.clone(),
309 );
310 dispatcher.register_method("initialize".to_string(), init_handler);
311
312 use turul_mcp_server::ListToolsHandler;
314 let mut list_handler = ListToolsHandler::new_with_session_manager(
315 self.tools.clone(),
316 self.session_manager.clone(),
317 self.strict_lifecycle,
318 self.task_runtime.is_some(),
319 );
320 #[cfg(feature = "dynamic-tools")]
321 if let Some(ref registry) = self.tool_registry {
322 list_handler = list_handler.with_tool_registry(Arc::clone(registry));
323 }
324 dispatcher.register_method("tools/list".to_string(), list_handler);
325
326 use turul_mcp_server::SessionAwareToolHandler;
328 let mut tool_handler = SessionAwareToolHandler::new(
329 self.tools.clone(),
330 self.session_manager.clone(),
331 self.strict_lifecycle,
332 );
333 if let Some(ref runtime) = self.task_runtime {
334 tool_handler = tool_handler.with_task_runtime(Arc::clone(runtime));
335 }
336 #[cfg(feature = "dynamic-tools")]
337 if let Some(ref registry) = self.tool_registry {
338 tool_handler = tool_handler.with_tool_registry(Arc::clone(registry));
339 }
340 dispatcher.register_method("tools/call".to_string(), tool_handler);
341
342 use turul_mcp_server::SessionAwareMcpHandlerBridge;
344 for (method, handler) in &self.handlers {
345 let bridge_handler = SessionAwareMcpHandlerBridge::new(
346 handler.clone(),
347 self.session_manager.clone(),
348 self.strict_lifecycle,
349 );
350 dispatcher.register_method(method.clone(), bridge_handler);
351 }
352
353 use turul_mcp_server::handlers::InitializedNotificationHandler;
356 let initialized_handler = InitializedNotificationHandler::new(self.session_manager.clone());
357 let initialized_bridge = SessionAwareMcpHandlerBridge::new(
358 Arc::new(initialized_handler),
359 self.session_manager.clone(),
360 self.strict_lifecycle,
361 );
362 dispatcher.register_method("notifications/initialized".to_string(), initialized_bridge);
363
364 let middleware_stack = Arc::new(self.middleware_stack.clone());
366
367 let handler = LambdaMcpHandler::with_middleware_and_fingerprint(
368 self.server_config.clone(),
369 Arc::new(dispatcher),
370 self.session_storage.clone(),
371 stream_manager,
372 self.stream_config.clone(),
373 self.capabilities.clone(),
374 middleware_stack,
375 self.enable_sse,
376 Arc::clone(&self.route_registry),
377 Some(self.tool_fingerprint.clone()),
378 );
379
380 #[cfg(feature = "dynamic-tools")]
381 let handler = if let Some(ref registry) = self.tool_registry {
382 handler.with_tool_registry(Arc::clone(registry))
383 } else {
384 handler
385 };
386
387 Ok(handler)
388 }
389
390 pub fn session_storage_info(&self) -> &str {
392 "Session storage configured"
393 }
394}