mcp_execution_core/
server_config.rs

1//! MCP server configuration with command, arguments, and environment.
2//!
3//! This module provides type-safe server configuration for launching MCP servers
4//! with security validation of commands, arguments, and environment variables.
5//!
6//! # Transport Types
7//!
8//! Supports two transport types:
9//! - Stdio: Subprocess communication via stdin/stdout (default)
10//! - HTTP: Communication via HTTP/HTTPS API
11//!
12//! # Security
13//!
14//! The configuration enforces:
15//! - Command validation (absolute path or binary name)
16//! - Argument sanitization (no shell metacharacters)
17//! - Environment variable validation (block dangerous names)
18//! - Forbidden characters: `;`, `|`, `&`, `>`, `<`, `` ` ``, `$`, `(`, `)`, `\n`, `\r`
19//! - Forbidden env vars: `LD_PRELOAD`, `LD_LIBRARY_PATH`, `DYLD_*`, `PATH`
20//!
21//! # Examples
22//!
23//! ```
24//! use mcp_execution_core::ServerConfig;
25//! use std::collections::HashMap;
26//!
27//! // Simple configuration with just command
28//! let config = ServerConfig::builder()
29//!     .command("docker".to_string())
30//!     .build();
31//!
32//! // Full configuration with args and env
33//! let config = ServerConfig::builder()
34//!     .command("/usr/local/bin/mcp-server".to_string())
35//!     .arg("--port".to_string())
36//!     .arg("8080".to_string())
37//!     .env("LOG_LEVEL".to_string(), "debug".to_string())
38//!     .build();
39//!
40//! // HTTP transport configuration
41//! let config = ServerConfig::builder()
42//!     .http_transport("https://api.example.com/mcp".to_string())
43//!     .header("Authorization".to_string(), "Bearer token".to_string())
44//!     .build();
45//! ```
46
47use serde::{Deserialize, Serialize};
48use std::collections::HashMap;
49use std::path::PathBuf;
50
51/// Transport type for MCP server communication.
52///
53/// Defines how the client communicates with the MCP server.
54///
55/// # Examples
56///
57/// ```
58/// use mcp_execution_core::TransportType;
59///
60/// // Default is stdio
61/// let transport = TransportType::default();
62/// assert_eq!(transport, TransportType::Stdio);
63/// ```
64#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
65#[serde(rename_all = "lowercase")]
66pub enum TransportType {
67    /// Stdio transport: subprocess communication via stdin/stdout.
68    #[default]
69    Stdio,
70    /// HTTP transport: communication via HTTP/HTTPS API.
71    Http,
72    /// SSE transport: Server-Sent Events for streaming communication.
73    Sse,
74}
75
76/// MCP server configuration with command, arguments, and environment.
77///
78/// Represents the configuration needed to communicate with an MCP server,
79/// supporting both stdio (subprocess) and HTTP transports.
80///
81/// # Transport Types
82///
83/// - **Stdio**: Launches a subprocess and communicates via stdin/stdout
84/// - **HTTP**: Connects to an HTTP/HTTPS API endpoint
85///
86/// # Security
87///
88/// This type is designed to be safe by construction. Use the builder pattern
89/// to construct instances, and call [`validate_server_config`] before execution
90/// to ensure security requirements are met.
91///
92/// # Examples
93///
94/// ```
95/// use mcp_execution_core::ServerConfig;
96///
97/// // Stdio transport
98/// let config = ServerConfig::builder()
99///     .command("docker".to_string())
100///     .arg("run".to_string())
101///     .arg("mcp-server".to_string())
102///     .build();
103///
104/// assert_eq!(config.command, "docker");
105/// assert_eq!(config.args.len(), 2);
106///
107/// // HTTP transport
108/// let config = ServerConfig::builder()
109///     .http_transport("https://api.example.com/mcp".to_string())
110///     .header("Authorization".to_string(), "Bearer token".to_string())
111///     .build();
112/// ```
113///
114/// [`validate_server_config`]: fn.validate_server_config.html
115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
116pub struct ServerConfig {
117    /// Transport type (stdio or http).
118    ///
119    /// Determines how the client communicates with the MCP server.
120    #[serde(default)]
121    pub transport: TransportType,
122
123    /// Command to execute (binary name or absolute path).
124    ///
125    /// **Only used for stdio transport.**
126    ///
127    /// Can be either:
128    /// - Binary name (e.g., "docker", "python") - resolved via PATH
129    /// - Absolute path (e.g., "/usr/local/bin/mcp-server")
130    #[serde(default)]
131    pub command: String,
132
133    /// Arguments to pass to command.
134    ///
135    /// **Only used for stdio transport.**
136    ///
137    /// Each argument is passed separately to avoid shell interpretation.
138    /// Do not include the command itself in arguments.
139    #[serde(default)]
140    pub args: Vec<String>,
141
142    /// Environment variables to set for the subprocess.
143    ///
144    /// **Only used for stdio transport.**
145    ///
146    /// These are added to (or override) the parent process environment.
147    /// Security validation blocks dangerous variables like `LD_PRELOAD`.
148    #[serde(default)]
149    pub env: HashMap<String, String>,
150
151    /// Working directory for the subprocess (optional).
152    ///
153    /// **Only used for stdio transport.**
154    ///
155    /// If None, inherits the parent process working directory.
156    #[serde(default)]
157    pub cwd: Option<PathBuf>,
158
159    /// URL for HTTP transport.
160    ///
161    /// **Only used for HTTP transport.**
162    ///
163    /// Example: `https://api.example.com/mcp`
164    #[serde(default)]
165    pub url: Option<String>,
166
167    /// HTTP headers for HTTP transport.
168    ///
169    /// **Only used for HTTP transport.**
170    ///
171    /// Common headers include:
172    /// - `Authorization`: Authentication token
173    /// - `Content-Type`: Request content type
174    #[serde(default)]
175    pub headers: HashMap<String, String>,
176}
177
178impl ServerConfig {
179    /// Creates a new builder for `ServerConfig`.
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// use mcp_execution_core::ServerConfig;
185    ///
186    /// let config = ServerConfig::builder()
187    ///     .command("docker".to_string())
188    ///     .build();
189    /// ```
190    #[must_use]
191    pub fn builder() -> ServerConfigBuilder {
192        ServerConfigBuilder::default()
193    }
194
195    /// Returns the transport type.
196    #[must_use]
197    pub const fn transport(&self) -> &TransportType {
198        &self.transport
199    }
200
201    /// Returns the command as a string slice.
202    #[must_use]
203    pub fn command(&self) -> &str {
204        &self.command
205    }
206
207    /// Returns a slice of arguments.
208    #[must_use]
209    pub fn args(&self) -> &[String] {
210        &self.args
211    }
212
213    /// Returns a reference to the environment variables map.
214    #[must_use]
215    pub const fn env(&self) -> &HashMap<String, String> {
216        &self.env
217    }
218
219    /// Returns the working directory, if set.
220    #[must_use]
221    pub const fn cwd(&self) -> Option<&PathBuf> {
222        self.cwd.as_ref()
223    }
224
225    /// Returns the URL for HTTP transport, if set.
226    #[must_use]
227    pub fn url(&self) -> Option<&str> {
228        self.url.as_deref()
229    }
230
231    /// Returns a reference to the HTTP headers map.
232    #[must_use]
233    pub const fn headers(&self) -> &HashMap<String, String> {
234        &self.headers
235    }
236}
237
238/// Builder for constructing `ServerConfig` instances.
239///
240/// Provides a fluent API for building server configurations with
241/// optional arguments, environment variables, and HTTP settings.
242///
243/// # Examples
244///
245/// ```
246/// use mcp_execution_core::ServerConfig;
247///
248/// // Stdio transport
249/// let config = ServerConfig::builder()
250///     .command("mcp-server".to_string())
251///     .arg("--verbose".to_string())
252///     .env("DEBUG".to_string(), "1".to_string())
253///     .build();
254///
255/// // HTTP transport
256/// let config = ServerConfig::builder()
257///     .http_transport("https://api.example.com/mcp".to_string())
258///     .header("Authorization".to_string(), "Bearer token".to_string())
259///     .build();
260/// ```
261#[derive(Debug, Default, Clone)]
262pub struct ServerConfigBuilder {
263    transport: TransportType,
264    command: Option<String>,
265    args: Vec<String>,
266    env: HashMap<String, String>,
267    cwd: Option<PathBuf>,
268    url: Option<String>,
269    headers: HashMap<String, String>,
270}
271
272impl ServerConfigBuilder {
273    /// Sets the command to execute.
274    ///
275    /// # Examples
276    ///
277    /// ```
278    /// use mcp_execution_core::ServerConfig;
279    ///
280    /// let config = ServerConfig::builder()
281    ///     .command("docker".to_string())
282    ///     .build();
283    /// ```
284    #[must_use]
285    pub fn command(mut self, command: String) -> Self {
286        self.command = Some(command);
287        self
288    }
289
290    /// Adds a single argument.
291    ///
292    /// # Examples
293    ///
294    /// ```
295    /// use mcp_execution_core::ServerConfig;
296    ///
297    /// let config = ServerConfig::builder()
298    ///     .command("docker".to_string())
299    ///     .arg("run".to_string())
300    ///     .arg("--rm".to_string())
301    ///     .build();
302    /// ```
303    #[must_use]
304    pub fn arg(mut self, arg: String) -> Self {
305        self.args.push(arg);
306        self
307    }
308
309    /// Sets all arguments at once, replacing any previously added.
310    ///
311    /// # Examples
312    ///
313    /// ```
314    /// use mcp_execution_core::ServerConfig;
315    ///
316    /// let config = ServerConfig::builder()
317    ///     .command("docker".to_string())
318    ///     .args(vec!["run".to_string(), "--rm".to_string()])
319    ///     .build();
320    /// ```
321    #[must_use]
322    pub fn args(mut self, args: Vec<String>) -> Self {
323        self.args = args;
324        self
325    }
326
327    /// Adds a single environment variable.
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// use mcp_execution_core::ServerConfig;
333    ///
334    /// let config = ServerConfig::builder()
335    ///     .command("mcp-server".to_string())
336    ///     .env("LOG_LEVEL".to_string(), "debug".to_string())
337    ///     .build();
338    /// ```
339    #[must_use]
340    pub fn env(mut self, key: String, value: String) -> Self {
341        self.env.insert(key, value);
342        self
343    }
344
345    /// Sets all environment variables at once, replacing any previously added.
346    ///
347    /// # Examples
348    ///
349    /// ```
350    /// use mcp_execution_core::ServerConfig;
351    /// use std::collections::HashMap;
352    ///
353    /// let mut env_map = HashMap::new();
354    /// env_map.insert("DEBUG".to_string(), "1".to_string());
355    ///
356    /// let config = ServerConfig::builder()
357    ///     .command("mcp-server".to_string())
358    ///     .environment(env_map)
359    ///     .build();
360    /// ```
361    #[must_use]
362    pub fn environment(mut self, env: HashMap<String, String>) -> Self {
363        self.env = env;
364        self
365    }
366
367    /// Sets the working directory for the subprocess.
368    ///
369    /// # Examples
370    ///
371    /// ```
372    /// use mcp_execution_core::ServerConfig;
373    /// use std::path::PathBuf;
374    ///
375    /// let config = ServerConfig::builder()
376    ///     .command("mcp-server".to_string())
377    ///     .cwd(PathBuf::from("/tmp"))
378    ///     .build();
379    /// ```
380    #[must_use]
381    pub fn cwd(mut self, cwd: PathBuf) -> Self {
382        self.cwd = Some(cwd);
383        self
384    }
385
386    /// Configures HTTP transport with the given URL.
387    ///
388    /// This sets the transport type to HTTP and configures the endpoint URL.
389    ///
390    /// # Examples
391    ///
392    /// ```
393    /// use mcp_execution_core::ServerConfig;
394    ///
395    /// let config = ServerConfig::builder()
396    ///     .http_transport("https://api.example.com/mcp".to_string())
397    ///     .build();
398    /// ```
399    #[must_use]
400    pub fn http_transport(mut self, url: String) -> Self {
401        self.transport = TransportType::Http;
402        self.url = Some(url);
403        // Set a dummy command for HTTP transport so build() doesn't panic
404        if self.command.is_none() {
405            self.command = Some(String::new());
406        }
407        self
408    }
409
410    /// Configures SSE transport with the given URL.
411    ///
412    /// This sets the transport type to SSE (Server-Sent Events) and configures the endpoint URL.
413    ///
414    /// # Examples
415    ///
416    /// ```
417    /// use mcp_execution_core::ServerConfig;
418    ///
419    /// let config = ServerConfig::builder()
420    ///     .sse_transport("https://api.example.com/sse".to_string())
421    ///     .build();
422    /// ```
423    #[must_use]
424    pub fn sse_transport(mut self, url: String) -> Self {
425        self.transport = TransportType::Sse;
426        self.url = Some(url);
427        // Set a dummy command for SSE transport so build() doesn't panic
428        if self.command.is_none() {
429            self.command = Some(String::new());
430        }
431        self
432    }
433
434    /// Sets the URL for HTTP transport.
435    ///
436    /// # Examples
437    ///
438    /// ```
439    /// use mcp_execution_core::ServerConfig;
440    ///
441    /// let config = ServerConfig::builder()
442    ///     .http_transport("https://api.example.com/mcp".to_string())
443    ///     .url("https://api.example.com/mcp/v2".to_string())
444    ///     .build();
445    /// ```
446    #[must_use]
447    pub fn url(mut self, url: String) -> Self {
448        self.url = Some(url);
449        self
450    }
451
452    /// Adds a single HTTP header.
453    ///
454    /// # Examples
455    ///
456    /// ```
457    /// use mcp_execution_core::ServerConfig;
458    ///
459    /// let config = ServerConfig::builder()
460    ///     .http_transport("https://api.example.com/mcp".to_string())
461    ///     .header("Authorization".to_string(), "Bearer token".to_string())
462    ///     .build();
463    /// ```
464    #[must_use]
465    pub fn header(mut self, key: String, value: String) -> Self {
466        self.headers.insert(key, value);
467        self
468    }
469
470    /// Sets all HTTP headers at once, replacing any previously added.
471    ///
472    /// # Examples
473    ///
474    /// ```
475    /// use mcp_execution_core::ServerConfig;
476    /// use std::collections::HashMap;
477    ///
478    /// let mut headers = HashMap::new();
479    /// headers.insert("Authorization".to_string(), "Bearer token".to_string());
480    ///
481    /// let config = ServerConfig::builder()
482    ///     .http_transport("https://api.example.com/mcp".to_string())
483    ///     .headers(headers)
484    ///     .build();
485    /// ```
486    #[must_use]
487    pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
488        self.headers = headers;
489        self
490    }
491
492    /// Builds the `ServerConfig`.
493    ///
494    /// # Panics
495    ///
496    /// Panics if:
497    /// - Command was not set for stdio transport
498    /// - URL was not set for HTTP transport
499    ///
500    /// Use `try_build()` for fallible construction.
501    ///
502    /// # Examples
503    ///
504    /// ```
505    /// use mcp_execution_core::ServerConfig;
506    ///
507    /// let config = ServerConfig::builder()
508    ///     .command("docker".to_string())
509    ///     .build();
510    /// ```
511    #[must_use]
512    pub fn build(self) -> ServerConfig {
513        self.try_build()
514            .expect("ServerConfig::build() failed validation")
515    }
516
517    /// Attempts to build the `ServerConfig`, returning an error if invalid.
518    ///
519    /// # Errors
520    ///
521    /// Returns an error if:
522    /// - Command is not set for stdio transport
523    /// - URL is not set for HTTP transport
524    ///
525    /// # Examples
526    ///
527    /// ```
528    /// use mcp_execution_core::ServerConfig;
529    ///
530    /// let result = ServerConfig::builder()
531    ///     .command("docker".to_string())
532    ///     .try_build();
533    ///
534    /// assert!(result.is_ok());
535    /// ```
536    pub fn try_build(self) -> Result<ServerConfig, String> {
537        match self.transport {
538            TransportType::Stdio => {
539                let command = self
540                    .command
541                    .ok_or_else(|| "command is required for stdio transport".to_string())?;
542
543                if command.trim().is_empty() {
544                    return Err("command cannot be empty for stdio transport".to_string());
545                }
546
547                Ok(ServerConfig {
548                    transport: TransportType::Stdio,
549                    command,
550                    args: self.args,
551                    env: self.env,
552                    cwd: self.cwd,
553                    url: None,
554                    headers: HashMap::new(),
555                })
556            }
557            TransportType::Http => {
558                let url = self
559                    .url
560                    .ok_or_else(|| "url is required for HTTP transport".to_string())?;
561
562                Ok(ServerConfig {
563                    transport: TransportType::Http,
564                    command: String::new(),
565                    args: Vec::new(),
566                    env: HashMap::new(),
567                    cwd: None,
568                    url: Some(url),
569                    headers: self.headers,
570                })
571            }
572            TransportType::Sse => {
573                let url = self
574                    .url
575                    .ok_or_else(|| "url is required for SSE transport".to_string())?;
576
577                Ok(ServerConfig {
578                    transport: TransportType::Sse,
579                    command: String::new(),
580                    args: Vec::new(),
581                    env: HashMap::new(),
582                    cwd: None,
583                    url: Some(url),
584                    headers: self.headers,
585                })
586            }
587        }
588    }
589}
590
591#[cfg(test)]
592mod tests {
593    use super::*;
594
595    #[test]
596    fn test_server_config_builder_minimal() {
597        let config = ServerConfig::builder()
598            .command("docker".to_string())
599            .build();
600
601        assert_eq!(config.command, "docker");
602        assert!(config.args.is_empty());
603        assert!(config.env.is_empty());
604        assert!(config.cwd.is_none());
605    }
606
607    #[test]
608    fn test_server_config_builder_with_args() {
609        let config = ServerConfig::builder()
610            .command("docker".to_string())
611            .arg("run".to_string())
612            .arg("--rm".to_string())
613            .arg("mcp-server".to_string())
614            .build();
615
616        assert_eq!(config.command, "docker");
617        assert_eq!(config.args, vec!["run", "--rm", "mcp-server"]);
618    }
619
620    #[test]
621    fn test_server_config_builder_with_args_vec() {
622        let config = ServerConfig::builder()
623            .command("docker".to_string())
624            .args(vec!["run".to_string(), "--rm".to_string()])
625            .build();
626
627        assert_eq!(config.args, vec!["run", "--rm"]);
628    }
629
630    #[test]
631    fn test_server_config_builder_with_env() {
632        let config = ServerConfig::builder()
633            .command("mcp-server".to_string())
634            .env("LOG_LEVEL".to_string(), "debug".to_string())
635            .env("DEBUG".to_string(), "1".to_string())
636            .build();
637
638        assert_eq!(config.env.len(), 2);
639        assert_eq!(config.env.get("LOG_LEVEL"), Some(&"debug".to_string()));
640        assert_eq!(config.env.get("DEBUG"), Some(&"1".to_string()));
641    }
642
643    #[test]
644    fn test_server_config_builder_with_environment_map() {
645        let mut env_map = HashMap::new();
646        env_map.insert("VAR1".to_string(), "value1".to_string());
647        env_map.insert("VAR2".to_string(), "value2".to_string());
648
649        let config = ServerConfig::builder()
650            .command("mcp-server".to_string())
651            .environment(env_map)
652            .build();
653
654        assert_eq!(config.env.len(), 2);
655    }
656
657    #[test]
658    fn test_server_config_builder_with_cwd() {
659        let config = ServerConfig::builder()
660            .command("mcp-server".to_string())
661            .cwd(PathBuf::from("/tmp"))
662            .build();
663
664        assert_eq!(config.cwd, Some(PathBuf::from("/tmp")));
665    }
666
667    #[test]
668    fn test_server_config_builder_full() {
669        let mut env_map = HashMap::new();
670        env_map.insert("LOG_LEVEL".to_string(), "debug".to_string());
671
672        let config = ServerConfig::builder()
673            .command("/usr/local/bin/mcp-server".to_string())
674            .args(vec!["--port".to_string(), "8080".to_string()])
675            .environment(env_map)
676            .cwd(PathBuf::from("/var/run"))
677            .build();
678
679        assert_eq!(config.command, "/usr/local/bin/mcp-server");
680        assert_eq!(config.args.len(), 2);
681        assert_eq!(config.env.len(), 1);
682        assert_eq!(config.cwd, Some(PathBuf::from("/var/run")));
683    }
684
685    #[test]
686    #[should_panic(expected = "command")]
687    fn test_server_config_builder_missing_command() {
688        let _ = ServerConfig::builder().build();
689    }
690
691    #[test]
692    fn test_server_config_builder_try_build_missing_command() {
693        let result = ServerConfig::builder().try_build();
694        assert!(result.is_err());
695        assert!(result.unwrap_err().contains("command"));
696    }
697
698    #[test]
699    fn test_server_config_accessors() {
700        let config = ServerConfig::builder()
701            .command("docker".to_string())
702            .arg("run".to_string())
703            .env("VAR".to_string(), "value".to_string())
704            .cwd(PathBuf::from("/tmp"))
705            .build();
706
707        assert_eq!(config.command(), "docker");
708        assert_eq!(config.args(), &["run".to_string()]);
709        assert_eq!(config.env().len(), 1);
710        assert_eq!(config.cwd(), Some(&PathBuf::from("/tmp")));
711    }
712
713    #[test]
714    fn test_server_config_serialize_deserialize() {
715        let config = ServerConfig::builder()
716            .command("mcp-server".to_string())
717            .arg("--verbose".to_string())
718            .env("DEBUG".to_string(), "1".to_string())
719            .build();
720
721        let json = serde_json::to_string(&config).unwrap();
722        let deserialized: ServerConfig = serde_json::from_str(&json).unwrap();
723
724        assert_eq!(config, deserialized);
725    }
726
727    #[test]
728    fn test_server_config_clone() {
729        let config = ServerConfig::builder()
730            .command("docker".to_string())
731            .build();
732
733        let cloned = config.clone();
734        assert_eq!(config, cloned);
735    }
736
737    #[test]
738    fn test_server_config_debug() {
739        let config = ServerConfig::builder()
740            .command("docker".to_string())
741            .build();
742
743        let debug_str = format!("{config:?}");
744        assert!(debug_str.contains("docker"));
745    }
746
747    #[test]
748    fn test_transport_type_default() {
749        let transport = TransportType::default();
750        assert_eq!(transport, TransportType::Stdio);
751    }
752
753    #[test]
754    fn test_server_config_http_transport() {
755        let config = ServerConfig::builder()
756            .http_transport("https://api.example.com/mcp".to_string())
757            .build();
758
759        assert_eq!(config.transport, TransportType::Http);
760        assert_eq!(config.url(), Some("https://api.example.com/mcp"));
761        assert!(config.headers.is_empty());
762        assert!(config.command.is_empty());
763    }
764
765    #[test]
766    fn test_server_config_http_with_headers() {
767        let config = ServerConfig::builder()
768            .http_transport("https://api.example.com/mcp".to_string())
769            .header("Authorization".to_string(), "Bearer token".to_string())
770            .header("Content-Type".to_string(), "application/json".to_string())
771            .build();
772
773        assert_eq!(config.transport, TransportType::Http);
774        assert_eq!(config.headers.len(), 2);
775        assert_eq!(
776            config.headers.get("Authorization"),
777            Some(&"Bearer token".to_string())
778        );
779        assert_eq!(
780            config.headers.get("Content-Type"),
781            Some(&"application/json".to_string())
782        );
783    }
784
785    #[test]
786    fn test_server_config_http_with_headers_map() {
787        let mut headers = HashMap::new();
788        headers.insert("Authorization".to_string(), "Bearer token".to_string());
789
790        let config = ServerConfig::builder()
791            .http_transport("https://api.example.com/mcp".to_string())
792            .headers(headers)
793            .build();
794
795        assert_eq!(config.headers.len(), 1);
796    }
797
798    #[test]
799    fn test_server_config_http_try_build_missing_url() {
800        let result = ServerConfig::builder().try_build();
801        assert!(result.is_err());
802        assert!(result.unwrap_err().contains("required"));
803    }
804
805    #[test]
806    fn test_server_config_http_accessors() {
807        let config = ServerConfig::builder()
808            .http_transport("https://api.example.com/mcp".to_string())
809            .header("Auth".to_string(), "token".to_string())
810            .build();
811
812        assert_eq!(config.transport(), &TransportType::Http);
813        assert_eq!(config.url(), Some("https://api.example.com/mcp"));
814        assert_eq!(config.headers().len(), 1);
815    }
816
817    #[test]
818    fn test_server_config_stdio_default_transport() {
819        let config = ServerConfig::builder()
820            .command("docker".to_string())
821            .build();
822
823        assert_eq!(config.transport, TransportType::Stdio);
824    }
825
826    #[test]
827    fn test_server_config_sse_transport() {
828        let config = ServerConfig::builder()
829            .sse_transport("https://api.example.com/sse".to_string())
830            .build();
831
832        assert_eq!(config.transport, TransportType::Sse);
833        assert_eq!(config.url(), Some("https://api.example.com/sse"));
834        assert!(config.headers.is_empty());
835        assert!(config.command.is_empty());
836    }
837
838    #[test]
839    fn test_server_config_sse_with_headers() {
840        let config = ServerConfig::builder()
841            .sse_transport("https://api.example.com/sse".to_string())
842            .header("Authorization".to_string(), "Bearer token".to_string())
843            .header("X-Custom".to_string(), "value".to_string())
844            .build();
845
846        assert_eq!(config.transport, TransportType::Sse);
847        assert_eq!(config.headers.len(), 2);
848        assert_eq!(
849            config.headers.get("Authorization"),
850            Some(&"Bearer token".to_string())
851        );
852        assert_eq!(config.headers.get("X-Custom"), Some(&"value".to_string()));
853    }
854
855    #[test]
856    fn test_server_config_sse_try_build_missing_url() {
857        let mut builder = ServerConfig::builder();
858        builder.transport = TransportType::Sse;
859
860        let result = builder.try_build();
861        assert!(result.is_err());
862        assert!(result.unwrap_err().contains("url is required"));
863    }
864
865    #[test]
866    fn test_transport_type_serialization() {
867        let stdio = TransportType::Stdio;
868        let http = TransportType::Http;
869        let sse = TransportType::Sse;
870
871        assert_eq!(serde_json::to_string(&stdio).unwrap(), "\"stdio\"");
872        assert_eq!(serde_json::to_string(&http).unwrap(), "\"http\"");
873        assert_eq!(serde_json::to_string(&sse).unwrap(), "\"sse\"");
874    }
875
876    #[test]
877    fn test_transport_type_deserialization() {
878        let stdio: TransportType = serde_json::from_str("\"stdio\"").unwrap();
879        let http: TransportType = serde_json::from_str("\"http\"").unwrap();
880        let sse: TransportType = serde_json::from_str("\"sse\"").unwrap();
881
882        assert_eq!(stdio, TransportType::Stdio);
883        assert_eq!(http, TransportType::Http);
884        assert_eq!(sse, TransportType::Sse);
885    }
886}