turul_mcp_aws_lambda/
builder.rs

1//! High-level builder API for Lambda MCP servers
2//!
3//! This module provides a fluent builder API similar to McpServer::builder()
4//! but specifically designed for AWS Lambda deployment.
5
6use std::collections::HashMap;
7use std::sync::Arc;
8
9use turul_http_mcp_server::{ServerConfig, StreamConfig};
10use turul_mcp_protocol::{Implementation, ServerCapabilities};
11use turul_mcp_server::handlers::{McpHandler, *};
12use turul_mcp_server::{
13    McpCompletion, McpElicitation, McpLogger, McpNotification, McpPrompt, McpResource, McpRoot,
14    McpSampling, McpTool,
15};
16use turul_mcp_session_storage::BoxedSessionStorage;
17
18use crate::error::Result;
19
20#[cfg(feature = "dynamodb")]
21use crate::error::LambdaError;
22use crate::server::LambdaMcpServer;
23
24#[cfg(feature = "cors")]
25use crate::cors::CorsConfig;
26
27/// High-level builder for Lambda MCP servers
28///
29/// This provides a clean, fluent API for building Lambda MCP servers
30/// similar to the framework's McpServer::builder() pattern.
31///
32/// ## Example
33///
34/// ```rust,no_run
35/// use std::sync::Arc;
36/// use turul_mcp_aws_lambda::LambdaMcpServerBuilder;
37/// use turul_mcp_session_storage::InMemorySessionStorage;
38/// use turul_mcp_derive::McpTool;
39/// use turul_mcp_server::{McpResult, SessionContext};
40///
41/// #[derive(McpTool, Clone, Default)]
42/// #[tool(name = "example", description = "Example tool")]
43/// struct ExampleTool {
44///     #[param(description = "Example parameter")]
45///     value: String,
46/// }
47///
48/// impl ExampleTool {
49///     async fn execute(&self, _session: Option<SessionContext>) -> McpResult<String> {
50///         Ok(format!("Got: {}", self.value))
51///     }
52/// }
53///
54/// #[tokio::main]
55/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
56///     let server = LambdaMcpServerBuilder::new()
57///         .name("my-lambda-server")
58///         .version("1.0.0")
59///         .tool(ExampleTool::default())
60///         .storage(Arc::new(InMemorySessionStorage::new()))
61///         .cors_allow_all_origins()
62///         .build()
63///         .await?;
64///
65///     // Use with Lambda runtime...
66///     Ok(())
67/// }
68/// ```
69pub struct LambdaMcpServerBuilder {
70    /// Server implementation info
71    name: String,
72    version: String,
73    title: Option<String>,
74
75    /// Server capabilities
76    capabilities: ServerCapabilities,
77
78    /// Tools registered with the server
79    tools: HashMap<String, Arc<dyn McpTool>>,
80
81    /// Resources registered with the server
82    resources: HashMap<String, Arc<dyn McpResource>>,
83
84    /// Prompts registered with the server
85    prompts: HashMap<String, Arc<dyn McpPrompt>>,
86
87    /// Elicitations registered with the server
88    elicitations: HashMap<String, Arc<dyn McpElicitation>>,
89
90    /// Sampling providers registered with the server
91    sampling: HashMap<String, Arc<dyn McpSampling>>,
92
93    /// Completion providers registered with the server
94    completions: HashMap<String, Arc<dyn McpCompletion>>,
95
96    /// Loggers registered with the server
97    loggers: HashMap<String, Arc<dyn McpLogger>>,
98
99    /// Root providers registered with the server
100    root_providers: HashMap<String, Arc<dyn McpRoot>>,
101
102    /// Notification providers registered with the server
103    notifications: HashMap<String, Arc<dyn McpNotification>>,
104
105    /// Handlers registered with the server
106    handlers: HashMap<String, Arc<dyn McpHandler>>,
107
108    /// Roots configured for the server
109    roots: Vec<turul_mcp_protocol::roots::Root>,
110
111    /// Optional instructions for clients
112    instructions: Option<String>,
113
114    /// Session configuration
115    session_timeout_minutes: Option<u64>,
116    session_cleanup_interval_seconds: Option<u64>,
117
118    /// Session storage backend (defaults to InMemory if None)
119    session_storage: Option<Arc<BoxedSessionStorage>>,
120
121    /// MCP Lifecycle enforcement configuration
122    strict_lifecycle: bool,
123
124    /// Enable SSE streaming
125    enable_sse: bool,
126    /// Server and stream configuration
127    server_config: ServerConfig,
128    stream_config: StreamConfig,
129
130    /// CORS configuration (if enabled)
131    #[cfg(feature = "cors")]
132    cors_config: Option<CorsConfig>,
133}
134
135impl LambdaMcpServerBuilder {
136    /// Create a new Lambda MCP server builder
137    pub fn new() -> Self {
138        // Initialize with default capabilities (same as McpServer)
139        // Capabilities will be set truthfully in build() based on registered components
140        let capabilities = ServerCapabilities::default();
141
142        // Initialize handlers with defaults (same as McpServerBuilder)
143        let mut handlers: HashMap<String, Arc<dyn McpHandler>> = HashMap::new();
144        handlers.insert("ping".to_string(), Arc::new(PingHandler));
145        handlers.insert(
146            "completion/complete".to_string(),
147            Arc::new(CompletionHandler),
148        );
149        handlers.insert(
150            "resources/list".to_string(),
151            Arc::new(ResourcesHandler::new()),
152        );
153        handlers.insert(
154            "prompts/list".to_string(),
155            Arc::new(PromptsListHandler::new()),
156        );
157        handlers.insert(
158            "prompts/get".to_string(),
159            Arc::new(PromptsGetHandler::new()),
160        );
161        handlers.insert("logging/setLevel".to_string(), Arc::new(LoggingHandler));
162        handlers.insert("roots/list".to_string(), Arc::new(RootsHandler::new()));
163        handlers.insert(
164            "sampling/createMessage".to_string(),
165            Arc::new(SamplingHandler),
166        );
167        handlers.insert(
168            "resources/templates/list".to_string(),
169            Arc::new(ResourceTemplatesHandler::new()),
170        );
171        handlers.insert(
172            "elicitation/create".to_string(),
173            Arc::new(ElicitationHandler::with_mock_provider()),
174        );
175
176        // Add notification handlers
177        let notifications_handler = Arc::new(NotificationsHandler);
178        handlers.insert(
179            "notifications/message".to_string(),
180            notifications_handler.clone(),
181        );
182        handlers.insert(
183            "notifications/progress".to_string(),
184            notifications_handler.clone(),
185        );
186        handlers.insert(
187            "notifications/resources/listChanged".to_string(),
188            notifications_handler.clone(),
189        );
190        handlers.insert(
191            "notifications/resources/updated".to_string(),
192            notifications_handler.clone(),
193        );
194        handlers.insert(
195            "notifications/tools/listChanged".to_string(),
196            notifications_handler.clone(),
197        );
198        handlers.insert(
199            "notifications/prompts/listChanged".to_string(),
200            notifications_handler.clone(),
201        );
202        handlers.insert(
203            "notifications/roots/listChanged".to_string(),
204            notifications_handler,
205        );
206
207        Self {
208            name: "turul-mcp-aws-lambda".to_string(),
209            version: env!("CARGO_PKG_VERSION").to_string(),
210            title: None,
211            capabilities,
212            tools: HashMap::new(),
213            resources: HashMap::new(),
214            prompts: HashMap::new(),
215            elicitations: HashMap::new(),
216            sampling: HashMap::new(),
217            completions: HashMap::new(),
218            loggers: HashMap::new(),
219            root_providers: HashMap::new(),
220            notifications: HashMap::new(),
221            handlers,
222            roots: Vec::new(),
223            instructions: None,
224            session_timeout_minutes: None,
225            session_cleanup_interval_seconds: None,
226            session_storage: None,
227            strict_lifecycle: false,
228            enable_sse: cfg!(feature = "sse"),
229            server_config: ServerConfig::default(),
230            stream_config: StreamConfig::default(),
231            #[cfg(feature = "cors")]
232            cors_config: None,
233        }
234    }
235
236    /// Set the server name
237    pub fn name(mut self, name: impl Into<String>) -> Self {
238        self.name = name.into();
239        self
240    }
241
242    /// Set the server version
243    pub fn version(mut self, version: impl Into<String>) -> Self {
244        self.version = version.into();
245        self
246    }
247
248    /// Set the server title
249    pub fn title(mut self, title: impl Into<String>) -> Self {
250        self.title = Some(title.into());
251        self
252    }
253
254    /// Set optional instructions for clients
255    pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
256        self.instructions = Some(instructions.into());
257        self
258    }
259
260    // =============================================================================
261    // PROVIDER REGISTRATION METHODS (same as McpServerBuilder)
262    // =============================================================================
263
264    /// Register a tool with the server
265    ///
266    /// Tools can be created using any of the framework's 4 creation levels:
267    /// - Function macros: `#[mcp_tool]`
268    /// - Derive macros: `#[derive(McpTool)]`
269    /// - Builder pattern: `ToolBuilder::new(...).build()`
270    /// - Manual implementation: Custom struct implementing `McpTool`
271    pub fn tool<T: McpTool + 'static>(mut self, tool: T) -> Self {
272        let name = tool.name().to_string();
273        self.tools.insert(name, Arc::new(tool));
274        self
275    }
276
277    /// Register a function tool created with `#[mcp_tool]` macro
278    pub fn tool_fn<F, T>(self, func: F) -> Self
279    where
280        F: Fn() -> T,
281        T: McpTool + 'static,
282    {
283        self.tool(func())
284    }
285
286    /// Register multiple tools
287    pub fn tools<T: McpTool + 'static, I: IntoIterator<Item = T>>(mut self, tools: I) -> Self {
288        for tool in tools {
289            self = self.tool(tool);
290        }
291        self
292    }
293
294    /// Register a resource with the server
295    pub fn resource<R: McpResource + 'static>(mut self, resource: R) -> Self {
296        let uri = resource.uri().to_string();
297        self.resources.insert(uri, Arc::new(resource));
298        self
299    }
300
301    /// Register multiple resources
302    pub fn resources<R: McpResource + 'static, I: IntoIterator<Item = R>>(
303        mut self,
304        resources: I,
305    ) -> Self {
306        for resource in resources {
307            self = self.resource(resource);
308        }
309        self
310    }
311
312    /// Register a prompt with the server
313    pub fn prompt<P: McpPrompt + 'static>(mut self, prompt: P) -> Self {
314        let name = prompt.name().to_string();
315        self.prompts.insert(name, Arc::new(prompt));
316        self
317    }
318
319    /// Register multiple prompts
320    pub fn prompts<P: McpPrompt + 'static, I: IntoIterator<Item = P>>(
321        mut self,
322        prompts: I,
323    ) -> Self {
324        for prompt in prompts {
325            self = self.prompt(prompt);
326        }
327        self
328    }
329
330    /// Register an elicitation provider with the server
331    pub fn elicitation<E: McpElicitation + 'static>(mut self, elicitation: E) -> Self {
332        let key = format!("elicitation_{}", self.elicitations.len());
333        self.elicitations.insert(key, Arc::new(elicitation));
334        self
335    }
336
337    /// Register multiple elicitation providers
338    pub fn elicitations<E: McpElicitation + 'static, I: IntoIterator<Item = E>>(
339        mut self,
340        elicitations: I,
341    ) -> Self {
342        for elicitation in elicitations {
343            self = self.elicitation(elicitation);
344        }
345        self
346    }
347
348    /// Register a sampling provider with the server
349    pub fn sampling_provider<S: McpSampling + 'static>(mut self, sampling: S) -> Self {
350        let key = format!("sampling_{}", self.sampling.len());
351        self.sampling.insert(key, Arc::new(sampling));
352        self
353    }
354
355    /// Register multiple sampling providers
356    pub fn sampling_providers<S: McpSampling + 'static, I: IntoIterator<Item = S>>(
357        mut self,
358        sampling: I,
359    ) -> Self {
360        for s in sampling {
361            self = self.sampling_provider(s);
362        }
363        self
364    }
365
366    /// Register a completion provider with the server
367    pub fn completion_provider<C: McpCompletion + 'static>(mut self, completion: C) -> Self {
368        let key = format!("completion_{}", self.completions.len());
369        self.completions.insert(key, Arc::new(completion));
370        self
371    }
372
373    /// Register multiple completion providers
374    pub fn completion_providers<C: McpCompletion + 'static, I: IntoIterator<Item = C>>(
375        mut self,
376        completions: I,
377    ) -> Self {
378        for completion in completions {
379            self = self.completion_provider(completion);
380        }
381        self
382    }
383
384    /// Register a logger with the server
385    pub fn logger<L: McpLogger + 'static>(mut self, logger: L) -> Self {
386        let key = format!("logger_{}", self.loggers.len());
387        self.loggers.insert(key, Arc::new(logger));
388        self
389    }
390
391    /// Register multiple loggers
392    pub fn loggers<L: McpLogger + 'static, I: IntoIterator<Item = L>>(
393        mut self,
394        loggers: I,
395    ) -> Self {
396        for logger in loggers {
397            self = self.logger(logger);
398        }
399        self
400    }
401
402    /// Register a root provider with the server
403    pub fn root_provider<R: McpRoot + 'static>(mut self, root: R) -> Self {
404        let key = format!("root_{}", self.root_providers.len());
405        self.root_providers.insert(key, Arc::new(root));
406        self
407    }
408
409    /// Register multiple root providers
410    pub fn root_providers<R: McpRoot + 'static, I: IntoIterator<Item = R>>(
411        mut self,
412        roots: I,
413    ) -> Self {
414        for root in roots {
415            self = self.root_provider(root);
416        }
417        self
418    }
419
420    /// Register a notification provider with the server
421    pub fn notification_provider<N: McpNotification + 'static>(mut self, notification: N) -> Self {
422        let key = format!("notification_{}", self.notifications.len());
423        self.notifications.insert(key, Arc::new(notification));
424        self
425    }
426
427    /// Register multiple notification providers
428    pub fn notification_providers<N: McpNotification + 'static, I: IntoIterator<Item = N>>(
429        mut self,
430        notifications: I,
431    ) -> Self {
432        for notification in notifications {
433            self = self.notification_provider(notification);
434        }
435        self
436    }
437
438    // =============================================================================
439    // ZERO-CONFIGURATION CONVENIENCE METHODS (same as McpServerBuilder)
440    // =============================================================================
441
442    /// Register a sampler - convenient alias for sampling_provider
443    pub fn sampler<S: McpSampling + 'static>(self, sampling: S) -> Self {
444        self.sampling_provider(sampling)
445    }
446
447    /// Register a completer - convenient alias for completion_provider
448    pub fn completer<C: McpCompletion + 'static>(self, completion: C) -> Self {
449        self.completion_provider(completion)
450    }
451
452    /// Register a notification by type - type determines method automatically
453    pub fn notification_type<N: McpNotification + 'static + Default>(self) -> Self {
454        let notification = N::default();
455        self.notification_provider(notification)
456    }
457
458    /// Register a handler with the server
459    pub fn handler<H: McpHandler + 'static>(mut self, handler: H) -> Self {
460        let handler_arc = Arc::new(handler);
461        for method in handler_arc.supported_methods() {
462            self.handlers.insert(method, handler_arc.clone());
463        }
464        self
465    }
466
467    /// Register multiple handlers
468    pub fn handlers<H: McpHandler + 'static, I: IntoIterator<Item = H>>(
469        mut self,
470        handlers: I,
471    ) -> Self {
472        for handler in handlers {
473            self = self.handler(handler);
474        }
475        self
476    }
477
478    /// Add a single root directory
479    pub fn root(mut self, root: turul_mcp_protocol::roots::Root) -> Self {
480        self.roots.push(root);
481        self
482    }
483
484    // =============================================================================
485    // CAPABILITY CONFIGURATION METHODS (same as McpServerBuilder)
486    // =============================================================================
487
488    /// Add completion support
489    pub fn with_completion(mut self) -> Self {
490        use turul_mcp_protocol::initialize::CompletionsCapabilities;
491        self.capabilities.completions = Some(CompletionsCapabilities {
492            enabled: Some(true),
493        });
494        self.handler(CompletionHandler)
495    }
496
497    /// Add prompts support
498    pub fn with_prompts(mut self) -> Self {
499        use turul_mcp_protocol::initialize::PromptsCapabilities;
500        self.capabilities.prompts = Some(PromptsCapabilities {
501            list_changed: Some(false),
502        });
503
504        // Prompts handlers are automatically registered when prompts are added via .prompt()
505        // This method now just enables the capability
506        self
507    }
508
509    /// Add resources support
510    pub fn with_resources(mut self) -> Self {
511        use turul_mcp_protocol::initialize::ResourcesCapabilities;
512        self.capabilities.resources = Some(ResourcesCapabilities {
513            subscribe: Some(false),
514            list_changed: Some(false),
515        });
516
517        // Create ResourcesHandler and add all registered resources
518        let mut handler = ResourcesHandler::new();
519        for resource in self.resources.values() {
520            handler = handler.add_resource_arc(resource.clone());
521        }
522
523        self.handler(handler)
524    }
525
526    /// Add logging support
527    pub fn with_logging(mut self) -> Self {
528        use turul_mcp_protocol::initialize::LoggingCapabilities;
529        self.capabilities.logging = Some(LoggingCapabilities::default());
530        self.handler(LoggingHandler)
531    }
532
533    /// Add roots support
534    pub fn with_roots(self) -> Self {
535        self.handler(RootsHandler::new())
536    }
537
538    /// Add sampling support
539    pub fn with_sampling(self) -> Self {
540        self.handler(SamplingHandler)
541    }
542
543    /// Add elicitation support with default mock provider
544    pub fn with_elicitation(mut self) -> Self {
545        use turul_mcp_protocol::initialize::ElicitationCapabilities;
546        self.capabilities.elicitation = Some(ElicitationCapabilities {
547            enabled: Some(true),
548        });
549        self.handler(ElicitationHandler::with_mock_provider())
550    }
551
552    /// Add elicitation support with custom provider
553    pub fn with_elicitation_provider<P: ElicitationProvider + 'static>(
554        mut self,
555        provider: P,
556    ) -> Self {
557        use turul_mcp_protocol::initialize::ElicitationCapabilities;
558        self.capabilities.elicitation = Some(ElicitationCapabilities {
559            enabled: Some(true),
560        });
561        self.handler(ElicitationHandler::new(Arc::new(provider)))
562    }
563
564    /// Add notifications support
565    pub fn with_notifications(self) -> Self {
566        self.handler(NotificationsHandler)
567    }
568
569    // =============================================================================
570    // SESSION AND CONFIGURATION METHODS
571    // =============================================================================
572
573    /// Configure session timeout (in minutes, default: 30)
574    pub fn session_timeout_minutes(mut self, minutes: u64) -> Self {
575        self.session_timeout_minutes = Some(minutes);
576        self
577    }
578
579    /// Configure session cleanup interval (in seconds, default: 60)
580    pub fn session_cleanup_interval_seconds(mut self, seconds: u64) -> Self {
581        self.session_cleanup_interval_seconds = Some(seconds);
582        self
583    }
584
585    /// Enable strict MCP lifecycle enforcement
586    pub fn strict_lifecycle(mut self, strict: bool) -> Self {
587        self.strict_lifecycle = strict;
588        self
589    }
590
591    /// Enable strict MCP lifecycle enforcement (convenience method)
592    pub fn with_strict_lifecycle(self) -> Self {
593        self.strict_lifecycle(true)
594    }
595
596    /// Enable or disable SSE streaming support
597    pub fn sse(mut self, enable: bool) -> Self {
598        self.enable_sse = enable;
599
600        // Update SSE endpoints in ServerConfig based on enable flag
601        if enable {
602            self.server_config.enable_get_sse = true;
603            self.server_config.enable_post_sse = true;
604        } else {
605            // When SSE is disabled, also disable SSE endpoints in ServerConfig
606            // This prevents GET /mcp from hanging by returning 405 instead
607            self.server_config.enable_get_sse = false;
608            self.server_config.enable_post_sse = false;
609        }
610
611        self
612    }
613
614    /// Configure sessions with recommended defaults for long-running sessions
615    pub fn with_long_sessions(mut self) -> Self {
616        self.session_timeout_minutes = Some(120); // 2 hours
617        self.session_cleanup_interval_seconds = Some(300); // 5 minutes
618        self
619    }
620
621    /// Configure sessions with recommended defaults for short-lived sessions
622    pub fn with_short_sessions(mut self) -> Self {
623        self.session_timeout_minutes = Some(5); // 5 minutes
624        self.session_cleanup_interval_seconds = Some(30); // 30 seconds
625        self
626    }
627
628    /// Set the session storage backend
629    ///
630    /// Supports all framework storage backends:
631    /// - `InMemorySessionStorage` - For development and testing
632    /// - `SqliteSessionStorage` - For single-instance persistence
633    /// - `PostgreSqlSessionStorage` - For multi-instance deployments
634    /// - `DynamoDbSessionStorage` - For serverless AWS deployments
635    pub fn storage(mut self, storage: Arc<BoxedSessionStorage>) -> Self {
636        self.session_storage = Some(storage);
637        self
638    }
639
640    /// Create DynamoDB storage from environment variables
641    ///
642    /// Uses these environment variables:
643    /// - `SESSION_TABLE_NAME` or `MCP_SESSION_TABLE` - DynamoDB table name
644    /// - `AWS_REGION` - AWS region
645    /// - AWS credentials from standard AWS credential chain
646    #[cfg(feature = "dynamodb")]
647    pub async fn dynamodb_storage(self) -> Result<Self> {
648        use turul_mcp_session_storage::DynamoDbSessionStorage;
649
650        let storage = DynamoDbSessionStorage::new().await.map_err(|e| {
651            LambdaError::Config(format!("Failed to create DynamoDB storage: {}", e))
652        })?;
653
654        Ok(self.storage(Arc::new(storage)))
655    }
656
657    /// Configure server settings
658    pub fn server_config(mut self, config: ServerConfig) -> Self {
659        self.server_config = config;
660        self
661    }
662
663    /// Configure streaming/SSE settings
664    pub fn stream_config(mut self, config: StreamConfig) -> Self {
665        self.stream_config = config;
666        self
667    }
668
669    // CORS Configuration Methods
670
671    /// Set custom CORS configuration
672    #[cfg(feature = "cors")]
673    pub fn cors(mut self, config: CorsConfig) -> Self {
674        self.cors_config = Some(config);
675        self
676    }
677
678    /// Allow all origins for CORS (development only)
679    #[cfg(feature = "cors")]
680    pub fn cors_allow_all_origins(mut self) -> Self {
681        self.cors_config = Some(CorsConfig::allow_all());
682        self
683    }
684
685    /// Set specific allowed origins for CORS
686    #[cfg(feature = "cors")]
687    pub fn cors_allow_origins(mut self, origins: Vec<String>) -> Self {
688        self.cors_config = Some(CorsConfig::for_origins(origins));
689        self
690    }
691
692    /// Configure CORS from environment variables
693    ///
694    /// Uses these environment variables:
695    /// - `MCP_CORS_ORIGINS` - Comma-separated list of allowed origins
696    /// - `MCP_CORS_CREDENTIALS` - Whether to allow credentials (true/false)
697    /// - `MCP_CORS_MAX_AGE` - Preflight cache max age in seconds
698    #[cfg(feature = "cors")]
699    pub fn cors_from_env(mut self) -> Self {
700        self.cors_config = Some(CorsConfig::from_env());
701        self
702    }
703
704    /// Disable CORS (headers will not be added)
705    #[cfg(feature = "cors")]
706    pub fn cors_disabled(self) -> Self {
707        // Don't set any CORS config - builder will not add headers
708        self
709    }
710
711    // Convenience Methods
712
713    /// Create with DynamoDB storage and environment-based CORS
714    ///
715    /// This is the recommended configuration for production Lambda deployments.
716    #[cfg(all(feature = "dynamodb", feature = "cors"))]
717    pub async fn production_config(self) -> Result<Self> {
718        Ok(self.dynamodb_storage().await?.cors_from_env())
719    }
720
721    /// Create with in-memory storage and permissive CORS
722    ///
723    /// This is the recommended configuration for development and testing.
724    #[cfg(feature = "cors")]
725    pub fn development_config(self) -> Self {
726        use turul_mcp_session_storage::InMemorySessionStorage;
727
728        self.storage(Arc::new(InMemorySessionStorage::new()))
729            .cors_allow_all_origins()
730    }
731
732    /// Build the Lambda MCP server
733    ///
734    /// Returns a server that can create handlers when needed.
735    pub async fn build(self) -> Result<LambdaMcpServer> {
736        use turul_mcp_session_storage::InMemorySessionStorage;
737
738        // Validate configuration (same as MCP server)
739        if self.name.is_empty() {
740            return Err(crate::error::LambdaError::Configuration(
741                "Server name cannot be empty".to_string(),
742            ));
743        }
744        if self.version.is_empty() {
745            return Err(crate::error::LambdaError::Configuration(
746                "Server version cannot be empty".to_string(),
747            ));
748        }
749
750        // Note: SSE behavior depends on which handler method is used:
751        // - handle(): Works with run(), but SSE responses may not stream properly
752        // - handle_streaming(): Works with run_with_streaming_response() for real SSE streaming
753
754        // Create session storage (use in-memory if none provided)
755        let session_storage = self
756            .session_storage
757            .unwrap_or_else(|| Arc::new(InMemorySessionStorage::new()));
758
759        // Create implementation info
760        let implementation = if let Some(title) = self.title {
761            Implementation::new(&self.name, &self.version).with_title(title)
762        } else {
763            Implementation::new(&self.name, &self.version)
764        };
765
766        // Auto-detect and configure server capabilities based on registered components (same as McpServer)
767        let mut capabilities = self.capabilities.clone();
768        let has_tools = !self.tools.is_empty();
769        let has_resources = !self.resources.is_empty();
770        let has_prompts = !self.prompts.is_empty();
771        let has_elicitations = !self.elicitations.is_empty();
772        let has_completions = !self.completions.is_empty();
773        let has_logging = !self.loggers.is_empty();
774        tracing::debug!("🔧 Has logging configured: {}", has_logging);
775
776        // Tools capabilities - truthful reporting (only set if tools are registered)
777        if has_tools {
778            capabilities.tools = Some(turul_mcp_protocol::initialize::ToolsCapabilities {
779                list_changed: Some(false), // Static framework: no dynamic change sources
780            });
781        }
782
783        // Resources capabilities - truthful reporting (only set if resources are registered)
784        if has_resources {
785            capabilities.resources = Some(turul_mcp_protocol::initialize::ResourcesCapabilities {
786                subscribe: Some(false),    // TODO: Implement resource subscriptions
787                list_changed: Some(false), // Static framework: no dynamic change sources
788            });
789        }
790
791        // Prompts capabilities - truthful reporting (only set if prompts are registered)
792        if has_prompts {
793            capabilities.prompts = Some(turul_mcp_protocol::initialize::PromptsCapabilities {
794                list_changed: Some(false), // Static framework: no dynamic change sources
795            });
796        }
797
798        // Elicitation capabilities - truthful reporting (only set if elicitations are registered)
799        if has_elicitations {
800            capabilities.elicitation =
801                Some(turul_mcp_protocol::initialize::ElicitationCapabilities {
802                    enabled: Some(true),
803                });
804        }
805
806        // Completion capabilities - truthful reporting (only set if completions are registered)
807        if has_completions {
808            capabilities.completions =
809                Some(turul_mcp_protocol::initialize::CompletionsCapabilities {
810                    enabled: Some(true),
811                });
812        }
813
814        // Logging capabilities - always enabled for debugging/monitoring (same as McpServer)
815        // Always enable logging for debugging/monitoring
816        capabilities.logging = Some(turul_mcp_protocol::initialize::LoggingCapabilities {
817            enabled: Some(true),
818            levels: Some(vec![
819                "debug".to_string(),
820                "info".to_string(),
821                "warning".to_string(),
822                "error".to_string(),
823            ]),
824        });
825
826        // Add RootsHandler if roots were configured (same pattern as MCP server)
827        let mut handlers = self.handlers;
828        if !self.roots.is_empty() {
829            let mut roots_handler = RootsHandler::new();
830            for root in &self.roots {
831                roots_handler = roots_handler.add_root(root.clone());
832            }
833            handlers.insert("roots/list".to_string(), Arc::new(roots_handler));
834        }
835
836        // Create the Lambda server (stores all configuration like MCP server does)
837        Ok(LambdaMcpServer::new(
838            implementation,
839            capabilities,
840            self.tools,
841            self.resources,
842            self.prompts,
843            self.elicitations,
844            self.sampling,
845            self.completions,
846            self.loggers,
847            self.root_providers,
848            self.notifications,
849            handlers,
850            self.roots,
851            self.instructions,
852            session_storage,
853            self.strict_lifecycle,
854            self.server_config,
855            self.enable_sse,
856            self.stream_config,
857            #[cfg(feature = "cors")]
858            self.cors_config,
859        ))
860    }
861}
862
863impl Default for LambdaMcpServerBuilder {
864    fn default() -> Self {
865        Self::new()
866    }
867}
868
869// Extension trait for cleaner chaining
870pub trait LambdaMcpServerBuilderExt {
871    /// Add multiple tools at once
872    fn tools<I, T>(self, tools: I) -> Self
873    where
874        I: IntoIterator<Item = T>,
875        T: McpTool + 'static;
876}
877
878impl LambdaMcpServerBuilderExt for LambdaMcpServerBuilder {
879    fn tools<I, T>(mut self, tools: I) -> Self
880    where
881        I: IntoIterator<Item = T>,
882        T: McpTool + 'static,
883    {
884        for tool in tools {
885            self = self.tool(tool);
886        }
887        self
888    }
889}
890
891/// Create a Lambda MCP server with minimal configuration
892///
893/// This is a convenience function for simple use cases where you just
894/// want to register some tools and get a working handler.
895pub async fn simple_lambda_server<I, T>(tools: I) -> Result<LambdaMcpServer>
896where
897    I: IntoIterator<Item = T>,
898    T: McpTool + 'static,
899{
900    let mut builder = LambdaMcpServerBuilder::new();
901
902    for tool in tools {
903        builder = builder.tool(tool);
904    }
905
906    #[cfg(feature = "cors")]
907    {
908        builder = builder.cors_allow_all_origins();
909    }
910
911    builder.sse(false).build().await
912}
913
914/// Create a Lambda MCP server configured for production
915///
916/// Uses DynamoDB for session storage and environment-based CORS configuration.
917#[cfg(all(feature = "dynamodb", feature = "cors"))]
918pub async fn production_lambda_server<I, T>(tools: I) -> Result<LambdaMcpServer>
919where
920    I: IntoIterator<Item = T>,
921    T: McpTool + 'static,
922{
923    let mut builder = LambdaMcpServerBuilder::new();
924
925    for tool in tools {
926        builder = builder.tool(tool);
927    }
928
929    builder.production_config().await?.build().await
930}
931
932#[cfg(test)]
933mod tests {
934    use super::*;
935    use turul_mcp_session_storage::InMemorySessionStorage;
936
937    // Mock tool for testing
938    #[derive(Clone, Default)]
939    struct TestTool;
940
941    impl turul_mcp_protocol::tools::HasBaseMetadata for TestTool {
942        fn name(&self) -> &str {
943            "test_tool"
944        }
945    }
946
947    impl turul_mcp_protocol::tools::HasDescription for TestTool {
948        fn description(&self) -> Option<&str> {
949            Some("Test tool")
950        }
951    }
952
953    impl turul_mcp_protocol::tools::HasInputSchema for TestTool {
954        fn input_schema(&self) -> &turul_mcp_protocol::ToolSchema {
955            use turul_mcp_protocol::ToolSchema;
956            static SCHEMA: std::sync::OnceLock<ToolSchema> = std::sync::OnceLock::new();
957            SCHEMA.get_or_init(ToolSchema::object)
958        }
959    }
960
961    impl turul_mcp_protocol::tools::HasOutputSchema for TestTool {
962        fn output_schema(&self) -> Option<&turul_mcp_protocol::ToolSchema> {
963            None
964        }
965    }
966
967    impl turul_mcp_protocol::tools::HasAnnotations for TestTool {
968        fn annotations(&self) -> Option<&turul_mcp_protocol::tools::ToolAnnotations> {
969            None
970        }
971    }
972
973    impl turul_mcp_protocol::tools::HasToolMeta for TestTool {
974        fn tool_meta(&self) -> Option<&std::collections::HashMap<String, serde_json::Value>> {
975            None
976        }
977    }
978
979    #[async_trait::async_trait]
980    impl McpTool for TestTool {
981        async fn call(
982            &self,
983            _args: serde_json::Value,
984            _session: Option<turul_mcp_server::SessionContext>,
985        ) -> turul_mcp_server::McpResult<turul_mcp_protocol::tools::CallToolResult> {
986            use turul_mcp_protocol::tools::{CallToolResult, ToolResult};
987            Ok(CallToolResult::success(vec![ToolResult::text(
988                "test result",
989            )]))
990        }
991    }
992
993    #[tokio::test]
994    async fn test_builder_basic() {
995        let server = LambdaMcpServerBuilder::new()
996            .name("test-server")
997            .version("1.0.0")
998            .tool(TestTool)
999            .storage(Arc::new(InMemorySessionStorage::new()))
1000            .sse(false) // Disable SSE for tests since streaming feature not enabled
1001            .build()
1002            .await
1003            .unwrap();
1004
1005        // Create handler from server and verify it has stream_manager
1006        let handler = server.handler().await.unwrap();
1007        // Verify handler has stream_manager (critical invariant)
1008        assert!(
1009            handler.get_stream_manager().as_ref() as *const _ as usize > 0,
1010            "Stream manager must be initialized"
1011        );
1012    }
1013
1014    #[tokio::test]
1015    async fn test_simple_lambda_server() {
1016        let tools = vec![TestTool];
1017        let server = simple_lambda_server(tools).await.unwrap();
1018
1019        // Create handler and verify it was created with default configuration
1020        let handler = server.handler().await.unwrap();
1021        // Verify handler has stream_manager
1022        // Verify handler has stream_manager (critical invariant)
1023        assert!(
1024            handler.get_stream_manager().as_ref() as *const _ as usize > 0,
1025            "Stream manager must be initialized"
1026        );
1027    }
1028
1029    #[tokio::test]
1030    async fn test_builder_extension_trait() {
1031        let tools = vec![TestTool, TestTool];
1032
1033        let server = LambdaMcpServerBuilder::new()
1034            .tools(tools)
1035            .storage(Arc::new(InMemorySessionStorage::new()))
1036            .sse(false) // Disable SSE for tests since streaming feature not enabled
1037            .build()
1038            .await
1039            .unwrap();
1040
1041        let handler = server.handler().await.unwrap();
1042        // Verify handler has stream_manager
1043        // Verify handler has stream_manager (critical invariant)
1044        assert!(
1045            handler.get_stream_manager().as_ref() as *const _ as usize > 0,
1046            "Stream manager must be initialized"
1047        );
1048    }
1049
1050    #[cfg(feature = "cors")]
1051    #[tokio::test]
1052    async fn test_cors_configuration() {
1053        let server = LambdaMcpServerBuilder::new()
1054            .cors_allow_all_origins()
1055            .storage(Arc::new(InMemorySessionStorage::new()))
1056            .sse(false) // Disable SSE for tests since streaming feature not enabled
1057            .build()
1058            .await
1059            .unwrap();
1060
1061        let handler = server.handler().await.unwrap();
1062        // Verify handler has stream_manager
1063        // Verify handler has stream_manager (critical invariant)
1064        assert!(
1065            handler.get_stream_manager().as_ref() as *const _ as usize > 0,
1066            "Stream manager must be initialized"
1067        );
1068    }
1069
1070    #[tokio::test]
1071    async fn test_sse_toggle_functionality() {
1072        // Test that SSE can be toggled on/off/on correctly
1073        let mut builder =
1074            LambdaMcpServerBuilder::new().storage(Arc::new(InMemorySessionStorage::new()));
1075
1076        // Initially enable SSE
1077        builder = builder.sse(true);
1078        assert!(builder.enable_sse, "SSE should be enabled");
1079        assert!(
1080            builder.server_config.enable_get_sse,
1081            "GET SSE endpoint should be enabled"
1082        );
1083        assert!(
1084            builder.server_config.enable_post_sse,
1085            "POST SSE endpoint should be enabled"
1086        );
1087
1088        // Disable SSE
1089        builder = builder.sse(false);
1090        assert!(!builder.enable_sse, "SSE should be disabled");
1091        assert!(
1092            !builder.server_config.enable_get_sse,
1093            "GET SSE endpoint should be disabled"
1094        );
1095        assert!(
1096            !builder.server_config.enable_post_sse,
1097            "POST SSE endpoint should be disabled"
1098        );
1099
1100        // Re-enable SSE (this was broken before the fix)
1101        builder = builder.sse(true);
1102        assert!(builder.enable_sse, "SSE should be re-enabled");
1103        assert!(
1104            builder.server_config.enable_get_sse,
1105            "GET SSE endpoint should be re-enabled"
1106        );
1107        assert!(
1108            builder.server_config.enable_post_sse,
1109            "POST SSE endpoint should be re-enabled"
1110        );
1111
1112        // Verify the server can be built with SSE enabled
1113        let server = builder.build().await.unwrap();
1114        let handler = server.handler().await.unwrap();
1115        assert!(
1116            handler.get_stream_manager().as_ref() as *const _ as usize > 0,
1117            "Stream manager must be initialized"
1118        );
1119    }
1120}