crates_docs/server/handler/
standard.rs1use async_trait::async_trait;
4use rust_mcp_sdk::{
5 mcp_server::ServerHandler,
6 schema::{
7 CallToolError, CallToolRequestParams, CallToolResult, GetPromptRequestParams,
8 GetPromptResult, ListPromptsResult, ListResourcesResult, ListToolsResult,
9 PaginatedRequestParams, ReadResourceRequestParams, ReadResourceResult, RpcError,
10 },
11 McpServer,
12};
13use std::sync::Arc;
14use tracing::{info_span, Instrument};
15use uuid::Uuid;
16
17use super::config::HandlerConfig;
18use super::types::ToolExecutionResult;
19use crate::metrics::ServerMetrics;
20use crate::server::CratesDocsServer;
21use crate::tools::ToolRegistry;
22
23pub struct CratesDocsHandler {
33 server: Arc<CratesDocsServer>,
34 config: HandlerConfig,
35 metrics: Option<Arc<ServerMetrics>>,
36}
37
38impl CratesDocsHandler {
39 #[must_use]
57 pub fn new(server: Arc<CratesDocsServer>) -> Self {
58 Self {
59 server,
60 config: HandlerConfig::default(),
61 metrics: None,
62 }
63 }
64
65 #[must_use]
67 pub fn with_config(server: Arc<CratesDocsServer>, config: HandlerConfig) -> Self {
68 Self {
69 server,
70 config,
71 metrics: None,
72 }
73 }
74
75 #[must_use]
77 pub fn with_merged_config(
78 server: Arc<CratesDocsServer>,
79 base_config: HandlerConfig,
80 override_config: Option<HandlerConfig>,
81 ) -> Self {
82 Self {
83 server,
84 config: base_config.merge(override_config),
85 metrics: None,
86 }
87 }
88
89 #[must_use]
91 pub fn with_metrics(self, metrics: Arc<ServerMetrics>) -> Self {
92 Self {
93 metrics: Some(metrics),
94 ..self
95 }
96 }
97
98 #[must_use]
100 pub fn server(&self) -> &Arc<CratesDocsServer> {
101 &self.server
102 }
103
104 #[must_use]
106 pub fn tool_registry(&self) -> &ToolRegistry {
107 self.server.tool_registry()
108 }
109
110 #[must_use]
112 pub fn config(&self) -> &HandlerConfig {
113 &self.config
114 }
115
116 #[must_use]
118 pub fn metrics(&self) -> Option<&Arc<ServerMetrics>> {
119 self.metrics.as_ref()
120 }
121
122 #[must_use]
124 pub fn list_tools(&self) -> ListToolsResult {
125 ListToolsResult {
126 tools: self.tool_registry().get_tools(),
127 meta: None,
128 next_cursor: None,
129 }
130 }
131
132 #[must_use]
134 pub fn list_resources(&self) -> ListResourcesResult {
135 ListResourcesResult {
136 resources: vec![],
137 meta: None,
138 next_cursor: None,
139 }
140 }
141
142 #[must_use]
144 pub fn list_prompts(&self) -> ListPromptsResult {
145 ListPromptsResult {
146 prompts: vec![],
147 meta: None,
148 next_cursor: None,
149 }
150 }
151
152 pub async fn execute_tool(&self, params: CallToolRequestParams) -> ToolExecutionResult {
163 let trace_id = Uuid::new_v4().to_string();
164 let tool_name = params.name.clone();
165 let span = info_span!(
166 "execute_tool",
167 trace_id = %trace_id,
168 tool = %tool_name,
169 verbose = self.config.verbose_logging,
170 );
171
172 async {
173 tracing::info!("Executing tool: {}", tool_name);
174 let start = std::time::Instant::now();
175
176 let arguments = params
177 .arguments
178 .map_or_else(|| serde_json::Value::Null, serde_json::Value::Object);
179
180 let result = self
181 .tool_registry()
182 .execute_tool(&tool_name, arguments)
183 .await;
184
185 let duration = start.elapsed();
186 let success = result.is_ok();
187
188 match &result {
190 Ok(_) => {
191 tracing::info!("Tool {} executed successfully in {:?}", tool_name, duration);
192 if self.config.verbose_logging {
193 tracing::debug!("Verbose: Tool execution details available");
194 }
195 }
196 Err(e) => {
197 tracing::error!(
198 "Tool {} execution failed after {:?}: {:?}",
199 tool_name,
200 duration,
201 e
202 );
203 }
204 }
205
206 if let Some(metrics) = &self.metrics {
208 metrics.record_request(&tool_name, success, duration);
209 }
210
211 ToolExecutionResult {
212 tool_name,
213 duration,
214 success,
215 result,
216 }
217 }
218 .instrument(span)
219 .await
220 }
221}
222
223#[async_trait]
224impl ServerHandler for CratesDocsHandler {
225 async fn handle_list_tools_request(
227 &self,
228 _request: Option<PaginatedRequestParams>,
229 _runtime: Arc<dyn McpServer>,
230 ) -> std::result::Result<ListToolsResult, RpcError> {
231 let trace_id = Uuid::new_v4().to_string();
232 let span = info_span!("list_tools", trace_id = %trace_id);
233
234 async {
235 tracing::debug!("Listing available tools");
236 let result = self.list_tools();
237 tracing::debug!("Found {} tools", result.tools.len());
238 Ok(result)
239 }
240 .instrument(span)
241 .await
242 }
243
244 async fn handle_call_tool_request(
246 &self,
247 params: CallToolRequestParams,
248 _runtime: Arc<dyn McpServer>,
249 ) -> std::result::Result<CallToolResult, CallToolError> {
250 self.execute_tool(params).await.into_call_tool_result()
251 }
252
253 async fn handle_list_resources_request(
255 &self,
256 _request: Option<PaginatedRequestParams>,
257 _runtime: Arc<dyn McpServer>,
258 ) -> std::result::Result<ListResourcesResult, RpcError> {
259 Ok(self.list_resources())
260 }
261
262 async fn handle_read_resource_request(
264 &self,
265 _params: ReadResourceRequestParams,
266 _runtime: Arc<dyn McpServer>,
267 ) -> std::result::Result<ReadResourceResult, RpcError> {
268 Err(RpcError::invalid_request().with_message("Resource not found".to_string()))
269 }
270
271 async fn handle_list_prompts_request(
273 &self,
274 _request: Option<PaginatedRequestParams>,
275 _runtime: Arc<dyn McpServer>,
276 ) -> std::result::Result<ListPromptsResult, RpcError> {
277 Ok(self.list_prompts())
278 }
279
280 async fn handle_get_prompt_request(
282 &self,
283 _params: GetPromptRequestParams,
284 _runtime: Arc<dyn McpServer>,
285 ) -> std::result::Result<GetPromptResult, RpcError> {
286 Err(RpcError::invalid_request().with_message("Prompt not found".to_string()))
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use crate::AppConfig;
294
295 #[tokio::test]
296 async fn test_crates_docs_handler_execute_tool() {
297 let server = Arc::new(CratesDocsServer::new(AppConfig::default()).unwrap());
298 let handler = CratesDocsHandler::new(server);
299
300 let result = handler
301 .execute_tool(rust_mcp_sdk::schema::CallToolRequestParams {
302 arguments: Some(serde_json::Map::new()),
303 meta: None,
304 name: "health_check".to_string(),
305 task: None,
306 })
307 .await;
308
309 assert!(result.success);
310 }
311
312 #[tokio::test]
313 async fn test_handler_with_merged_config() {
314 let server = Arc::new(CratesDocsServer::new(AppConfig::default()).unwrap());
315 let base_config = HandlerConfig::default();
316 let override_config = HandlerConfig::new().with_verbose_logging();
317
318 let handler =
319 CratesDocsHandler::with_merged_config(server, base_config, Some(override_config));
320
321 assert!(handler.config().verbose_logging);
322 assert!(!handler.config().enable_metrics);
323 }
324
325 #[tokio::test]
326 async fn test_handler_with_metrics() {
327 let server = Arc::new(CratesDocsServer::new(AppConfig::default()).unwrap());
328 let metrics = Arc::new(ServerMetrics::new());
329 let handler = CratesDocsHandler::new(server).with_metrics(metrics.clone());
330
331 let _result = handler
332 .execute_tool(rust_mcp_sdk::schema::CallToolRequestParams {
333 arguments: None,
334 meta: None,
335 name: "health_check".to_string(),
336 task: None,
337 })
338 .await;
339
340 let metrics_output = metrics.export().unwrap();
342 assert!(metrics_output.contains("mcp_requests_total"));
343 }
344
345 #[tokio::test]
346 async fn test_handler_list_methods() {
347 let server = Arc::new(CratesDocsServer::new(AppConfig::default()).unwrap());
348 let handler = CratesDocsHandler::new(server);
349
350 let tools = handler.list_tools();
351 assert!(!tools.tools.is_empty());
352 assert_eq!(tools.tools.len(), 4); let resources = handler.list_resources();
355 assert!(resources.resources.is_empty());
356
357 let prompts = handler.list_prompts();
358 assert!(prompts.prompts.is_empty());
359 }
360}