Skip to main content

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}