Skip to main content

crates_docs/server/
transport.rs

1//! Transport module
2//!
3//! Provides Stdio, HTTP and SSE transport protocol support.
4//!
5//! # Supported Transport Modes
6//!
7//! - **Stdio**: Standard input/output, suitable for MCP client integration
8//! - **HTTP**: Streamable HTTP, supports stateless requests
9//! - **SSE**: Server-Sent Events, supports server push
10//! - **Hybrid**: Hybrid mode, supports both HTTP and SSE
11//!
12//! # Example
13//!
14//! ```rust,no_run
15//! use crates_docs::server::transport::{run_stdio_server, TransportMode};
16//! use crates_docs::{AppConfig, CratesDocsServer};
17//!
18//! #[tokio::main]
19//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
20//!     let config = AppConfig::default();
21//!     let server = CratesDocsServer::new(config)?;
22//!
23//!     // Run Stdio server
24//!     run_stdio_server(&server).await?;
25//!
26//!     Ok(())
27//! }
28//! ```
29
30use crate::error::Result;
31use crate::server::handler::CratesDocsHandler;
32use crate::server::CratesDocsServer;
33use rust_mcp_sdk::{
34    error::McpSdkError,
35    event_store,
36    mcp_server::{hyper_server, server_runtime, HyperServerOptions, McpServerOptions},
37    McpServer, StdioTransport, ToMcpServerHandler, TransportOptions,
38};
39use std::sync::Arc;
40
41/// Run Stdio server
42///
43/// Communicates with MCP clients via standard input/output.
44///
45/// # Arguments
46///
47/// * `server` - `CratesDocsServer` instance
48///
49/// # Errors
50///
51/// Returns error if server startup fails
52///
53/// # Example
54///
55/// ```rust,no_run
56/// use crates_docs::server::transport::run_stdio_server;
57/// use crates_docs::{AppConfig, CratesDocsServer};
58///
59/// #[tokio::main]
60/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
61///     let config = AppConfig::default();
62///     let server = CratesDocsServer::new(config)?;
63///     run_stdio_server(&server).await?;
64///     Ok(())
65/// }
66/// ```
67pub async fn run_stdio_server(server: &CratesDocsServer) -> Result<()> {
68    tracing::info!("Starting Stdio MCP server...");
69
70    let server_info = server.server_info();
71    let handler = CratesDocsHandler::new(Arc::new(server.clone()));
72
73    // Create Stdio transport
74    let transport = StdioTransport::new(TransportOptions::default())
75        .map_err(|e| crate::error::Error::mcp("transport", e.to_string()))?;
76
77    // Create MCP server
78    let mcp_server: Arc<rust_mcp_sdk::mcp_server::ServerRuntime> =
79        server_runtime::create_server(McpServerOptions {
80            server_details: server_info,
81            transport,
82            handler: handler.to_mcp_server_handler(),
83            task_store: None,
84            client_task_store: None,
85            message_observer: None,
86        });
87
88    tracing::info!("Stdio MCP server started, waiting for connections...");
89    mcp_server
90        .start()
91        .await
92        .map_err(|e: McpSdkError| crate::error::Error::mcp("server_start", e.to_string()))?;
93
94    Ok(())
95}
96
97/// Hyper server configuration
98///
99/// Configuration for HTTP/SSE/Hybrid MCP servers using the Builder pattern.
100///
101/// # Example
102///
103/// ```rust
104/// use crates_docs::server::transport::HyperServerConfig;
105///
106/// let http_config = HyperServerConfig::http();
107/// let sse_config = HyperServerConfig::sse();
108/// let hybrid_config = HyperServerConfig::hybrid();
109/// ```
110#[derive(Debug, Clone)]
111pub struct HyperServerConfig {
112    /// Protocol name for logging (e.g., "HTTP", "SSE", "Hybrid")
113    protocol_name: String,
114    /// Whether SSE support is enabled
115    sse_support: bool,
116}
117
118impl HyperServerConfig {
119    /// Create HTTP server configuration
120    ///
121    /// HTTP mode supports Streamable HTTP protocol for stateless requests.
122    #[must_use]
123    pub fn http() -> Self {
124        Self {
125            protocol_name: "HTTP".to_string(),
126            sse_support: false,
127        }
128    }
129
130    /// Create SSE server configuration
131    ///
132    /// SSE mode supports Server-Sent Events for server push capabilities.
133    #[must_use]
134    pub fn sse() -> Self {
135        Self {
136            protocol_name: "SSE".to_string(),
137            sse_support: true,
138        }
139    }
140
141    /// Create Hybrid server configuration
142    ///
143    /// Hybrid mode supports both HTTP and SSE protocols.
144    #[must_use]
145    pub fn hybrid() -> Self {
146        Self {
147            protocol_name: "Hybrid".to_string(),
148            sse_support: true,
149        }
150    }
151
152    /// Get protocol name
153    #[must_use]
154    pub fn protocol_name(&self) -> &str {
155        &self.protocol_name
156    }
157
158    /// Check if SSE support is enabled
159    #[must_use]
160    pub fn sse_support(&self) -> bool {
161        self.sse_support
162    }
163}
164
165/// Run a Hyper-based MCP server with the given configuration.
166///
167/// This function handles HTTP, SSE, and Hybrid servers based on the configuration.
168///
169/// # Arguments
170///
171/// * `server` - `CratesDocsServer` instance
172/// * `config` - `HyperServerConfig` instance
173///
174/// # Errors
175///
176/// Returns error if server startup fails
177///
178/// # Example
179///
180/// ```rust,no_run
181/// use crates_docs::server::transport::{run_hyper_server, HyperServerConfig};
182/// use crates_docs::{AppConfig, CratesDocsServer};
183///
184/// #[tokio::main]
185/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
186///     let config = AppConfig::default();
187///     let server = CratesDocsServer::new(config)?;
188///     let http_config = HyperServerConfig::http();
189///     run_hyper_server(&server, http_config).await?;
190///     Ok(())
191/// }
192/// ```
193pub async fn run_hyper_server(server: &CratesDocsServer, config: HyperServerConfig) -> Result<()> {
194    let server_config = server.config();
195    let server_info = server.server_info();
196    let handler = CratesDocsHandler::new(Arc::new(server.clone()));
197
198    tracing::info!(
199        "Starting {} MCP server on {}:{}...",
200        config.protocol_name(),
201        server_config.server.host,
202        server_config.server.port
203    );
204
205    // Create Hyper server options with security settings from config
206    let options = HyperServerOptions {
207        host: server_config.server.host.clone(),
208        port: server_config.server.port,
209        transport_options: Arc::new(TransportOptions::default()),
210        sse_support: config.sse_support(),
211        event_store: Some(Arc::new(event_store::InMemoryEventStore::default())),
212        task_store: None,
213        client_task_store: None,
214        allowed_hosts: Some(server_config.server.allowed_hosts.clone()),
215        allowed_origins: Some(server_config.server.allowed_origins.clone()),
216        health_endpoint: Some("/health".to_string()),
217        ..Default::default()
218    };
219
220    // Create HTTP/SSE/Hybrid server
221    let mcp_server =
222        hyper_server::create_server(server_info, handler.to_mcp_server_handler(), options);
223
224    // Build the started message based on the protocol
225    let started_msg = if config.sse_support() && config.protocol_name() != "SSE" {
226        // Hybrid mode
227        format!(
228            "{} MCP server started, listening on {}:{} (HTTP + SSE)",
229            config.protocol_name(),
230            server_config.server.host,
231            server_config.server.port
232        )
233    } else {
234        format!(
235            "{} MCP server started, listening on {}:{}",
236            config.protocol_name(),
237            server_config.server.host,
238            server_config.server.port
239        )
240    };
241    tracing::info!("{}", started_msg);
242
243    mcp_server
244        .start()
245        .await
246        .map_err(|e: McpSdkError| crate::error::Error::mcp("server_start", e.to_string()))?;
247
248    Ok(())
249}
250
251/// Transport mode
252///
253/// Defines the transport protocol types supported by MCP server.
254///
255/// # Variants
256///
257/// - `Stdio`: Standard input/output, suitable for MCP client integration
258/// - `Http`: Streamable HTTP, supports stateless requests
259/// - `Sse`: Server-Sent Events, supports server push
260/// - `Hybrid`: Hybrid mode, supports both HTTP and SSE
261///
262/// # Example
263///
264/// ```rust
265/// use crates_docs::server::transport::TransportMode;
266/// use std::str::FromStr;
267///
268/// let mode = TransportMode::from_str("http").unwrap();
269/// assert_eq!(mode, TransportMode::Http);
270/// assert_eq!(mode.to_string(), "http");
271/// ```
272#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
273pub enum TransportMode {
274    /// Stdio transport (for CLI integration)
275    Stdio,
276    /// HTTP transport (Streamable HTTP)
277    Http,
278    /// SSE transport (Server-Sent Events)
279    Sse,
280    /// Hybrid mode (supports both HTTP and SSE)
281    Hybrid,
282}
283
284impl std::str::FromStr for TransportMode {
285    type Err = String;
286
287    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
288        match s.to_lowercase().as_str() {
289            "stdio" => Ok(TransportMode::Stdio),
290            "http" => Ok(TransportMode::Http),
291            "sse" => Ok(TransportMode::Sse),
292            "hybrid" => Ok(TransportMode::Hybrid),
293            _ => Err(format!("Unknown transport mode: {s}")),
294        }
295    }
296}
297
298impl std::fmt::Display for TransportMode {
299    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300        match self {
301            TransportMode::Stdio => write!(f, "stdio"),
302            TransportMode::Http => write!(f, "http"),
303            TransportMode::Sse => write!(f, "sse"),
304            TransportMode::Hybrid => write!(f, "hybrid"),
305        }
306    }
307}
308
309impl TransportMode {
310    /// Convert to `HyperServerConfig`
311    #[must_use]
312    pub fn to_hyper_config(&self) -> Option<HyperServerConfig> {
313        match self {
314            TransportMode::Stdio => None,
315            TransportMode::Http => Some(HyperServerConfig::http()),
316            TransportMode::Sse => Some(HyperServerConfig::sse()),
317            TransportMode::Hybrid => Some(HyperServerConfig::hybrid()),
318        }
319    }
320}
321
322/// Run server with the specified transport mode
323pub async fn run_server_with_mode(server: &CratesDocsServer, mode: TransportMode) -> Result<()> {
324    match mode {
325        TransportMode::Stdio => run_stdio_server(server).await,
326        TransportMode::Http | TransportMode::Sse | TransportMode::Hybrid => {
327            let config = mode
328                .to_hyper_config()
329                .expect("Hyper config should exist for HTTP/SSE/Hybrid");
330            run_hyper_server(server, config).await
331        }
332    }
333}