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;
17use tracing::{info_span, Instrument};
18use uuid::Uuid;
19
20pub struct CratesDocsHandler {
22 server: Arc<CratesDocsServer>,
23}
24
25impl CratesDocsHandler {
26 #[must_use]
28 pub fn new(server: Arc<CratesDocsServer>) -> Self {
29 Self { server }
30 }
31
32 fn tool_registry(&self) -> &ToolRegistry {
34 self.server.tool_registry()
35 }
36}
37
38#[async_trait]
39impl ServerHandler for CratesDocsHandler {
40 async fn handle_list_tools_request(
42 &self,
43 _request: Option<PaginatedRequestParams>,
44 _runtime: std::sync::Arc<dyn McpServer>,
45 ) -> std::result::Result<ListToolsResult, RpcError> {
46 let trace_id = Uuid::new_v4().to_string();
47 let span = info_span!(
48 "list_tools",
49 trace_id = %trace_id,
50 );
51
52 async {
53 tracing::debug!("Listing available tools");
54 let tools = self.tool_registry().get_tools();
55 tracing::debug!("Found {} tools", tools.len());
56
57 Ok(ListToolsResult {
58 tools,
59 meta: None,
60 next_cursor: None,
61 })
62 }
63 .instrument(span)
64 .await
65 }
66
67 async fn handle_call_tool_request(
69 &self,
70 params: CallToolRequestParams,
71 _runtime: std::sync::Arc<dyn McpServer>,
72 ) -> std::result::Result<CallToolResult, CallToolError> {
73 let trace_id = Uuid::new_v4().to_string();
74 let tool_name = params.name.clone();
75 let span = info_span!(
76 "call_tool",
77 trace_id = %trace_id,
78 tool = %tool_name,
79 );
80
81 async {
82 tracing::info!("Executing tool: {}", tool_name);
83 let start = std::time::Instant::now();
84
85 let result = self
86 .tool_registry()
87 .execute_tool(
88 &tool_name,
89 params
90 .arguments
91 .map_or_else(|| serde_json::Value::Null, serde_json::Value::Object),
92 )
93 .await;
94
95 let duration = start.elapsed();
96 match &result {
97 Ok(_) => {
98 tracing::info!("Tool {} executed successfully in {:?}", tool_name, duration);
99 }
100 Err(e) => {
101 tracing::error!(
102 "Tool {} execution failed after {:?}: {:?}",
103 tool_name,
104 duration,
105 e
106 );
107 }
108 }
109
110 result
111 }
112 .instrument(span)
113 .await
114 }
115
116 async fn handle_list_resources_request(
118 &self,
119 _request: Option<PaginatedRequestParams>,
120 _runtime: std::sync::Arc<dyn McpServer>,
121 ) -> std::result::Result<ListResourcesResult, RpcError> {
122 Ok(ListResourcesResult {
124 resources: vec![],
125 meta: None,
126 next_cursor: None,
127 })
128 }
129
130 async fn handle_read_resource_request(
132 &self,
133 _params: ReadResourceRequestParams,
134 _runtime: std::sync::Arc<dyn McpServer>,
135 ) -> std::result::Result<ReadResourceResult, RpcError> {
136 Err(RpcError::invalid_request().with_message("Resource not found".to_string()))
138 }
139
140 async fn handle_list_prompts_request(
142 &self,
143 _request: Option<PaginatedRequestParams>,
144 _runtime: std::sync::Arc<dyn McpServer>,
145 ) -> std::result::Result<ListPromptsResult, RpcError> {
146 Ok(ListPromptsResult {
148 prompts: vec![],
149 meta: None,
150 next_cursor: None,
151 })
152 }
153
154 async fn handle_get_prompt_request(
156 &self,
157 _params: GetPromptRequestParams,
158 _runtime: std::sync::Arc<dyn McpServer>,
159 ) -> std::result::Result<GetPromptResult, RpcError> {
160 Err(RpcError::invalid_request().with_message("Prompt not found".to_string()))
162 }
163}
164
165pub struct CratesDocsHandlerCore {
167 server: Arc<CratesDocsServer>,
168}
169
170impl CratesDocsHandlerCore {
171 #[must_use]
173 pub fn new(server: Arc<CratesDocsServer>) -> Self {
174 Self { server }
175 }
176
177 async fn execute_tool_request(&self, params: CallToolRequestParams) -> ResultFromServer {
178 let trace_id = Uuid::new_v4().to_string();
179 let tool_name = params.name.clone();
180 let span = info_span!(
181 "execute_tool_core",
182 trace_id = %trace_id,
183 tool = %tool_name,
184 );
185
186 async {
187 tracing::info!("Executing tool request: {}", tool_name);
188 let start = std::time::Instant::now();
189
190 let result = self
191 .server
192 .tool_registry()
193 .execute_tool(
194 &tool_name,
195 params
196 .arguments
197 .map_or_else(|| serde_json::Value::Null, serde_json::Value::Object),
198 )
199 .await;
200
201 let duration = start.elapsed();
202 match &result {
203 Ok(_) => {
204 tracing::info!("Tool {} executed successfully in {:?}", tool_name, duration);
205 }
206 Err(e) => {
207 tracing::error!(
208 "Tool {} execution failed after {:?}: {:?}",
209 tool_name,
210 duration,
211 e
212 );
213 }
214 }
215
216 result.unwrap_or_else(CallToolResult::from).into()
217 }
218 .instrument(span)
219 .await
220 }
221}
222
223#[async_trait]
224impl ServerHandlerCore for CratesDocsHandlerCore {
225 async fn handle_request(
227 &self,
228 request: RequestFromClient,
229 _runtime: std::sync::Arc<dyn McpServer>,
230 ) -> std::result::Result<ResultFromServer, RpcError> {
231 match request {
232 RequestFromClient::ListToolsRequest(_params) => {
233 let tools = self.server.tool_registry().get_tools();
234 Ok(ListToolsResult {
235 tools,
236 meta: None,
237 next_cursor: None,
238 }
239 .into())
240 }
241 RequestFromClient::CallToolRequest(params) => {
242 Ok(self.execute_tool_request(params).await)
243 }
244 RequestFromClient::ListResourcesRequest(_params) => Ok(ListResourcesResult {
245 resources: vec![],
246 meta: None,
247 next_cursor: None,
248 }
249 .into()),
250 RequestFromClient::ReadResourceRequest(_params) => {
251 Err(RpcError::invalid_request().with_message("Resource not found".to_string()))
252 }
253 RequestFromClient::ListPromptsRequest(_params) => Ok(ListPromptsResult {
254 prompts: vec![],
255 meta: None,
256 next_cursor: None,
257 }
258 .into()),
259 RequestFromClient::GetPromptRequest(_params) => {
260 Err(RpcError::invalid_request().with_message("Prompt not found".to_string()))
261 }
262 RequestFromClient::InitializeRequest(_params) => {
263 Err(RpcError::method_not_found()
265 .with_message("Initialize request should be handled by runtime".to_string()))
266 }
267 _ => {
268 Err(RpcError::method_not_found()
270 .with_message("Unimplemented request type".to_string()))
271 }
272 }
273 }
274
275 async fn handle_notification(
277 &self,
278 _notification: NotificationFromClient,
279 _runtime: std::sync::Arc<dyn McpServer>,
280 ) -> std::result::Result<(), RpcError> {
281 Ok(())
283 }
284
285 async fn handle_error(
287 &self,
288 _error: &RpcError,
289 _runtime: std::sync::Arc<dyn McpServer>,
290 ) -> std::result::Result<(), RpcError> {
291 tracing::error!("MCP error: {:?}", _error);
293 Ok(())
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::CratesDocsHandlerCore;
300 use crate::server::CratesDocsServer;
301 use rust_mcp_sdk::schema::{CallToolRequestParams, CallToolResult, ContentBlock};
302 use std::sync::Arc;
303
304 #[tokio::test]
305 async fn test_execute_tool_request_preserves_tool_errors() {
306 let server = Arc::new(CratesDocsServer::new(crate::AppConfig::default()).unwrap());
307 let handler = CratesDocsHandlerCore::new(server);
308 let result = handler
309 .execute_tool_request(CallToolRequestParams {
310 arguments: Some(serde_json::Map::from_iter([(
311 "verbose".to_string(),
312 serde_json::Value::String("bad".to_string()),
313 )])),
314 meta: None,
315 name: "health_check".to_string(),
316 task: None,
317 })
318 .await;
319
320 let result = CallToolResult::try_from(result).unwrap();
321 assert_eq!(result.is_error, Some(true));
322
323 let Some(ContentBlock::TextContent(text)) = result.content.first() else {
324 panic!("expected first content block to be text");
325 };
326
327 assert!(text.text.contains("health_check"));
328 assert!(text.text.contains("Parameter parsing failed"));
329 assert!(!text.text.contains("Unknown tool"));
330 }
331}