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}