Skip to main content

turbomcp_server/
config.rs

1//! Server Configuration
2//!
3//! This module provides configuration options for MCP servers including:
4//! - Protocol version negotiation
5//! - Rate limiting
6//! - Connection limits
7//! - Capability requirements
8
9use std::collections::HashSet;
10use std::sync::Arc;
11use std::sync::atomic::{AtomicUsize, Ordering};
12use std::time::{Duration, Instant};
13
14use parking_lot::Mutex;
15use serde::{Deserialize, Serialize};
16
17// Re-export from core (single source of truth - DRY)
18pub use turbomcp_core::SUPPORTED_VERSIONS as SUPPORTED_PROTOCOL_VERSIONS;
19pub use turbomcp_types::ProtocolVersion;
20
21/// Default maximum connections for TCP transport.
22pub const DEFAULT_MAX_CONNECTIONS: usize = 1000;
23
24/// Default rate limit (requests per second).
25pub const DEFAULT_RATE_LIMIT: u32 = 100;
26
27/// Default rate limit window.
28pub const DEFAULT_RATE_LIMIT_WINDOW: Duration = Duration::from_secs(1);
29
30/// Default maximum message size (10MB).
31pub const DEFAULT_MAX_MESSAGE_SIZE: usize = 10 * 1024 * 1024;
32
33/// Default idle lifetime of a Streamable HTTP session (1 hour).
34pub const DEFAULT_HTTP_SESSION_IDLE_TIMEOUT: Duration = Duration::from_secs(60 * 60);
35
36/// Default cap on concurrent Streamable HTTP sessions.
37pub const DEFAULT_MAX_HTTP_SESSIONS: usize = 10_000;
38
39/// Origin validation configuration for HTTP transports.
40#[derive(Debug, Clone)]
41pub struct OriginValidationConfig {
42    /// Explicitly allowed origins.
43    pub allowed_origins: HashSet<String>,
44    /// Whether to allow localhost/browser-dev origins.
45    pub allow_localhost: bool,
46    /// Whether to disable origin checks entirely.
47    pub allow_any: bool,
48    /// Trusted reverse-proxy IPs / CIDRs whose `X-Forwarded-For`,
49    /// `X-Real-IP`, `CF-Connecting-IP`, and `X-Client-IP` headers we will
50    /// honour. **Empty means trust nothing** — direct clients can no longer
51    /// spoof their source IP via headers. Entries accept CIDR notation
52    /// (`10.0.0.0/8`) or bare addresses (`10.0.0.5`).
53    pub trusted_proxies: Vec<String>,
54    /// Whether to accept requests with no `Origin` header from non-loopback
55    /// clients.
56    ///
57    /// Non-browser clients — CLIs, agent runtimes, other servers — send no
58    /// `Origin`, so a server reachable over the network refuses all of them by
59    /// default. This admits them while still validating every `Origin` that is
60    /// present, which `allow_any` does not. Pair it with authentication.
61    pub allow_missing_origin: bool,
62    /// Whether the HTTP transport answers CORS preflights and attaches CORS
63    /// headers for the origins this policy accepts.
64    ///
65    /// Off by default. A browser-based client on an allowlisted origin needs it
66    /// to read responses at all, and in particular to see `Mcp-Session-Id`.
67    pub cors: bool,
68}
69
70impl Default for OriginValidationConfig {
71    fn default() -> Self {
72        Self {
73            allowed_origins: HashSet::new(),
74            allow_localhost: true,
75            allow_any: false,
76            trusted_proxies: Vec::new(),
77            allow_missing_origin: false,
78            cors: false,
79        }
80    }
81}
82
83impl OriginValidationConfig {
84    /// Create a new origin validation configuration.
85    #[must_use]
86    pub fn new() -> Self {
87        Self::default()
88    }
89}
90
91/// Session lifetime policy for the Streamable HTTP transport.
92///
93/// A session holds memory for as long as it exists — replay history, log
94/// level, pending server requests — and a client that initializes and walks
95/// away never says so. §Session Management lets the server end a session at
96/// any time; requests naming it then get 404 and a conforming client starts
97/// a new one.
98#[derive(Debug, Clone, Copy)]
99pub struct HttpSessionConfig {
100    /// How long a session may sit idle before it is reaped.
101    ///
102    /// A session is idle while it has no request in flight and no attached
103    /// stream, so a client holding a GET stream open is never reaped however
104    /// quiet it is.
105    pub idle_timeout: Duration,
106    /// Maximum concurrent sessions. An `initialize` beyond this is refused
107    /// with 503 Service Unavailable, after idle sessions have been reaped.
108    pub max_sessions: usize,
109}
110
111impl Default for HttpSessionConfig {
112    fn default() -> Self {
113        Self {
114            idle_timeout: DEFAULT_HTTP_SESSION_IDLE_TIMEOUT,
115            max_sessions: DEFAULT_MAX_HTTP_SESSIONS,
116        }
117    }
118}
119
120/// Server configuration.
121#[derive(Debug, Clone)]
122pub struct ServerConfig {
123    /// Protocol version configuration.
124    pub protocol: ProtocolConfig,
125    /// Rate limiting configuration.
126    pub rate_limit: Option<RateLimitConfig>,
127    /// Connection limits.
128    pub connection_limits: ConnectionLimits,
129    /// Required client capabilities.
130    pub required_capabilities: RequiredCapabilities,
131    /// Maximum message size in bytes (default: 10MB).
132    pub max_message_size: usize,
133    /// HTTP origin validation policy.
134    pub origin_validation: OriginValidationConfig,
135    /// Streamable HTTP session lifetime policy.
136    pub http_sessions: HttpSessionConfig,
137    /// MCP authorization for the Streamable HTTP transport. `None` serves
138    /// every request unauthenticated.
139    #[cfg(feature = "http")]
140    pub authorization: Option<crate::HttpAuthorization>,
141}
142
143impl Default for ServerConfig {
144    fn default() -> Self {
145        Self {
146            protocol: ProtocolConfig::default(),
147            rate_limit: None,
148            connection_limits: ConnectionLimits::default(),
149            required_capabilities: RequiredCapabilities::default(),
150            max_message_size: DEFAULT_MAX_MESSAGE_SIZE,
151            origin_validation: OriginValidationConfig::default(),
152            http_sessions: HttpSessionConfig::default(),
153            #[cfg(feature = "http")]
154            authorization: None,
155        }
156    }
157}
158
159impl ServerConfig {
160    /// Create a new server configuration with defaults.
161    #[must_use]
162    pub fn new() -> Self {
163        Self::default()
164    }
165
166    /// Create a builder for server configuration.
167    #[must_use]
168    pub fn builder() -> ServerConfigBuilder {
169        ServerConfigBuilder::default()
170    }
171}
172
173/// Builder for server configuration.
174#[derive(Debug, Clone, Default)]
175pub struct ServerConfigBuilder {
176    protocol: Option<ProtocolConfig>,
177    rate_limit: Option<RateLimitConfig>,
178    connection_limits: Option<ConnectionLimits>,
179    required_capabilities: Option<RequiredCapabilities>,
180    max_message_size: Option<usize>,
181    origin_validation: Option<OriginValidationConfig>,
182    http_sessions: Option<HttpSessionConfig>,
183    #[cfg(feature = "http")]
184    authorization: Option<crate::HttpAuthorization>,
185}
186
187impl ServerConfigBuilder {
188    /// Set protocol configuration.
189    #[must_use]
190    pub fn protocol(mut self, config: ProtocolConfig) -> Self {
191        self.protocol = Some(config);
192        self
193    }
194
195    /// Set rate limiting configuration.
196    #[must_use]
197    pub fn rate_limit(mut self, config: RateLimitConfig) -> Self {
198        self.rate_limit = Some(config);
199        self
200    }
201
202    /// Set connection limits.
203    #[must_use]
204    pub fn connection_limits(mut self, limits: ConnectionLimits) -> Self {
205        self.connection_limits = Some(limits);
206        self
207    }
208
209    /// Set required client capabilities.
210    #[must_use]
211    pub fn required_capabilities(mut self, caps: RequiredCapabilities) -> Self {
212        self.required_capabilities = Some(caps);
213        self
214    }
215
216    /// Set maximum message size in bytes.
217    ///
218    /// Messages exceeding this size will be rejected.
219    /// Default: 10MB.
220    #[must_use]
221    pub fn max_message_size(mut self, size: usize) -> Self {
222        self.max_message_size = Some(size);
223        self
224    }
225
226    /// Set HTTP origin validation configuration.
227    #[must_use]
228    pub fn origin_validation(mut self, config: OriginValidationConfig) -> Self {
229        self.origin_validation = Some(config);
230        self
231    }
232
233    /// Add a single allowed origin for HTTP transports.
234    #[must_use]
235    pub fn allow_origin(mut self, origin: impl Into<String>) -> Self {
236        self.origin_validation
237            .get_or_insert_with(OriginValidationConfig::default)
238            .allowed_origins
239            .insert(origin.into());
240        self
241    }
242
243    /// Add multiple allowed origins for HTTP transports.
244    #[must_use]
245    pub fn allow_origins<I, S>(mut self, origins: I) -> Self
246    where
247        I: IntoIterator<Item = S>,
248        S: Into<String>,
249    {
250        let config = self
251            .origin_validation
252            .get_or_insert_with(OriginValidationConfig::default);
253        config
254            .allowed_origins
255            .extend(origins.into_iter().map(Into::into));
256        self
257    }
258
259    /// Control whether localhost origins are accepted.
260    #[must_use]
261    pub fn allow_localhost_origins(mut self, allow: bool) -> Self {
262        self.origin_validation
263            .get_or_insert_with(OriginValidationConfig::default)
264            .allow_localhost = allow;
265        self
266    }
267
268    /// Disable origin checks entirely.
269    #[must_use]
270    pub fn allow_any_origin(mut self, allow: bool) -> Self {
271        self.origin_validation
272            .get_or_insert_with(OriginValidationConfig::default)
273            .allow_any = allow;
274        self
275    }
276
277    /// Accept requests with no `Origin` header from non-loopback clients,
278    /// while still validating every `Origin` that is present.
279    ///
280    /// See [`OriginValidationConfig::allow_missing_origin`].
281    #[must_use]
282    pub fn allow_missing_origin(mut self, allow: bool) -> Self {
283        self.origin_validation
284            .get_or_insert_with(OriginValidationConfig::default)
285            .allow_missing_origin = allow;
286        self
287    }
288
289    /// Answer CORS preflights and attach CORS headers for accepted origins.
290    ///
291    /// See [`OriginValidationConfig::cors`].
292    #[must_use]
293    pub fn cors(mut self, enabled: bool) -> Self {
294        self.origin_validation
295            .get_or_insert_with(OriginValidationConfig::default)
296            .cors = enabled;
297        self
298    }
299
300    /// Require MCP authorization on the Streamable HTTP transport.
301    ///
302    /// See [`HttpAuthorization`](crate::HttpAuthorization): the server
303    /// publishes its protected-resource metadata, and every MCP request must
304    /// carry a bearer token the configured validator accepts.
305    #[cfg(feature = "http")]
306    #[must_use]
307    pub fn authorization(mut self, authorization: crate::HttpAuthorization) -> Self {
308        self.authorization = Some(authorization);
309        self
310    }
311
312    /// Set the Streamable HTTP session policy.
313    #[must_use]
314    pub fn http_sessions(mut self, config: HttpSessionConfig) -> Self {
315        self.http_sessions = Some(config);
316        self
317    }
318
319    /// Set how long a Streamable HTTP session may sit idle before it is reaped.
320    ///
321    /// Default: 1 hour.
322    #[must_use]
323    pub fn http_session_idle_timeout(mut self, timeout: Duration) -> Self {
324        self.http_sessions
325            .get_or_insert_with(HttpSessionConfig::default)
326            .idle_timeout = timeout;
327        self
328    }
329
330    /// Set the maximum number of concurrent Streamable HTTP sessions.
331    ///
332    /// Default: 10,000.
333    #[must_use]
334    pub fn max_http_sessions(mut self, max: usize) -> Self {
335        self.http_sessions
336            .get_or_insert_with(HttpSessionConfig::default)
337            .max_sessions = max;
338        self
339    }
340
341    /// Build the server configuration with sensible defaults.
342    ///
343    /// This method always succeeds and uses defaults for any unset fields.
344    /// For strict validation, use [`try_build()`](Self::try_build).
345    #[must_use]
346    pub fn build(self) -> ServerConfig {
347        ServerConfig {
348            protocol: self.protocol.unwrap_or_default(),
349            rate_limit: self.rate_limit,
350            connection_limits: self.connection_limits.unwrap_or_default(),
351            required_capabilities: self.required_capabilities.unwrap_or_default(),
352            max_message_size: self.max_message_size.unwrap_or(DEFAULT_MAX_MESSAGE_SIZE),
353            origin_validation: self.origin_validation.unwrap_or_default(),
354            http_sessions: self.http_sessions.unwrap_or_default(),
355            #[cfg(feature = "http")]
356            authorization: self.authorization,
357        }
358    }
359
360    /// Build the server configuration with validation.
361    ///
362    /// This method validates the configuration and returns an error if any
363    /// constraints are violated. Use this for stricter configuration checking
364    /// in enterprise deployments.
365    ///
366    /// # Errors
367    ///
368    /// Returns an error if:
369    /// - `max_message_size` is less than 1024 bytes (minimum viable message size)
370    /// - Rate limit `max_requests` is 0
371    /// - Rate limit `window` is zero
372    /// - Connection limits have all values set to 0
373    /// - `max_http_sessions` is 0
374    ///
375    /// # Example
376    ///
377    /// ```rust
378    /// use turbomcp_server::ServerConfig;
379    ///
380    /// // Validated build - catches configuration errors
381    /// let config = ServerConfig::builder()
382    ///     .max_message_size(1024 * 1024) // 1MB
383    ///     .try_build()
384    ///     .expect("Invalid configuration");
385    /// ```
386    pub fn try_build(self) -> Result<ServerConfig, ConfigValidationError> {
387        let max_message_size = self.max_message_size.unwrap_or(DEFAULT_MAX_MESSAGE_SIZE);
388
389        // Validate message size
390        if max_message_size < 1024 {
391            return Err(ConfigValidationError::InvalidMessageSize {
392                size: max_message_size,
393                min: 1024,
394            });
395        }
396
397        // Validate rate limit if provided
398        if let Some(ref rate_limit) = self.rate_limit {
399            if rate_limit.max_requests == 0 {
400                return Err(ConfigValidationError::InvalidRateLimit {
401                    reason: "max_requests cannot be 0".to_string(),
402                });
403            }
404            if rate_limit.window.is_zero() {
405                return Err(ConfigValidationError::InvalidRateLimit {
406                    reason: "rate limit window cannot be zero".to_string(),
407                });
408            }
409        }
410
411        // Validate connection limits
412        let connection_limits = self.connection_limits.unwrap_or_default();
413        if connection_limits.max_tcp_connections == 0
414            && connection_limits.max_websocket_connections == 0
415            && connection_limits.max_http_concurrent == 0
416            && connection_limits.max_unix_connections == 0
417        {
418            return Err(ConfigValidationError::InvalidConnectionLimits {
419                reason: "at least one connection limit must be non-zero".to_string(),
420            });
421        }
422
423        let http_sessions = self.http_sessions.unwrap_or_default();
424        if http_sessions.max_sessions == 0 {
425            return Err(ConfigValidationError::InvalidConnectionLimits {
426                reason: "max_http_sessions cannot be 0".to_string(),
427            });
428        }
429
430        Ok(ServerConfig {
431            protocol: self.protocol.unwrap_or_default(),
432            rate_limit: self.rate_limit,
433            connection_limits,
434            required_capabilities: self.required_capabilities.unwrap_or_default(),
435            max_message_size,
436            origin_validation: self.origin_validation.unwrap_or_default(),
437            http_sessions,
438            #[cfg(feature = "http")]
439            authorization: self.authorization,
440        })
441    }
442}
443
444/// Every field carries over. The destructuring is exhaustive on purpose: a
445/// field added to `ServerConfig` and not handled here is a compile error.
446/// `ServerBuilder::with_config` used to copy fields one by one and silently
447/// dropped `http_sessions` and `authorization` — the latter leaving a server
448/// configured for authorization serving HTTP unauthenticated.
449impl From<ServerConfig> for ServerConfigBuilder {
450    fn from(config: ServerConfig) -> Self {
451        let ServerConfig {
452            protocol,
453            rate_limit,
454            connection_limits,
455            required_capabilities,
456            max_message_size,
457            origin_validation,
458            http_sessions,
459            #[cfg(feature = "http")]
460            authorization,
461        } = config;
462        Self {
463            protocol: Some(protocol),
464            rate_limit,
465            connection_limits: Some(connection_limits),
466            required_capabilities: Some(required_capabilities),
467            max_message_size: Some(max_message_size),
468            origin_validation: Some(origin_validation),
469            http_sessions: Some(http_sessions),
470            #[cfg(feature = "http")]
471            authorization,
472        }
473    }
474}
475
476/// Errors that can occur during configuration validation.
477#[derive(Debug, Clone, thiserror::Error)]
478pub enum ConfigValidationError {
479    /// Invalid message size configuration.
480    #[error("Invalid max_message_size: {size} bytes is below minimum of {min} bytes")]
481    InvalidMessageSize {
482        /// The configured size.
483        size: usize,
484        /// The minimum allowed size.
485        min: usize,
486    },
487
488    /// Invalid rate limit configuration.
489    #[error("Invalid rate limit: {reason}")]
490    InvalidRateLimit {
491        /// Description of the validation failure.
492        reason: String,
493    },
494
495    /// Invalid connection limits configuration.
496    #[error("Invalid connection limits: {reason}")]
497    InvalidConnectionLimits {
498        /// Description of the validation failure.
499        reason: String,
500    },
501}
502
503/// Protocol version configuration.
504#[derive(Debug, Clone)]
505pub struct ProtocolConfig {
506    /// Preferred protocol version.
507    pub preferred_version: ProtocolVersion,
508    /// Supported protocol versions.
509    pub supported_versions: Vec<ProtocolVersion>,
510    /// Answer an unsupported requested version with the preferred one.
511    ///
512    /// The lifecycle spec requires this, so it should stay `true`. With
513    /// `false` an unsupported request is refused with -32602 instead.
514    pub allow_fallback: bool,
515}
516
517impl Default for ProtocolConfig {
518    /// Multi-version by default in v3.1.
519    ///
520    /// Pre-3.1 the default was exact-match against `LATEST`, which silently
521    /// rejected clients on older stable spec versions even though we ship
522    /// version adapters for them. The spec permits the server to choose a
523    /// different version than the client requested; defaulting to the full
524    /// stable set unblocks older clients while still preferring the newest.
525    /// Use [`Self::strict`] to restore the old single-version behavior.
526    ///
527    /// `allow_fallback` is on because the lifecycle spec makes it mandatory:
528    /// "If the server supports the requested protocol version, it MUST respond
529    /// with the same version. Otherwise, the server MUST respond with another
530    /// protocol version it supports." Refusing the handshake outright is not
531    /// one of the permitted outcomes — the client is the party that decides
532    /// whether the offered version is acceptable.
533    fn default() -> Self {
534        Self {
535            preferred_version: ProtocolVersion::LATEST.clone(),
536            supported_versions: ProtocolVersion::STABLE.to_vec(),
537            allow_fallback: true,
538        }
539    }
540}
541
542impl ProtocolConfig {
543    /// Create a configuration that speaks only the specified version.
544    ///
545    /// A client asking for any other version is answered with this one — the
546    /// lifecycle spec's required response to an unsupported request — and
547    /// decides for itself whether to continue. It used to be refused outright,
548    /// which the spec does not permit.
549    #[must_use]
550    pub fn strict(version: impl Into<ProtocolVersion>) -> Self {
551        let v = version.into();
552        Self {
553            preferred_version: v.clone(),
554            supported_versions: vec![v],
555            allow_fallback: true,
556        }
557    }
558
559    /// Create a multi-version configuration that accepts all stable versions.
560    ///
561    /// The preferred version is the latest stable. Older clients are accepted
562    /// and responses are filtered through the appropriate version adapter.
563    /// A version outside the stable set is answered with the preferred one
564    /// rather than an error, as the lifecycle spec requires.
565    #[must_use]
566    pub fn multi_version() -> Self {
567        Self {
568            preferred_version: ProtocolVersion::LATEST.clone(),
569            supported_versions: ProtocolVersion::STABLE.to_vec(),
570            allow_fallback: true,
571        }
572    }
573
574    /// Check if a protocol version is supported.
575    #[must_use]
576    pub fn is_supported(&self, version: &ProtocolVersion) -> bool {
577        self.supported_versions.contains(version)
578    }
579
580    /// Negotiate protocol version with client.
581    ///
582    /// Returns the negotiated version or None if no compatible version found.
583    #[must_use]
584    pub fn negotiate(&self, client_version: Option<&str>) -> Option<ProtocolVersion> {
585        match client_version {
586            Some(version_str) => {
587                let version = ProtocolVersion::from(version_str);
588                if self.is_supported(&version) {
589                    Some(version)
590                } else if self.allow_fallback {
591                    Some(self.preferred_version.clone())
592                } else {
593                    None
594                }
595            }
596            None => Some(self.preferred_version.clone()),
597        }
598    }
599}
600
601/// Rate limiting configuration.
602#[derive(Debug, Clone)]
603pub struct RateLimitConfig {
604    /// Maximum requests per window.
605    pub max_requests: u32,
606    /// Time window for rate limiting.
607    pub window: Duration,
608    /// Whether to rate limit per client (by user_id or IP).
609    pub per_client: bool,
610}
611
612impl Default for RateLimitConfig {
613    fn default() -> Self {
614        Self {
615            max_requests: DEFAULT_RATE_LIMIT,
616            window: DEFAULT_RATE_LIMIT_WINDOW,
617            per_client: true,
618        }
619    }
620}
621
622impl RateLimitConfig {
623    /// Create a new rate limit configuration.
624    #[must_use]
625    pub fn new(max_requests: u32, window: Duration) -> Self {
626        Self {
627            max_requests,
628            window,
629            per_client: true,
630        }
631    }
632
633    /// Set per-client rate limiting.
634    #[must_use]
635    pub fn per_client(mut self, enabled: bool) -> Self {
636        self.per_client = enabled;
637        self
638    }
639}
640
641/// Connection limits.
642#[derive(Debug, Clone)]
643pub struct ConnectionLimits {
644    /// Maximum concurrent TCP connections.
645    pub max_tcp_connections: usize,
646    /// Maximum concurrent WebSocket connections.
647    pub max_websocket_connections: usize,
648    /// Maximum concurrent HTTP requests.
649    pub max_http_concurrent: usize,
650    /// Maximum concurrent Unix socket connections.
651    pub max_unix_connections: usize,
652}
653
654impl Default for ConnectionLimits {
655    fn default() -> Self {
656        Self {
657            max_tcp_connections: DEFAULT_MAX_CONNECTIONS,
658            max_websocket_connections: DEFAULT_MAX_CONNECTIONS,
659            max_http_concurrent: DEFAULT_MAX_CONNECTIONS,
660            max_unix_connections: DEFAULT_MAX_CONNECTIONS,
661        }
662    }
663}
664
665impl ConnectionLimits {
666    /// Create a new connection limits configuration.
667    #[must_use]
668    pub fn new(max_connections: usize) -> Self {
669        Self {
670            max_tcp_connections: max_connections,
671            max_websocket_connections: max_connections,
672            max_http_concurrent: max_connections,
673            max_unix_connections: max_connections,
674        }
675    }
676}
677
678/// Required client capabilities.
679///
680/// Specifies which client capabilities the server requires.
681#[derive(Debug, Clone, Default, Serialize, Deserialize)]
682pub struct RequiredCapabilities {
683    /// Require roots capability.
684    #[serde(default)]
685    pub roots: bool,
686    /// Require sampling capability.
687    #[serde(default)]
688    pub sampling: bool,
689    /// Require draft extensions.
690    #[serde(default)]
691    pub extensions: HashSet<String>,
692    /// Require experimental capabilities.
693    #[serde(default)]
694    pub experimental: HashSet<String>,
695}
696
697impl RequiredCapabilities {
698    /// Create empty required capabilities (no requirements).
699    #[must_use]
700    pub fn none() -> Self {
701        Self::default()
702    }
703
704    /// Require roots capability.
705    #[must_use]
706    pub fn with_roots(mut self) -> Self {
707        self.roots = true;
708        self
709    }
710
711    /// Require sampling capability.
712    #[must_use]
713    pub fn with_sampling(mut self) -> Self {
714        self.sampling = true;
715        self
716    }
717
718    /// Require a draft extension.
719    #[must_use]
720    pub fn with_extension(mut self, name: impl Into<String>) -> Self {
721        self.extensions.insert(name.into());
722        self
723    }
724
725    /// Require an experimental capability.
726    #[must_use]
727    pub fn with_experimental(mut self, name: impl Into<String>) -> Self {
728        self.experimental.insert(name.into());
729        self
730    }
731
732    /// Check if all required capabilities are present in client capabilities.
733    #[must_use]
734    pub fn validate(&self, client_caps: &ClientCapabilities) -> CapabilityValidation {
735        let mut missing = Vec::new();
736
737        if self.roots && !client_caps.roots {
738            missing.push("roots".to_string());
739        }
740
741        if self.sampling && !client_caps.sampling {
742            missing.push("sampling".to_string());
743        }
744
745        for extension in &self.extensions {
746            if !client_caps.extensions.contains(extension) {
747                missing.push(format!("extensions/{}", extension));
748            }
749        }
750
751        for exp in &self.experimental {
752            if !client_caps.experimental.contains(exp) {
753                missing.push(format!("experimental/{}", exp));
754            }
755        }
756
757        if missing.is_empty() {
758            CapabilityValidation::Valid
759        } else {
760            CapabilityValidation::Missing(missing)
761        }
762    }
763}
764
765/// Client capabilities received during initialization.
766#[derive(Debug, Clone, Default, Serialize, Deserialize)]
767pub struct ClientCapabilities {
768    /// Client supports roots.
769    #[serde(default)]
770    pub roots: bool,
771    /// Client supports sampling.
772    #[serde(default)]
773    pub sampling: bool,
774    /// Client draft extensions.
775    #[serde(default)]
776    pub extensions: HashSet<String>,
777    /// Client experimental capabilities.
778    #[serde(default)]
779    pub experimental: HashSet<String>,
780}
781
782impl ClientCapabilities {
783    /// Parse client capabilities from initialize request params.
784    #[must_use]
785    pub fn from_params(params: &serde_json::Value) -> Self {
786        let caps = params.get("capabilities").cloned().unwrap_or_default();
787
788        Self {
789            roots: caps.get("roots").map(|v| !v.is_null()).unwrap_or(false),
790            sampling: caps.get("sampling").map(|v| !v.is_null()).unwrap_or(false),
791            extensions: caps
792                .get("extensions")
793                .and_then(|v| v.as_object())
794                .map(|obj| obj.keys().cloned().collect())
795                .unwrap_or_default(),
796            experimental: caps
797                .get("experimental")
798                .and_then(|v| v.as_object())
799                .map(|obj| obj.keys().cloned().collect())
800                .unwrap_or_default(),
801        }
802    }
803}
804
805/// Result of capability validation.
806#[derive(Debug, Clone)]
807pub enum CapabilityValidation {
808    /// All required capabilities are present.
809    Valid,
810    /// Some required capabilities are missing.
811    Missing(Vec<String>),
812}
813
814impl CapabilityValidation {
815    /// Check if validation passed.
816    #[must_use]
817    pub fn is_valid(&self) -> bool {
818        matches!(self, Self::Valid)
819    }
820
821    /// Get missing capabilities if any.
822    #[must_use]
823    pub fn missing(&self) -> Option<&[String]> {
824        match self {
825            Self::Valid => None,
826            Self::Missing(caps) => Some(caps),
827        }
828    }
829}
830
831/// Rate limiter using token bucket algorithm.
832#[derive(Debug)]
833pub struct RateLimiter {
834    config: RateLimitConfig,
835    /// Global bucket for non-per-client limiting.
836    global_bucket: Mutex<TokenBucket>,
837    /// Per-client buckets (keyed by client ID).
838    client_buckets: Mutex<std::collections::HashMap<String, TokenBucket>>,
839    /// Last cleanup timestamp for automatic cleanup.
840    last_cleanup: Mutex<Instant>,
841}
842
843impl RateLimiter {
844    /// Create a new rate limiter.
845    #[must_use]
846    pub fn new(config: RateLimitConfig) -> Self {
847        Self {
848            global_bucket: Mutex::new(TokenBucket::new(config.max_requests, config.window)),
849            client_buckets: Mutex::new(std::collections::HashMap::new()),
850            last_cleanup: Mutex::new(Instant::now()),
851            config,
852        }
853    }
854
855    /// Check if a request is allowed.
856    ///
857    /// Returns `true` if allowed, `false` if rate limited.
858    pub fn check(&self, client_id: Option<&str>) -> bool {
859        // Periodic cleanup of stale client buckets (avoid unbounded growth)
860        let needs_cleanup = {
861            let last = self.last_cleanup.lock();
862            last.elapsed() > Duration::from_secs(60)
863        };
864        if needs_cleanup {
865            self.cleanup(Duration::from_secs(300));
866            *self.last_cleanup.lock() = Instant::now();
867        }
868
869        if self.config.per_client {
870            if let Some(id) = client_id {
871                let mut buckets = self.client_buckets.lock();
872                let bucket = buckets.entry(id.to_string()).or_insert_with(|| {
873                    TokenBucket::new(self.config.max_requests, self.config.window)
874                });
875                bucket.try_acquire()
876            } else {
877                // No client ID, use global bucket
878                self.global_bucket.lock().try_acquire()
879            }
880        } else {
881            self.global_bucket.lock().try_acquire()
882        }
883    }
884
885    /// Clean up old client buckets to prevent memory growth.
886    pub fn cleanup(&self, max_age: Duration) {
887        let mut buckets = self.client_buckets.lock();
888        let now = Instant::now();
889        buckets.retain(|_, bucket| now.duration_since(bucket.last_access) < max_age);
890    }
891
892    /// Get the current number of tracked client buckets.
893    #[must_use]
894    pub fn client_bucket_count(&self) -> usize {
895        self.client_buckets.lock().len()
896    }
897}
898
899/// Token bucket for rate limiting.
900#[derive(Debug)]
901struct TokenBucket {
902    tokens: f64,
903    max_tokens: f64,
904    refill_rate: f64, // tokens per second
905    last_refill: Instant,
906    last_access: Instant,
907}
908
909impl TokenBucket {
910    fn new(max_requests: u32, window: Duration) -> Self {
911        let max_tokens = max_requests as f64;
912        let refill_rate = max_tokens / window.as_secs_f64();
913        Self {
914            tokens: max_tokens,
915            max_tokens,
916            refill_rate,
917            last_refill: Instant::now(),
918            last_access: Instant::now(),
919        }
920    }
921
922    fn try_acquire(&mut self) -> bool {
923        let now = Instant::now();
924        let elapsed = now.duration_since(self.last_refill);
925
926        // Only refill if meaningful time has passed (reduces syscalls on burst traffic)
927        if elapsed >= Duration::from_millis(10) {
928            self.tokens =
929                (self.tokens + elapsed.as_secs_f64() * self.refill_rate).min(self.max_tokens);
930            self.last_refill = now;
931        }
932
933        self.last_access = now;
934
935        if self.tokens >= 1.0 {
936            self.tokens -= 1.0;
937            true
938        } else {
939            false
940        }
941    }
942}
943
944/// Connection counter for tracking active connections.
945///
946/// This is designed to be wrapped in `Arc` and shared across async tasks.
947/// Use `try_acquire_arc` to get a guard that can be moved into spawned tasks.
948#[derive(Debug)]
949pub struct ConnectionCounter {
950    current: AtomicUsize,
951    max: usize,
952}
953
954impl ConnectionCounter {
955    /// Create a new connection counter.
956    #[must_use]
957    pub fn new(max: usize) -> Self {
958        Self {
959            current: AtomicUsize::new(0),
960            max,
961        }
962    }
963
964    /// Try to acquire a connection slot (for use when counter is in Arc).
965    ///
966    /// Returns a guard that releases the slot when dropped, or None if at capacity.
967    /// The guard is `Send + 'static` and can be moved into spawned async tasks.
968    ///
969    /// The CAS loop is unbounded — under genuine contention the standard pattern
970    /// is to keep retrying until either the slot is acquired or capacity is hit;
971    /// progress is guaranteed because each iteration either advances `current`
972    /// (acquire success) or witnesses another thread's advance (their CAS won).
973    pub fn try_acquire_arc(self: &Arc<Self>) -> Option<ConnectionGuard> {
974        loop {
975            let current = self.current.load(Ordering::Relaxed);
976            if current >= self.max {
977                return None;
978            }
979            if self
980                .current
981                .compare_exchange(current, current + 1, Ordering::SeqCst, Ordering::Relaxed)
982                .is_ok()
983            {
984                return Some(ConnectionGuard {
985                    counter: Arc::clone(self),
986                });
987            }
988            // Hint to the CPU that we're spinning (avoids pipeline stalls).
989            std::hint::spin_loop();
990        }
991    }
992
993    /// Get current connection count.
994    #[must_use]
995    pub fn current(&self) -> usize {
996        self.current.load(Ordering::Relaxed)
997    }
998
999    /// Get maximum connections.
1000    #[must_use]
1001    pub fn max(&self) -> usize {
1002        self.max
1003    }
1004
1005    fn release(&self) {
1006        self.current.fetch_sub(1, Ordering::SeqCst);
1007    }
1008}
1009
1010/// Guard that releases a connection slot when dropped.
1011///
1012/// This guard is `Send + 'static` and can be safely moved into spawned async tasks.
1013#[derive(Debug)]
1014pub struct ConnectionGuard {
1015    counter: Arc<ConnectionCounter>,
1016}
1017
1018impl Drop for ConnectionGuard {
1019    fn drop(&mut self) {
1020        self.counter.release();
1021    }
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026    use super::*;
1027
1028    #[test]
1029    fn test_protocol_negotiation_exact_match() {
1030        let config = ProtocolConfig::default();
1031        assert_eq!(
1032            config.negotiate(Some("2025-11-25")),
1033            Some(ProtocolVersion::V2025_11_25)
1034        );
1035    }
1036
1037    #[test]
1038    fn test_protocol_negotiation_default_accepts_stable_versions() {
1039        // v3.1: default is multi-version (all stable versions). Older spec
1040        // versions are accepted; older clients route through version adapters.
1041        // Use ProtocolConfig::strict(LATEST) to restore exact-match behavior.
1042        let config = ProtocolConfig::default();
1043        assert_eq!(
1044            config.negotiate(Some("2025-06-18")),
1045            Some(ProtocolVersion::V2025_06_18)
1046        );
1047    }
1048
1049    /// `strict` speaks one version, and offers it to a client asking for
1050    /// another: refusing the handshake is not an answer the lifecycle spec
1051    /// permits.
1052    #[test]
1053    fn test_protocol_negotiation_strict_offers_its_version() {
1054        let config = ProtocolConfig::strict(ProtocolVersion::LATEST.clone());
1055        assert_eq!(
1056            config.negotiate(Some("2025-06-18")),
1057            Some(ProtocolVersion::LATEST)
1058        );
1059    }
1060
1061    #[test]
1062    fn test_protocol_negotiation_multi_version_accepts_older() {
1063        let config = ProtocolConfig::multi_version();
1064        assert_eq!(
1065            config.negotiate(Some("2025-06-18")),
1066            Some(ProtocolVersion::V2025_06_18)
1067        );
1068        assert_eq!(
1069            config.negotiate(Some("2025-11-25")),
1070            Some(ProtocolVersion::V2025_11_25)
1071        );
1072    }
1073
1074    #[test]
1075    fn test_protocol_negotiation_none_returns_preferred() {
1076        let config = ProtocolConfig::default();
1077        assert_eq!(config.negotiate(None), Some(ProtocolVersion::V2025_11_25));
1078    }
1079
1080    /// The lifecycle spec gives a server two choices for a version it does not
1081    /// support, and refusing the handshake is not one of them: it MUST answer
1082    /// with a version it does support and let the client decide.
1083    #[test]
1084    fn test_protocol_negotiation_unknown_version_falls_back() {
1085        let config = ProtocolConfig::default();
1086        assert_eq!(
1087            config.negotiate(Some("unknown-version")),
1088            Some(ProtocolVersion::V2025_11_25)
1089        );
1090    }
1091
1092    /// The real-world case: clients still pinned to retired spec revisions.
1093    /// Before 3.5.0 these could not connect to a TurboMCP server at all.
1094    #[test]
1095    fn test_protocol_negotiation_retired_versions_fall_back() {
1096        let config = ProtocolConfig::default();
1097        for retired in ["2024-11-05", "2025-03-26"] {
1098            assert_eq!(
1099                config.negotiate(Some(retired)),
1100                Some(ProtocolVersion::V2025_11_25),
1101                "{retired} should be offered the latest supported version"
1102            );
1103        }
1104    }
1105
1106    /// `strict` narrows what is spoken, not whether the handshake is
1107    /// answered; only `allow_fallback: false` refuses.
1108    #[test]
1109    fn test_protocol_negotiation_strict() {
1110        let config = ProtocolConfig::strict("2025-11-25");
1111        assert!(!config.is_supported(&ProtocolVersion::V2025_06_18));
1112
1113        let refusing = ProtocolConfig {
1114            allow_fallback: false,
1115            ..config
1116        };
1117        assert_eq!(refusing.negotiate(Some("2025-06-18")), None);
1118    }
1119
1120    #[test]
1121    fn test_capability_validation() {
1122        let required = RequiredCapabilities::none().with_roots();
1123        let client = ClientCapabilities {
1124            roots: true,
1125            ..Default::default()
1126        };
1127        assert!(required.validate(&client).is_valid());
1128
1129        let client_missing = ClientCapabilities::default();
1130        assert!(!required.validate(&client_missing).is_valid());
1131    }
1132
1133    #[test]
1134    fn test_extension_capability_validation() {
1135        let required = RequiredCapabilities::none().with_extension("trace");
1136        let client = ClientCapabilities {
1137            extensions: ["trace".to_string()].into_iter().collect(),
1138            ..Default::default()
1139        };
1140        assert!(required.validate(&client).is_valid());
1141
1142        let missing = ClientCapabilities::default();
1143        let validation = required.validate(&missing);
1144        assert!(!validation.is_valid());
1145        assert_eq!(
1146            validation.missing(),
1147            Some(&["extensions/trace".to_string()][..])
1148        );
1149    }
1150
1151    #[test]
1152    fn test_client_capabilities_parse_extensions() {
1153        let params = serde_json::json!({
1154            "capabilities": {
1155                "extensions": {
1156                    "trace": {"version": "1"},
1157                    "handoff": {}
1158                }
1159            }
1160        });
1161
1162        let caps = ClientCapabilities::from_params(&params);
1163        assert!(caps.extensions.contains("trace"));
1164        assert!(caps.extensions.contains("handoff"));
1165    }
1166
1167    #[test]
1168    fn test_rate_limiter() {
1169        let config = RateLimitConfig::new(2, Duration::from_secs(1));
1170        let limiter = RateLimiter::new(config);
1171
1172        assert!(limiter.check(None));
1173        assert!(limiter.check(None));
1174        assert!(!limiter.check(None)); // Should be rate limited
1175    }
1176
1177    #[test]
1178    fn test_connection_counter() {
1179        let counter = Arc::new(ConnectionCounter::new(2));
1180
1181        let guard1 = counter.try_acquire_arc();
1182        assert!(guard1.is_some());
1183        assert_eq!(counter.current(), 1);
1184
1185        let guard2 = counter.try_acquire_arc();
1186        assert!(guard2.is_some());
1187        assert_eq!(counter.current(), 2);
1188
1189        let guard3 = counter.try_acquire_arc();
1190        assert!(guard3.is_none()); // At capacity
1191
1192        drop(guard1);
1193        assert_eq!(counter.current(), 1);
1194
1195        let guard4 = counter.try_acquire_arc();
1196        assert!(guard4.is_some());
1197    }
1198
1199    // =========================================================================
1200    // Builder validation tests
1201    // =========================================================================
1202
1203    #[test]
1204    fn test_builder_default_succeeds() {
1205        // Default configuration should always succeed
1206        let config = ServerConfig::builder().build();
1207        assert_eq!(config.max_message_size, DEFAULT_MAX_MESSAGE_SIZE);
1208        assert!(config.origin_validation.allow_localhost);
1209        assert!(config.origin_validation.allowed_origins.is_empty());
1210    }
1211
1212    #[test]
1213    fn test_builder_origin_validation_overrides() {
1214        let config = ServerConfig::builder()
1215            .allow_origin("https://app.example.com")
1216            .allow_localhost_origins(false)
1217            .build();
1218
1219        assert!(!config.origin_validation.allow_localhost);
1220        assert!(
1221            config
1222                .origin_validation
1223                .allowed_origins
1224                .contains("https://app.example.com")
1225        );
1226    }
1227
1228    #[test]
1229    fn test_builder_try_build_valid() {
1230        let result = ServerConfig::builder()
1231            .max_message_size(1024 * 1024)
1232            .try_build();
1233        assert!(result.is_ok());
1234    }
1235
1236    #[test]
1237    fn test_builder_try_build_invalid_message_size() {
1238        let result = ServerConfig::builder()
1239            .max_message_size(100) // Below minimum
1240            .try_build();
1241        assert!(result.is_err());
1242        assert!(matches!(
1243            result.unwrap_err(),
1244            ConfigValidationError::InvalidMessageSize { .. }
1245        ));
1246    }
1247
1248    #[test]
1249    fn test_builder_try_build_invalid_rate_limit() {
1250        let result = ServerConfig::builder()
1251            .rate_limit(RateLimitConfig {
1252                max_requests: 0, // Invalid
1253                window: Duration::from_secs(1),
1254                per_client: true,
1255            })
1256            .try_build();
1257        assert!(result.is_err());
1258        assert!(matches!(
1259            result.unwrap_err(),
1260            ConfigValidationError::InvalidRateLimit { .. }
1261        ));
1262    }
1263
1264    #[test]
1265    fn test_builder_try_build_zero_window() {
1266        let result = ServerConfig::builder()
1267            .rate_limit(RateLimitConfig {
1268                max_requests: 100,
1269                window: Duration::ZERO, // Invalid
1270                per_client: true,
1271            })
1272            .try_build();
1273        assert!(result.is_err());
1274        assert!(matches!(
1275            result.unwrap_err(),
1276            ConfigValidationError::InvalidRateLimit { .. }
1277        ));
1278    }
1279
1280    #[test]
1281    fn test_builder_try_build_invalid_connection_limits() {
1282        let result = ServerConfig::builder()
1283            .connection_limits(ConnectionLimits {
1284                max_tcp_connections: 0,
1285                max_websocket_connections: 0,
1286                max_http_concurrent: 0,
1287                max_unix_connections: 0,
1288            })
1289            .try_build();
1290        assert!(result.is_err());
1291        assert!(matches!(
1292            result.unwrap_err(),
1293            ConfigValidationError::InvalidConnectionLimits { .. }
1294        ));
1295    }
1296}
1297
1298#[cfg(test)]
1299mod proptest_tests {
1300    use super::*;
1301    use proptest::prelude::*;
1302
1303    proptest! {
1304        #[test]
1305        fn config_builder_never_panics(
1306            max_msg_size in 0usize..10_000_000,
1307        ) {
1308            // Builder should never panic, just return errors for invalid inputs
1309            let _ = ServerConfig::builder()
1310                .max_message_size(max_msg_size)
1311                .try_build();
1312        }
1313
1314        #[test]
1315        fn connection_counter_bounded(max in 1usize..10000) {
1316            let counter = Arc::new(ConnectionCounter::new(max));
1317            let mut guards = Vec::new();
1318            // Should never acquire more than max
1319            for _ in 0..max + 10 {
1320                if let Some(guard) = counter.try_acquire_arc() {
1321                    guards.push(guard);
1322                }
1323            }
1324            assert_eq!(guards.len(), max);
1325            assert_eq!(counter.current(), max);
1326        }
1327    }
1328}