Skip to main content

crates_docs/server/
handler.rs

1//! MCP handler implementation
2
3use crate::server::CratesDocsServer;
4use crate::tools::ToolRegistry;
5use async_trait::async_trait;
6use rust_mcp_sdk::{
7    mcp_server::{ServerHandler, ServerHandlerCore},
8    schema::{
9        CallToolError, CallToolRequestParams, CallToolResult, GetPromptRequestParams,
10        GetPromptResult, ListPromptsResult, ListResourcesResult, ListToolsResult,
11        NotificationFromClient, PaginatedRequestParams, ReadResourceRequestParams,
12        ReadResourceResult, RequestFromClient, ResultFromServer, RpcError,
13    },
14    McpServer,
15};
16use std::sync::Arc;
17
18/// MCP server handler
19pub struct CratesDocsHandler {
20    server: Arc<CratesDocsServer>,
21}
22
23impl CratesDocsHandler {
24    /// Create a new handler
25    #[must_use]
26    pub fn new(server: Arc<CratesDocsServer>) -> Self {
27        Self { server }
28    }
29
30    /// Get the tool registry
31    fn tool_registry(&self) -> &ToolRegistry {
32        self.server.tool_registry()
33    }
34}
35
36#[async_trait]
37impl ServerHandler for CratesDocsHandler {
38    /// Handle list tools request
39    async fn handle_list_tools_request(
40        &self,
41        _request: Option<PaginatedRequestParams>,
42        _runtime: std::sync::Arc<dyn McpServer>,
43    ) -> std::result::Result<ListToolsResult, RpcError> {
44        let tools = self.tool_registry().get_tools();
45
46        Ok(ListToolsResult {
47            tools,
48            meta: None,
49            next_cursor: None,
50        })
51    }
52
53    /// Handle call tool request
54    async fn handle_call_tool_request(
55        &self,
56        params: CallToolRequestParams,
57        _runtime: std::sync::Arc<dyn McpServer>,
58    ) -> std::result::Result<CallToolResult, CallToolError> {
59        self.tool_registry()
60            .execute_tool(
61                &params.name,
62                params
63                    .arguments
64                    .map_or_else(|| serde_json::Value::Null, serde_json::Value::Object),
65            )
66            .await
67    }
68
69    /// Handle list resources request
70    async fn handle_list_resources_request(
71        &self,
72        _request: Option<PaginatedRequestParams>,
73        _runtime: std::sync::Arc<dyn McpServer>,
74    ) -> std::result::Result<ListResourcesResult, RpcError> {
75        // Resources are not currently provided
76        Ok(ListResourcesResult {
77            resources: vec![],
78            meta: None,
79            next_cursor: None,
80        })
81    }
82
83    /// Handle read resource request
84    async fn handle_read_resource_request(
85        &self,
86        _params: ReadResourceRequestParams,
87        _runtime: std::sync::Arc<dyn McpServer>,
88    ) -> std::result::Result<ReadResourceResult, RpcError> {
89        // Resources are not currently provided
90        Err(RpcError::invalid_request().with_message("Resource not found".to_string()))
91    }
92
93    /// Handle list prompts request
94    async fn handle_list_prompts_request(
95        &self,
96        _request: Option<PaginatedRequestParams>,
97        _runtime: std::sync::Arc<dyn McpServer>,
98    ) -> std::result::Result<ListPromptsResult, RpcError> {
99        // Prompts are not currently provided
100        Ok(ListPromptsResult {
101            prompts: vec![],
102            meta: None,
103            next_cursor: None,
104        })
105    }
106
107    /// Handle get prompt request
108    async fn handle_get_prompt_request(
109        &self,
110        _params: GetPromptRequestParams,
111        _runtime: std::sync::Arc<dyn McpServer>,
112    ) -> std::result::Result<GetPromptResult, RpcError> {
113        // Prompts are not currently provided
114        Err(RpcError::invalid_request().with_message("Prompt not found".to_string()))
115    }
116}
117
118/// Core handler implementation (provides more control)
119pub struct CratesDocsHandlerCore {
120    server: Arc<CratesDocsServer>,
121}
122
123impl CratesDocsHandlerCore {
124    /// Create a new core handler
125    #[must_use]
126    pub fn new(server: Arc<CratesDocsServer>) -> Self {
127        Self { server }
128    }
129
130    async fn execute_tool_request(&self, params: CallToolRequestParams) -> ResultFromServer {
131        self.server
132            .tool_registry()
133            .execute_tool(
134                &params.name,
135                params
136                    .arguments
137                    .map_or_else(|| serde_json::Value::Null, serde_json::Value::Object),
138            )
139            .await
140            .unwrap_or_else(CallToolResult::from)
141            .into()
142    }
143}
144
145#[async_trait]
146impl ServerHandlerCore for CratesDocsHandlerCore {
147    /// Handle request
148    async fn handle_request(
149        &self,
150        request: RequestFromClient,
151        _runtime: std::sync::Arc<dyn McpServer>,
152    ) -> std::result::Result<ResultFromServer, RpcError> {
153        match request {
154            RequestFromClient::ListToolsRequest(_params) => {
155                let tools = self.server.tool_registry().get_tools();
156                Ok(ListToolsResult {
157                    tools,
158                    meta: None,
159                    next_cursor: None,
160                }
161                .into())
162            }
163            RequestFromClient::CallToolRequest(params) => {
164                Ok(self.execute_tool_request(params).await)
165            }
166            RequestFromClient::ListResourcesRequest(_params) => Ok(ListResourcesResult {
167                resources: vec![],
168                meta: None,
169                next_cursor: None,
170            }
171            .into()),
172            RequestFromClient::ReadResourceRequest(_params) => {
173                Err(RpcError::invalid_request().with_message("Resource not found".to_string()))
174            }
175            RequestFromClient::ListPromptsRequest(_params) => Ok(ListPromptsResult {
176                prompts: vec![],
177                meta: None,
178                next_cursor: None,
179            }
180            .into()),
181            RequestFromClient::GetPromptRequest(_params) => {
182                Err(RpcError::invalid_request().with_message("Prompt not found".to_string()))
183            }
184            RequestFromClient::InitializeRequest(_params) => {
185                // Use default initialization handling
186                Err(RpcError::method_not_found()
187                    .with_message("Initialize request should be handled by runtime".to_string()))
188            }
189            _ => {
190                // Other requests use default handling
191                Err(RpcError::method_not_found()
192                    .with_message("Unimplemented request type".to_string()))
193            }
194        }
195    }
196
197    /// Handle notification
198    async fn handle_notification(
199        &self,
200        _notification: NotificationFromClient,
201        _runtime: std::sync::Arc<dyn McpServer>,
202    ) -> std::result::Result<(), RpcError> {
203        // Notifications are not currently handled
204        Ok(())
205    }
206
207    /// Handle error
208    async fn handle_error(
209        &self,
210        _error: &RpcError,
211        _runtime: std::sync::Arc<dyn McpServer>,
212    ) -> std::result::Result<(), RpcError> {
213        // Log error but don't interrupt
214        tracing::error!("MCP error: {:?}", _error);
215        Ok(())
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::CratesDocsHandlerCore;
222    use crate::server::CratesDocsServer;
223    use rust_mcp_sdk::schema::{CallToolRequestParams, CallToolResult, ContentBlock};
224    use std::sync::Arc;
225
226    #[tokio::test]
227    async fn test_execute_tool_request_preserves_tool_errors() {
228        let server = Arc::new(CratesDocsServer::new(crate::AppConfig::default()).unwrap());
229        let handler = CratesDocsHandlerCore::new(server);
230        let result = handler
231            .execute_tool_request(CallToolRequestParams {
232                arguments: Some(serde_json::Map::from_iter([(
233                    "verbose".to_string(),
234                    serde_json::Value::String("bad".to_string()),
235                )])),
236                meta: None,
237                name: "health_check".to_string(),
238                task: None,
239            })
240            .await;
241
242        let result = CallToolResult::try_from(result).unwrap();
243        assert_eq!(result.is_error, Some(true));
244
245        let Some(ContentBlock::TextContent(text)) = result.content.first() else {
246            panic!("expected first content block to be text");
247        };
248
249        assert!(text.text.contains("health_check"));
250        assert!(text.text.contains("Parameter parsing failed"));
251        assert!(!text.text.contains("Unknown tool"));
252    }
253}