Skip to main content

crates_docs/server/handler/
core_handler.rs

1//! Core handler implementation
2
3use async_trait::async_trait;
4use rust_mcp_sdk::{
5    mcp_server::ServerHandlerCore,
6    schema::{NotificationFromClient, RequestFromClient, ResultFromServer, RpcError},
7    McpServer,
8};
9use std::sync::Arc;
10
11use super::config::HandlerConfig;
12use super::core::HandlerCore;
13use crate::metrics::ServerMetrics;
14use crate::server::CratesDocsServer;
15
16/// Core handler implementation (provides more control)
17///
18/// Implements more fine-grained MCP protocol handler interface.
19/// Delegates all core logic to `HandlerCore`.
20pub struct CratesDocsHandlerCore {
21    core: HandlerCore,
22}
23
24impl CratesDocsHandlerCore {
25    /// Create a new core handler
26    #[must_use]
27    pub fn new(server: Arc<CratesDocsServer>) -> Self {
28        Self {
29            core: HandlerCore::new(server),
30        }
31    }
32
33    /// Create core handler with configuration
34    #[must_use]
35    pub fn with_config(server: Arc<CratesDocsServer>, config: HandlerConfig) -> Self {
36        Self {
37            core: HandlerCore::with_config(server, config),
38        }
39    }
40
41    /// Create core handler with merged configuration
42    #[must_use]
43    pub fn with_merged_config(
44        server: Arc<CratesDocsServer>,
45        base_config: HandlerConfig,
46        override_config: Option<HandlerConfig>,
47    ) -> Self {
48        Self {
49            core: HandlerCore::with_merged_config(server, base_config, override_config),
50        }
51    }
52
53    /// Set metrics
54    #[must_use]
55    pub fn with_metrics(self, metrics: Arc<ServerMetrics>) -> Self {
56        Self {
57            core: self.core.with_metrics(metrics),
58        }
59    }
60
61    /// Get core handler
62    #[must_use]
63    pub fn core(&self) -> &HandlerCore {
64        &self.core
65    }
66
67    /// Get server reference
68    #[must_use]
69    pub fn server(&self) -> &Arc<CratesDocsServer> {
70        self.core.server()
71    }
72}
73
74#[async_trait]
75impl ServerHandlerCore for CratesDocsHandlerCore {
76    /// Handle request
77    async fn handle_request(
78        &self,
79        request: RequestFromClient,
80        _runtime: Arc<dyn McpServer>,
81    ) -> std::result::Result<ResultFromServer, RpcError> {
82        match request {
83            RequestFromClient::ListToolsRequest(_params) => Ok(self.core.list_tools().into()),
84            RequestFromClient::CallToolRequest(params) => Ok(self
85                .core
86                .execute_tool(params)
87                .await
88                .into_result_from_server()),
89            RequestFromClient::ListResourcesRequest(_params) => {
90                Ok(self.core.list_resources().into())
91            }
92            RequestFromClient::ReadResourceRequest(_params) => {
93                Err(RpcError::invalid_request().with_message("Resource not found".to_string()))
94            }
95            RequestFromClient::ListPromptsRequest(_params) => Ok(self.core.list_prompts().into()),
96            RequestFromClient::GetPromptRequest(_params) => {
97                Err(RpcError::invalid_request().with_message("Prompt not found".to_string()))
98            }
99            RequestFromClient::InitializeRequest(_params) => Err(RpcError::method_not_found()
100                .with_message("Initialize request should be handled by runtime".to_string())),
101            _ => {
102                Err(RpcError::method_not_found()
103                    .with_message("Unimplemented request type".to_string()))
104            }
105        }
106    }
107
108    /// Handle notification
109    async fn handle_notification(
110        &self,
111        _notification: NotificationFromClient,
112        _runtime: Arc<dyn McpServer>,
113    ) -> std::result::Result<(), RpcError> {
114        Ok(())
115    }
116
117    /// Handle error
118    async fn handle_error(
119        &self,
120        error: &RpcError,
121        _runtime: Arc<dyn McpServer>,
122    ) -> std::result::Result<(), RpcError> {
123        tracing::error!("MCP error: {:?}", error);
124        Ok(())
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use crate::AppConfig;
132    use rust_mcp_sdk::schema::{CallToolResult, ContentBlock};
133
134    #[tokio::test]
135    async fn test_crates_docs_handler_core_execute_tool_request() {
136        let server = Arc::new(CratesDocsServer::new(AppConfig::default()).unwrap());
137        let handler = CratesDocsHandlerCore::new(server);
138
139        let result = handler
140            .core()
141            .execute_tool(rust_mcp_sdk::schema::CallToolRequestParams {
142                arguments: Some(serde_json::Map::from_iter([(
143                    "verbose".to_string(),
144                    serde_json::Value::String("bad".to_string()),
145                )])),
146                meta: None,
147                name: "health_check".to_string(),
148                task: None,
149            })
150            .await;
151
152        let call_result = result.into_call_tool_result();
153        assert!(call_result.is_err() || call_result.unwrap().is_error == Some(true));
154    }
155
156    #[tokio::test]
157    async fn test_execute_tool_request_preserves_tool_errors() {
158        let server = Arc::new(CratesDocsServer::new(AppConfig::default()).unwrap());
159        let handler = CratesDocsHandlerCore::new(server);
160
161        let tool_result = handler
162            .core()
163            .execute_tool(rust_mcp_sdk::schema::CallToolRequestParams {
164                arguments: Some(serde_json::Map::from_iter([(
165                    "verbose".to_string(),
166                    serde_json::Value::String("bad".to_string()),
167                )])),
168                meta: None,
169                name: "health_check".to_string(),
170                task: None,
171            })
172            .await;
173
174        let result = CallToolResult::try_from(tool_result.into_result_from_server()).unwrap();
175        assert_eq!(result.is_error, Some(true));
176
177        let Some(ContentBlock::TextContent(text)) = result.content.first() else {
178            panic!("expected first content block to be text");
179        };
180
181        assert!(text.text.contains("health_check"));
182        assert!(text.text.contains("Parameter parsing failed"));
183        assert!(!text.text.contains("Unknown tool"));
184    }
185}