crates_docs/server/mod.rs
1//! Server module
2//!
3//! Provides MCP server implementation with multiple transport protocols (stdio, HTTP, SSE, Hybrid).
4//!
5//! # Main Components
6//!
7//! - `CratesDocsServer`: Main server struct
8//! - `handler`: MCP request handling
9//! - `transport`: Transport layer implementation
10//! - `auth`: OAuth authentication support
11//!
12//! # Handler Design
13//!
14//! Single-layer architecture with all handling logic directly in `CratesDocsHandler`:
15//! - `CratesDocsHandler`: Implements MCP protocol handler interface
16//! - `HandlerConfig`: Configuration class, supports merge operation
17//!
18//! # Example
19//!
20//! ```rust,no_run
21//! use crates_docs::{AppConfig, CratesDocsServer};
22//! use crates_docs::server::handler::{CratesDocsHandler, HandlerConfig};
23//! use std::sync::Arc;
24//!
25//! #[tokio::main]
26//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
27//! let config = AppConfig::default();
28//! let server = Arc::new(CratesDocsServer::new(config)?);
29//!
30//! // Create handler with merged config
31//! let base_config = HandlerConfig::default();
32//! let override_config = HandlerConfig::new().with_verbose_logging();
33//! let handler = CratesDocsHandler::with_merged_config(
34//! server,
35//! base_config,
36//! Some(override_config)
37//! );
38//!
39//! // Run HTTP server
40//! let http_config = crates_docs::server::transport::HyperServerConfig::http();
41//! crates_docs::server::transport::run_hyper_server(&handler.server(), http_config).await?;
42//!
43//! Ok(())
44//! }
45//! ```
46
47pub mod auth;
48pub mod auth_middleware;
49pub mod handler;
50pub mod transport;
51
52use crate::cache::Cache;
53use crate::config::AppConfig;
54use crate::error::Result;
55use crate::tools::ToolRegistry;
56use rust_mcp_sdk::schema::{
57 Implementation, InitializeResult, ProtocolVersion, ServerCapabilities, ServerCapabilitiesTools,
58};
59use std::sync::Arc;
60
61/// Re-export `ServerConfig` from config module for backward compatibility
62pub use crate::config::ServerConfig;
63
64/// Re-export `CratesDocsHandler` from handler module
65pub use handler::CratesDocsHandler;
66
67/// Re-export `HyperServerConfig` from transport module
68pub use transport::HyperServerConfig;
69
70/// Crates Docs MCP Server
71///
72/// Main server struct, managing configuration, tool registry, and cache.
73/// Supports multiple transport protocols: stdio, HTTP, SSE, Hybrid.
74///
75/// # Fields
76///
77/// - `config`: Application configuration
78/// - `tool_registry`: Tool registry
79/// - `cache`: Cache instance
80#[derive(Clone)]
81pub struct CratesDocsServer {
82 config: AppConfig,
83 tool_registry: Arc<ToolRegistry>,
84 cache: Arc<dyn Cache>,
85}
86
87impl CratesDocsServer {
88 /// Create server from components (internal initialization logic)
89 ///
90 /// # Arguments
91 ///
92 /// * `config` - Application configuration
93 /// * `cache` - Cache instance
94 ///
95 /// # Errors
96 ///
97 /// Returns error if document service creation fails
98 fn from_parts(config: AppConfig, cache: Arc<dyn Cache>) -> crate::error::Result<Self> {
99 // Initialize global HTTP client with performance config for connection pool reuse
100 // This ensures all HTTP requests share the same connection pool
101 // Note: init_global_http_client will fail if already initialized, which is fine
102 let _ = crate::utils::init_global_http_client(&config.performance);
103
104 // Create document service with cache configuration
105 let doc_service = Arc::new(crate::tools::docs::DocService::with_config(
106 cache.clone(),
107 &config.cache,
108 )?);
109
110 // Create tool registry
111 let tool_registry = Arc::new(crate::tools::create_default_registry(&doc_service));
112
113 Ok(Self {
114 config,
115 tool_registry,
116 cache,
117 })
118 }
119
120 /// Create new server instance (synchronous)
121 ///
122 /// # Arguments
123 ///
124 /// * `config` - Application configuration
125 ///
126 /// # Errors
127 ///
128 /// Returns error if cache creation fails
129 ///
130 /// # Note
131 ///
132 /// This method only supports memory cache. For Redis, use [`new_async`](Self::new_async).
133 ///
134 /// # Example
135 ///
136 /// ```rust,no_run
137 /// use crates_docs::{AppConfig, CratesDocsServer};
138 ///
139 /// let config = AppConfig::default();
140 /// let server = CratesDocsServer::new(config).expect("Failed to create server");
141 /// ```
142 pub fn new(config: AppConfig) -> Result<Self> {
143 let cache_box: Box<dyn Cache> = crate::cache::create_cache(&config.cache)?;
144 let cache: Arc<dyn Cache> = Arc::from(cache_box);
145 Self::from_parts(config, cache)
146 }
147
148 /// Create new server instance (async)
149 ///
150 /// # Arguments
151 ///
152 /// * `config` - Application configuration
153 ///
154 /// # Errors
155 ///
156 /// Returns error if cache creation fails
157 ///
158 /// # Note
159 ///
160 /// Supports memory cache and Redis cache (requires `cache-redis` feature).
161 ///
162 /// # Example
163 ///
164 /// ```rust,no_run
165 /// use crates_docs::{AppConfig, CratesDocsServer};
166 ///
167 /// #[tokio::main]
168 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
169 /// let config = AppConfig::default();
170 /// let server = CratesDocsServer::new_async(config).await?;
171 /// Ok(())
172 /// }
173 /// ```
174 #[allow(unused_variables)]
175 #[allow(clippy::unused_async)]
176 pub async fn new_async(config: AppConfig) -> Result<Self> {
177 // Decide which creation method to use based on cache type and feature
178 #[cfg(feature = "cache-redis")]
179 {
180 let cache_box: Box<dyn Cache> = crate::cache::create_cache_async(&config.cache).await?;
181 let cache: Arc<dyn Cache> = Arc::from(cache_box);
182 Self::from_parts(config, cache)
183 }
184
185 #[cfg(not(feature = "cache-redis"))]
186 {
187 // No cache-redis feature, fall back to synchronous creation
188 let cache_box: Box<dyn Cache> = crate::cache::create_cache(&config.cache)?;
189 let cache: Arc<dyn Cache> = Arc::from(cache_box);
190 Self::from_parts(config, cache)
191 }
192 }
193
194 /// Get server configuration
195 #[must_use]
196 pub fn config(&self) -> &AppConfig {
197 &self.config
198 }
199
200 /// Get tool registry
201 #[must_use]
202 pub fn tool_registry(&self) -> &Arc<ToolRegistry> {
203 &self.tool_registry
204 }
205
206 /// Get cache instance
207 #[must_use]
208 pub fn cache(&self) -> &Arc<dyn Cache> {
209 &self.cache
210 }
211
212 /// Get server info
213 ///
214 /// Returns MCP initialization result with server metadata and capabilities
215 #[must_use]
216 pub fn server_info(&self) -> InitializeResult {
217 InitializeResult {
218 server_info: Implementation {
219 name: self.config.server.name.clone(),
220 version: self.config.server.version.clone(),
221 title: Some("Crates Docs MCP Server".to_string()),
222 description: self.config.server.description.clone(),
223 icons: self.config.server.icons.clone(),
224 website_url: self.config.server.website_url.clone(),
225 },
226 capabilities: ServerCapabilities {
227 tools: Some(ServerCapabilitiesTools { list_changed: None }),
228 resources: None,
229 prompts: None,
230 experimental: None,
231 completions: None,
232 logging: None,
233 tasks: None,
234 },
235 protocol_version: ProtocolVersion::V2025_11_25.into(),
236 instructions: Some(
237 "Use this server to query Rust crate documentation. Supports crate lookup, crate search, and health check."
238 .to_string(),
239 ),
240 meta: None,
241 }
242 }
243
244 /// Run Stdio server
245 ///
246 /// # Errors
247 ///
248 /// Returns error if server startup fails
249 pub async fn run_stdio(&self) -> Result<()> {
250 transport::run_stdio_server(self).await
251 }
252
253 /// Run HTTP server
254 ///
255 /// # Errors
256 ///
257 /// Returns error if server startup fails
258 pub async fn run_http(&self) -> Result<()> {
259 transport::run_hyper_server(self, transport::HyperServerConfig::http()).await
260 }
261
262 /// Run SSE server
263 ///
264 /// # Errors
265 ///
266 /// Returns error if server startup fails
267 pub async fn run_sse(&self) -> Result<()> {
268 transport::run_hyper_server(self, transport::HyperServerConfig::sse()).await
269 }
270}