1pub mod auth;
6pub mod handler;
7pub mod transport;
8
9use crate::cache::Cache;
10use crate::error::Result;
11use crate::tools::ToolRegistry;
12use rust_mcp_sdk::schema::{
13 Icon, IconTheme, Implementation, InitializeResult, ProtocolVersion, ServerCapabilities,
14 ServerCapabilitiesTools,
15};
16use std::sync::Arc;
17
18#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
20pub struct ServerConfig {
21 pub name: String,
23
24 pub version: String,
26
27 pub description: Option<String>,
29
30 pub icons: Vec<Icon>,
32
33 pub website_url: Option<String>,
35
36 pub host: String,
38
39 pub port: u16,
41
42 pub transport_mode: String,
44
45 pub enable_sse: bool,
47
48 pub enable_oauth: bool,
50
51 pub max_connections: usize,
53
54 pub request_timeout_secs: u64,
56
57 pub response_timeout_secs: u64,
59
60 pub allowed_hosts: Vec<String>,
62
63 pub allowed_origins: Vec<String>,
66
67 pub cache: crate::cache::CacheConfig,
69
70 pub oauth: crate::server::auth::OAuthConfig,
72
73 pub logging: crate::config::LoggingConfig,
75
76 pub performance: crate::config::PerformanceConfig,
78}
79
80impl Default for ServerConfig {
81 fn default() -> Self {
82 Self {
83 name: "crates-docs".to_string(),
84 version: crate::VERSION.to_string(),
85 description: Some(
86 "High-performance Rust crate documentation query MCP server".to_string(),
87 ),
88 icons: vec![
89 Icon {
90 src: "https://docs.rs/static/favicon-32x32.png".to_string(),
91 mime_type: Some("image/png".to_string()),
92 sizes: vec!["32x32".to_string()],
93 theme: Some(IconTheme::Light),
94 },
95 Icon {
96 src: "https://docs.rs/static/favicon-32x32.png".to_string(),
97 mime_type: Some("image/png".to_string()),
98 sizes: vec!["32x32".to_string()],
99 theme: Some(IconTheme::Dark),
100 },
101 ],
102 website_url: Some("https://github.com/KingingWang/crates-docs".to_string()),
103 host: "127.0.0.1".to_string(),
104 port: 8080,
105 transport_mode: "hybrid".to_string(),
106 enable_sse: true,
107 enable_oauth: false,
108 max_connections: 100,
109 request_timeout_secs: 30,
110 response_timeout_secs: 60,
111 allowed_hosts: vec!["localhost".to_string(), "127.0.0.1".to_string()],
113 allowed_origins: vec!["http://localhost:*".to_string()],
114 cache: crate::cache::CacheConfig::default(),
115 oauth: crate::server::auth::OAuthConfig::default(),
116 logging: crate::config::LoggingConfig::default(),
117 performance: crate::config::PerformanceConfig::default(),
118 }
119 }
120}
121
122#[derive(Clone)]
124pub struct CratesDocsServer {
125 config: ServerConfig,
126 tool_registry: Arc<ToolRegistry>,
127 cache: Arc<dyn Cache>,
128}
129
130impl CratesDocsServer {
131 pub fn new(config: ServerConfig) -> Result<Self> {
135 let cache_box: Box<dyn Cache> = crate::cache::create_cache(&config.cache)?;
136 let cache: Arc<dyn Cache> = Arc::from(cache_box);
137
138 let doc_service = Arc::new(crate::tools::docs::DocService::new(cache.clone()));
140
141 let tool_registry = Arc::new(crate::tools::create_default_registry(&doc_service));
143
144 Ok(Self {
145 config,
146 tool_registry,
147 cache,
148 })
149 }
150
151 #[allow(unused_variables)]
155 #[allow(clippy::unused_async)]
156 pub async fn new_async(config: ServerConfig) -> Result<Self> {
157 #[cfg(feature = "cache-redis")]
159 {
160 let cache_box: Box<dyn Cache> = crate::cache::create_cache_async(&config.cache).await?;
161 let cache: Arc<dyn Cache> = Arc::from(cache_box);
162
163 let doc_service = Arc::new(crate::tools::docs::DocService::new(cache.clone()));
165
166 let tool_registry = Arc::new(crate::tools::create_default_registry(&doc_service));
168
169 Ok(Self {
170 config,
171 tool_registry,
172 cache,
173 })
174 }
175
176 #[cfg(not(feature = "cache-redis"))]
177 {
178 let cache_box: Box<dyn Cache> = crate::cache::create_cache(&config.cache)?;
180 let cache: Arc<dyn Cache> = Arc::from(cache_box);
181
182 let doc_service = Arc::new(crate::tools::docs::DocService::new(cache.clone()));
184
185 let tool_registry = Arc::new(crate::tools::create_default_registry(&doc_service));
187
188 Ok(Self {
189 config,
190 tool_registry,
191 cache,
192 })
193 }
194 }
195
196 #[must_use]
198 pub fn config(&self) -> &ServerConfig {
199 &self.config
200 }
201
202 #[must_use]
204 pub fn tool_registry(&self) -> &Arc<ToolRegistry> {
205 &self.tool_registry
206 }
207
208 #[must_use]
210 pub fn cache(&self) -> &Arc<dyn Cache> {
211 &self.cache
212 }
213
214 #[must_use]
216 pub fn server_info(&self) -> InitializeResult {
217 InitializeResult {
218 server_info: Implementation {
219 name: self.config.name.clone(),
220 version: self.config.version.clone(),
221 title: Some("Crates Docs MCP Server".to_string()),
222 description: self.config.description.clone(),
223 icons: self.config.icons.clone(),
224 website_url: self.config.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 pub async fn run_stdio(&self) -> Result<()> {
246 transport::run_stdio_server(self).await
247 }
248
249 pub async fn run_http(&self) -> Result<()> {
251 transport::run_http_server(self).await
252 }
253
254 pub async fn run_sse(&self) -> Result<()> {
256 transport::run_sse_server(self).await
257 }
258}