crates_docs/server/handler/
core_handler.rs1use 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
16pub struct CratesDocsHandlerCore {
21 core: HandlerCore,
22}
23
24impl CratesDocsHandlerCore {
25 #[must_use]
27 pub fn new(server: Arc<CratesDocsServer>) -> Self {
28 Self {
29 core: HandlerCore::new(server),
30 }
31 }
32
33 #[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 #[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 #[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 #[must_use]
63 pub fn core(&self) -> &HandlerCore {
64 &self.core
65 }
66
67 #[must_use]
69 pub fn server(&self) -> &Arc<CratesDocsServer> {
70 self.core.server()
71 }
72}
73
74#[async_trait]
75impl ServerHandlerCore for CratesDocsHandlerCore {
76 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 async fn handle_notification(
110 &self,
111 _notification: NotificationFromClient,
112 _runtime: Arc<dyn McpServer>,
113 ) -> std::result::Result<(), RpcError> {
114 Ok(())
115 }
116
117 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}