Skip to main content

rust_mcp_actix/
options.rs

1use rust_mcp_sdk::auth::AuthProvider;
2use rust_mcp_sdk::event_store::EventStore;
3use rust_mcp_sdk::id_generator::IdGenerator;
4use rust_mcp_sdk::mcp_http::DnsRebindingOptions;
5use rust_mcp_sdk::mcp_http::HealthHandler;
6use rust_mcp_sdk::mcp_http::McpMountOptions;
7use rust_mcp_sdk::mcp_http::{
8    DEFAULT_MAX_REQUEST_BODY_SIZE, DEFAULT_MESSAGES_ENDPOINT, DEFAULT_SSE_ENDPOINT,
9    DEFAULT_STREAMABLE_HTTP_ENDPOINT,
10};
11use rust_mcp_sdk::schema::schema_utils::{ClientMessage, ServerMessage};
12use rust_mcp_sdk::session_store::SessionStore;
13use rust_mcp_sdk::task_store::{ClientTaskStore, ServerTaskStore};
14use rust_mcp_sdk::McpObserver;
15use rust_mcp_sdk::SessionId;
16use rust_mcp_sdk::TransportOptions;
17use std::net::{SocketAddr, ToSocketAddrs};
18use std::sync::Arc;
19use std::time::Duration;
20
21const DEFAULT_CLIENT_PING_INTERVAL: Duration = Duration::from_secs(12);
22
23/// Configuration for the Actix MCP server.
24///
25/// Used to configure the turnkey server created via
26/// [`create_actix_server()`](crate::create_actix_server).
27pub struct ActixServerOptions {
28    /// Hostname or IP address the server will bind to (default: `"127.0.0.1"`)
29    pub host: String,
30    /// TCP port (default: `8080`)
31    pub port: u16,
32    /// Optional session ID generator
33    pub session_id_generator: Option<Arc<dyn IdGenerator<SessionId>>>,
34    /// Custom Streamable HTTP endpoint path (default: `/mcp`)
35    pub custom_streamable_http_endpoint: Option<String>,
36    /// Shared transport configuration
37    pub transport_options: Arc<TransportOptions>,
38    /// Event store for resumability support
39    pub event_store: Option<Arc<dyn EventStore>>,
40    /// Task store for server-side tasks
41    pub task_store: Option<Arc<ServerTaskStore>>,
42    /// Task store for client-side tasks
43    pub client_task_store: Option<Arc<ClientTaskStore>>,
44    /// If true, return JSON instead of SSE stream
45    pub enable_json_response: Option<bool>,
46    /// Interval between keep-alive pings
47    pub ping_interval: Duration,
48    /// Enable SSE transport support (default: true)
49    pub sse_support: bool,
50    /// Custom SSE endpoint path (default: `/sse`)
51    pub custom_sse_endpoint: Option<String>,
52    /// Custom SSE messages endpoint path (default: `/messages`)
53    pub custom_messages_endpoint: Option<String>,
54    /// Optional authentication provider
55    pub auth: Option<Arc<dyn AuthProvider>>,
56    /// Health check endpoint path (None disables)
57    pub health_endpoint: Option<String>,
58    /// Custom health check handler
59    pub health_handler: Option<Arc<dyn HealthHandler>>,
60    /// Optional message observer for telemetry
61    pub message_observer: Option<Arc<dyn McpObserver<ClientMessage, ServerMessage>>>,
62    /// Maximum request body size in bytes. Defaults to 4 MiB when None.
63    pub max_request_body_size: Option<usize>,
64    /// DNS rebinding protection configuration (enabled by default).
65    ///
66    /// When `dns_rebinding_protection` is `true` and no `allowed_hosts` or
67    /// `allowed_origins` are configured, `allowed_hosts` is auto-derived from
68    /// `host:port` unless the bind address is a wildcard.
69    pub dns_rebinding: DnsRebindingOptions,
70    /// Optional session store implementation. Defaults to a bounded
71    /// `InMemorySessionStore` (10k max sessions, no idle TTL) when `None`.
72    /// Pass your own [`SessionStore`] implementation to use Redis, custom
73    /// limits, or any other session backend.
74    pub session_store: Option<Arc<dyn SessionStore>>,
75    /// Enable TLS/SSL (requires `ssl` feature, default: false)
76    pub enable_ssl: bool,
77    /// Path to TLS certificate PEM file
78    pub ssl_cert_path: Option<String>,
79    /// Path to TLS private key PEM file
80    pub ssl_key_path: Option<String>,
81}
82
83impl ActixServerOptions {
84    /// Validates the server configuration.
85    pub fn validate(&self) -> Result<(), String> {
86        if self.host.is_empty() {
87            return Err("host must not be empty".into());
88        }
89        if self.enable_ssl && (self.ssl_cert_path.is_none() || self.ssl_key_path.is_none()) {
90            return Err(
91                "Both 'ssl_cert_path' and 'ssl_key_path' must be provided when SSL is enabled."
92                    .into(),
93            );
94        }
95        Ok(())
96    }
97
98    /// Resolves the `SocketAddr` from host and port.
99    pub fn resolve_server_address(&self) -> Result<SocketAddr, String> {
100        self.validate()?;
101
102        let host = self
103            .host
104            .strip_prefix("http://")
105            .or_else(|| self.host.strip_prefix("https://"))
106            .unwrap_or(&self.host)
107            .to_string();
108
109        let mut iter = (host.as_str(), self.port)
110            .to_socket_addrs()
111            .map_err(|e| format!("Failed to resolve address: {}", e))?;
112
113        iter.next()
114            .ok_or_else(|| format!("Could not resolve: {}:{}", self.host, self.port))
115    }
116
117    pub fn base_url(&self) -> String {
118        format!(
119            "{}://{}:{}",
120            if self.enable_ssl { "https" } else { "http" },
121            self.host,
122            self.port
123        )
124    }
125
126    pub fn streamable_http_url(&self) -> String {
127        format!("{}{}", self.base_url(), self.streamable_http_endpoint())
128    }
129
130    pub fn sse_url(&self) -> String {
131        format!("{}{}", self.base_url(), self.sse_endpoint())
132    }
133
134    pub fn sse_message_url(&self) -> String {
135        format!("{}{}", self.base_url(), self.sse_messages_endpoint())
136    }
137
138    pub fn sse_endpoint(&self) -> &str {
139        self.custom_sse_endpoint
140            .as_deref()
141            .unwrap_or(DEFAULT_SSE_ENDPOINT)
142    }
143
144    pub fn sse_messages_endpoint(&self) -> &str {
145        self.custom_messages_endpoint
146            .as_deref()
147            .unwrap_or(DEFAULT_MESSAGES_ENDPOINT)
148    }
149
150    pub fn streamable_http_endpoint(&self) -> &str {
151        self.custom_streamable_http_endpoint
152            .as_deref()
153            .unwrap_or(DEFAULT_STREAMABLE_HTTP_ENDPOINT)
154    }
155
156    /// Maximum incoming HTTP request body size in bytes, falling back to the
157    /// default (4 MiB) when not configured.
158    pub fn max_request_body_size(&self) -> usize {
159        self.max_request_body_size
160            .unwrap_or(DEFAULT_MAX_REQUEST_BODY_SIZE)
161    }
162
163    /// Resolves mount options from this server configuration.
164    pub fn resolve_mount_options(&self) -> McpMountOptions {
165        McpMountOptions {
166            streamable_http_endpoint: self.streamable_http_endpoint().to_string(),
167            sse_endpoint: self.sse_endpoint().to_string(),
168            sse_messages_endpoint: self.sse_messages_endpoint().to_string(),
169            health_endpoint: self.health_endpoint.clone(),
170            max_request_body_size: self.max_request_body_size(),
171        }
172    }
173}
174
175impl Default for ActixServerOptions {
176    fn default() -> Self {
177        Self {
178            host: "127.0.0.1".into(),
179            port: 8080,
180            session_id_generator: None,
181            custom_streamable_http_endpoint: None,
182            transport_options: Default::default(),
183            event_store: None,
184            task_store: None,
185            client_task_store: None,
186            enable_json_response: None,
187            ping_interval: DEFAULT_CLIENT_PING_INTERVAL,
188            sse_support: true,
189            custom_sse_endpoint: None,
190            custom_messages_endpoint: None,
191            auth: None,
192            health_endpoint: None,
193            health_handler: None,
194            message_observer: None,
195            max_request_body_size: None,
196            dns_rebinding: DnsRebindingOptions::default(),
197            session_store: None,
198            enable_ssl: false,
199            ssl_cert_path: None,
200            ssl_key_path: None,
201        }
202    }
203}