Skip to main content

crates_docs/server/handler/
standard.rs

1//! MCP server handler implementation
2
3use async_trait::async_trait;
4use rust_mcp_sdk::{
5    mcp_server::ServerHandler,
6    schema::{
7        CallToolError, CallToolRequestParams, CallToolResult, GetPromptRequestParams,
8        GetPromptResult, ListPromptsResult, ListResourcesResult, ListToolsResult,
9        PaginatedRequestParams, ReadResourceRequestParams, ReadResourceResult, RpcError,
10    },
11    McpServer,
12};
13use std::sync::Arc;
14use tracing::{info_span, Instrument};
15use uuid::Uuid;
16
17use super::config::HandlerConfig;
18use super::core::HandlerCore;
19use crate::metrics::ServerMetrics;
20use crate::server::CratesDocsServer;
21
22/// MCP server handler
23///
24/// Implements standard MCP protocol handler interface, handles client requests.
25/// Delegates all core logic to `HandlerCore`.
26///
27/// # Fields
28///
29/// - `core`: Shared core handling logic
30pub struct CratesDocsHandler {
31    core: HandlerCore,
32}
33
34impl CratesDocsHandler {
35    /// Create new handler
36    ///
37    /// # Arguments
38    ///
39    /// * `server` - Server instance
40    ///
41    /// # Example
42    ///
43    /// ```rust,no_run
44    /// use std::sync::Arc;
45    /// use crates_docs::server::{CratesDocsServer, CratesDocsHandler};
46    /// use crates_docs::AppConfig;
47    ///
48    /// let config = AppConfig::default();
49    /// let server = Arc::new(CratesDocsServer::new(config).unwrap());
50    /// let handler = CratesDocsHandler::new(server);
51    /// ```
52    #[must_use]
53    pub fn new(server: Arc<CratesDocsServer>) -> Self {
54        Self {
55            core: HandlerCore::new(server),
56        }
57    }
58
59    /// Create handler with configuration
60    #[must_use]
61    pub fn with_config(server: Arc<CratesDocsServer>, config: HandlerConfig) -> Self {
62        Self {
63            core: HandlerCore::with_config(server, config),
64        }
65    }
66
67    /// Create handler with merged configuration
68    #[must_use]
69    pub fn with_merged_config(
70        server: Arc<CratesDocsServer>,
71        base_config: HandlerConfig,
72        override_config: Option<HandlerConfig>,
73    ) -> Self {
74        Self {
75            core: HandlerCore::with_merged_config(server, base_config, override_config),
76        }
77    }
78
79    /// Set metrics
80    #[must_use]
81    pub fn with_metrics(self, metrics: Arc<ServerMetrics>) -> Self {
82        Self {
83            core: self.core.with_metrics(metrics),
84        }
85    }
86
87    /// Get core handler
88    #[must_use]
89    pub fn core(&self) -> &HandlerCore {
90        &self.core
91    }
92
93    /// Get server reference
94    #[must_use]
95    pub fn server(&self) -> &Arc<CratesDocsServer> {
96        self.core.server()
97    }
98}
99
100#[async_trait]
101impl ServerHandler for CratesDocsHandler {
102    /// Handle list tools request
103    async fn handle_list_tools_request(
104        &self,
105        _request: Option<PaginatedRequestParams>,
106        _runtime: Arc<dyn McpServer>,
107    ) -> std::result::Result<ListToolsResult, RpcError> {
108        let trace_id = Uuid::new_v4().to_string();
109        let span = info_span!("list_tools", trace_id = %trace_id);
110
111        async {
112            tracing::debug!("Listing available tools");
113            let result = self.core.list_tools();
114            tracing::debug!("Found {} tools", result.tools.len());
115            Ok(result)
116        }
117        .instrument(span)
118        .await
119    }
120
121    /// Handle call tool request
122    async fn handle_call_tool_request(
123        &self,
124        params: CallToolRequestParams,
125        _runtime: Arc<dyn McpServer>,
126    ) -> std::result::Result<CallToolResult, CallToolError> {
127        self.core.execute_tool(params).await.into_call_tool_result()
128    }
129
130    /// Handle list resources request
131    async fn handle_list_resources_request(
132        &self,
133        _request: Option<PaginatedRequestParams>,
134        _runtime: Arc<dyn McpServer>,
135    ) -> std::result::Result<ListResourcesResult, RpcError> {
136        Ok(self.core.list_resources())
137    }
138
139    /// Handle read resource request
140    async fn handle_read_resource_request(
141        &self,
142        _params: ReadResourceRequestParams,
143        _runtime: Arc<dyn McpServer>,
144    ) -> std::result::Result<ReadResourceResult, RpcError> {
145        Err(RpcError::invalid_request().with_message("Resource not found".to_string()))
146    }
147
148    /// Handle list prompts request
149    async fn handle_list_prompts_request(
150        &self,
151        _request: Option<PaginatedRequestParams>,
152        _runtime: Arc<dyn McpServer>,
153    ) -> std::result::Result<ListPromptsResult, RpcError> {
154        Ok(self.core.list_prompts())
155    }
156
157    /// Handle get prompt request
158    async fn handle_get_prompt_request(
159        &self,
160        _params: GetPromptRequestParams,
161        _runtime: Arc<dyn McpServer>,
162    ) -> std::result::Result<GetPromptResult, RpcError> {
163        Err(RpcError::invalid_request().with_message("Prompt not found".to_string()))
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use crate::AppConfig;
171
172    #[tokio::test]
173    async fn test_crates_docs_handler_delegation() {
174        let server = Arc::new(CratesDocsServer::new(AppConfig::default()).unwrap());
175        let handler = CratesDocsHandler::new(server);
176
177        let result = handler
178            .core()
179            .execute_tool(rust_mcp_sdk::schema::CallToolRequestParams {
180                arguments: Some(serde_json::Map::new()),
181                meta: None,
182                name: "health_check".to_string(),
183                task: None,
184            })
185            .await;
186
187        assert!(result.success);
188    }
189
190    #[tokio::test]
191    async fn test_handler_with_merged_config() {
192        let server = Arc::new(CratesDocsServer::new(AppConfig::default()).unwrap());
193        let base_config = HandlerConfig::default();
194        let override_config = HandlerConfig::new().with_verbose_logging();
195
196        let handler =
197            CratesDocsHandler::with_merged_config(server, base_config, Some(override_config));
198
199        assert!(handler.core().config().verbose_logging);
200        assert!(!handler.core().config().enable_metrics);
201    }
202}