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}