1use 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
18pub struct CratesDocsHandler {
20 server: Arc<CratesDocsServer>,
21}
22
23impl CratesDocsHandler {
24 #[must_use]
26 pub fn new(server: Arc<CratesDocsServer>) -> Self {
27 Self { server }
28 }
29
30 fn tool_registry(&self) -> &ToolRegistry {
32 self.server.tool_registry()
33 }
34}
35
36#[async_trait]
37impl ServerHandler for CratesDocsHandler {
38 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 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 ¶ms.name,
62 params
63 .arguments
64 .map_or_else(|| serde_json::Value::Null, serde_json::Value::Object),
65 )
66 .await
67 }
68
69 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 Ok(ListResourcesResult {
77 resources: vec![],
78 meta: None,
79 next_cursor: None,
80 })
81 }
82
83 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 Err(RpcError::invalid_request().with_message("Resource not found".to_string()))
91 }
92
93 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 Ok(ListPromptsResult {
101 prompts: vec![],
102 meta: None,
103 next_cursor: None,
104 })
105 }
106
107 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 Err(RpcError::invalid_request().with_message("Prompt not found".to_string()))
115 }
116}
117
118pub struct CratesDocsHandlerCore {
120 server: Arc<CratesDocsServer>,
121}
122
123impl CratesDocsHandlerCore {
124 #[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 ¶ms.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 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 Err(RpcError::method_not_found()
187 .with_message("Initialize request should be handled by runtime".to_string()))
188 }
189 _ => {
190 Err(RpcError::method_not_found()
192 .with_message("Unimplemented request type".to_string()))
193 }
194 }
195 }
196
197 async fn handle_notification(
199 &self,
200 _notification: NotificationFromClient,
201 _runtime: std::sync::Arc<dyn McpServer>,
202 ) -> std::result::Result<(), RpcError> {
203 Ok(())
205 }
206
207 async fn handle_error(
209 &self,
210 _error: &RpcError,
211 _runtime: std::sync::Arc<dyn McpServer>,
212 ) -> std::result::Result<(), RpcError> {
213 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}