pulseengine_mcp_server/
backend.rs

1//! Backend trait for pluggable MCP implementations
2
3use async_trait::async_trait;
4use pulseengine_mcp_protocol::*;
5use std::error::Error as StdError;
6use thiserror::Error;
7
8/// Error type for backend operations
9#[derive(Debug, Error)]
10pub enum BackendError {
11    #[error("Backend not initialized")]
12    NotInitialized,
13
14    #[error("Configuration error: {0}")]
15    Configuration(String),
16
17    #[error("Connection error: {0}")]
18    Connection(String),
19
20    #[error("Operation not supported: {0}")]
21    NotSupported(String),
22
23    #[error("Internal backend error: {0}")]
24    Internal(String),
25
26    #[error("Custom error: {0}")]
27    Custom(Box<dyn StdError + Send + Sync>),
28}
29
30impl BackendError {
31    pub fn configuration(msg: impl Into<String>) -> Self {
32        Self::Configuration(msg.into())
33    }
34
35    pub fn connection(msg: impl Into<String>) -> Self {
36        Self::Connection(msg.into())
37    }
38
39    pub fn not_supported(msg: impl Into<String>) -> Self {
40        Self::NotSupported(msg.into())
41    }
42
43    pub fn internal(msg: impl Into<String>) -> Self {
44        Self::Internal(msg.into())
45    }
46
47    pub fn custom(error: impl StdError + Send + Sync + 'static) -> Self {
48        Self::Custom(Box::new(error))
49    }
50}
51
52/// Convert BackendError to MCP protocol Error
53impl From<BackendError> for Error {
54    fn from(err: BackendError) -> Self {
55        match err {
56            BackendError::NotInitialized => Error::internal_error("Backend not initialized"),
57            BackendError::Configuration(msg) => Error::invalid_params(msg),
58            BackendError::Connection(msg) => {
59                Error::internal_error(format!("Connection failed: {msg}"))
60            }
61            BackendError::NotSupported(msg) => Error::method_not_found(msg),
62            BackendError::Internal(msg) => Error::internal_error(msg),
63            BackendError::Custom(err) => Error::internal_error(err.to_string()),
64        }
65    }
66}
67
68/// Main trait for MCP backend implementations
69///
70/// This trait defines the interface that backends must implement to provide
71/// domain-specific functionality (tools, resources, prompts) while the framework
72/// handles the MCP protocol, authentication, transport, and middleware.
73#[async_trait]
74pub trait McpBackend: Send + Sync + Clone {
75    /// Backend-specific error type
76    type Error: StdError + Send + Sync + Into<Error> + From<BackendError> + 'static;
77
78    /// Backend configuration type
79    type Config: Clone + Send + Sync;
80
81    /// Initialize the backend with configuration
82    ///
83    /// This is called once during server startup and should establish any
84    /// necessary connections, load configuration, and prepare the backend
85    /// for handling requests.
86    async fn initialize(config: Self::Config) -> std::result::Result<Self, Self::Error>;
87
88    /// Get server information and capabilities
89    ///
90    /// This defines what the backend supports (tools, resources, prompts)
91    /// and provides metadata about the implementation.
92    fn get_server_info(&self) -> ServerInfo;
93
94    /// Health check for the backend
95    ///
96    /// Should verify that all backend services are operational.
97    /// Called regularly for monitoring and health endpoints.
98    async fn health_check(&self) -> std::result::Result<(), Self::Error>;
99
100    // Tool Management
101
102    /// List available tools with pagination
103    async fn list_tools(
104        &self,
105        request: PaginatedRequestParam,
106    ) -> std::result::Result<ListToolsResult, Self::Error>;
107
108    /// Execute a tool with the given parameters
109    async fn call_tool(
110        &self,
111        request: CallToolRequestParam,
112    ) -> std::result::Result<CallToolResult, Self::Error>;
113
114    // Resource Management
115
116    /// List available resources with pagination
117    async fn list_resources(
118        &self,
119        request: PaginatedRequestParam,
120    ) -> std::result::Result<ListResourcesResult, Self::Error>;
121
122    /// Read a resource by URI
123    async fn read_resource(
124        &self,
125        request: ReadResourceRequestParam,
126    ) -> std::result::Result<ReadResourceResult, Self::Error>;
127
128    /// List resource templates (optional)
129    async fn list_resource_templates(
130        &self,
131        request: PaginatedRequestParam,
132    ) -> std::result::Result<ListResourceTemplatesResult, Self::Error> {
133        let _ = request;
134        Ok(ListResourceTemplatesResult {
135            resource_templates: vec![],
136            next_cursor: Some(String::new()),
137        })
138    }
139
140    // Prompt Management
141
142    /// List available prompts with pagination
143    async fn list_prompts(
144        &self,
145        request: PaginatedRequestParam,
146    ) -> std::result::Result<ListPromptsResult, Self::Error>;
147
148    /// Get a specific prompt
149    async fn get_prompt(
150        &self,
151        request: GetPromptRequestParam,
152    ) -> std::result::Result<GetPromptResult, Self::Error>;
153
154    // Subscription Management (optional)
155
156    /// Subscribe to resource updates
157    async fn subscribe(
158        &self,
159        request: SubscribeRequestParam,
160    ) -> std::result::Result<(), Self::Error> {
161        let _ = request;
162        Err(BackendError::not_supported("Subscriptions not supported").into())
163    }
164
165    /// Unsubscribe from resource updates
166    async fn unsubscribe(
167        &self,
168        request: UnsubscribeRequestParam,
169    ) -> std::result::Result<(), Self::Error> {
170        let _ = request;
171        Err(BackendError::not_supported("Subscriptions not supported").into())
172    }
173
174    // Auto-completion (optional)
175
176    /// Complete tool or resource names
177    async fn complete(
178        &self,
179        request: CompleteRequestParam,
180    ) -> std::result::Result<CompleteResult, Self::Error> {
181        let _ = request;
182        Ok(CompleteResult { completion: vec![] })
183    }
184
185    // Elicitation (optional)
186
187    /// Request structured input from the user
188    async fn elicit(
189        &self,
190        request: ElicitationRequestParam,
191    ) -> std::result::Result<ElicitationResult, Self::Error> {
192        let _ = request;
193        Err(BackendError::not_supported("Elicitation not supported").into())
194    }
195
196    // Logging control (optional)
197
198    /// Set logging level
199    async fn set_level(
200        &self,
201        request: SetLevelRequestParam,
202    ) -> std::result::Result<(), Self::Error> {
203        let _ = request;
204        Err(BackendError::not_supported("Logging level control not supported").into())
205    }
206
207    // Lifecycle hooks
208
209    /// Called when the server is starting up
210    async fn on_startup(&self) -> std::result::Result<(), Self::Error> {
211        Ok(())
212    }
213
214    /// Called when the server is shutting down
215    async fn on_shutdown(&self) -> std::result::Result<(), Self::Error> {
216        Ok(())
217    }
218
219    /// Called when a client connects
220    async fn on_client_connect(
221        &self,
222        client_info: &Implementation,
223    ) -> std::result::Result<(), Self::Error> {
224        let _ = client_info;
225        Ok(())
226    }
227
228    /// Called when a client disconnects
229    async fn on_client_disconnect(
230        &self,
231        client_info: &Implementation,
232    ) -> std::result::Result<(), Self::Error> {
233        let _ = client_info;
234        Ok(())
235    }
236
237    // Custom method handlers (for domain-specific extensions)
238
239    /// Handle custom methods not part of the standard MCP protocol
240    async fn handle_custom_method(
241        &self,
242        method: &str,
243        params: serde_json::Value,
244    ) -> std::result::Result<serde_json::Value, Self::Error> {
245        let _ = (method, params);
246        Err(BackendError::not_supported(format!("Custom method not supported: {method}")).into())
247    }
248}
249
250/// Convenience trait for backends that don't need all capabilities
251#[async_trait]
252pub trait SimpleBackend: Send + Sync + Clone {
253    type Error: StdError + Send + Sync + Into<Error> + From<BackendError> + 'static;
254    type Config: Clone + Send + Sync;
255
256    async fn initialize(config: Self::Config) -> std::result::Result<Self, Self::Error>;
257    fn get_server_info(&self) -> ServerInfo;
258    async fn health_check(&self) -> std::result::Result<(), Self::Error>;
259
260    // Only require tools - other methods have default implementations
261    async fn list_tools(
262        &self,
263        request: PaginatedRequestParam,
264    ) -> std::result::Result<ListToolsResult, Self::Error>;
265    async fn call_tool(
266        &self,
267        request: CallToolRequestParam,
268    ) -> std::result::Result<CallToolResult, Self::Error>;
269}
270
271/// Blanket implementation to convert SimpleBackend to McpBackend
272#[async_trait]
273impl<T> McpBackend for T
274where
275    T: SimpleBackend,
276{
277    type Error = T::Error;
278    type Config = T::Config;
279
280    async fn initialize(config: Self::Config) -> std::result::Result<Self, Self::Error> {
281        T::initialize(config).await
282    }
283
284    fn get_server_info(&self) -> ServerInfo {
285        T::get_server_info(self)
286    }
287
288    async fn health_check(&self) -> std::result::Result<(), Self::Error> {
289        T::health_check(self).await
290    }
291
292    async fn list_tools(
293        &self,
294        request: PaginatedRequestParam,
295    ) -> std::result::Result<ListToolsResult, Self::Error> {
296        T::list_tools(self, request).await
297    }
298
299    async fn call_tool(
300        &self,
301        request: CallToolRequestParam,
302    ) -> std::result::Result<CallToolResult, Self::Error> {
303        T::call_tool(self, request).await
304    }
305
306    // Default implementations for optional capabilities
307    async fn list_resources(
308        &self,
309        _request: PaginatedRequestParam,
310    ) -> std::result::Result<ListResourcesResult, Self::Error> {
311        Ok(ListResourcesResult {
312            resources: vec![],
313            next_cursor: None,
314        })
315    }
316
317    async fn read_resource(
318        &self,
319        request: ReadResourceRequestParam,
320    ) -> std::result::Result<ReadResourceResult, Self::Error> {
321        Err(BackendError::not_supported(format!("Resource not found: {}", request.uri)).into())
322    }
323
324    async fn list_prompts(
325        &self,
326        _request: PaginatedRequestParam,
327    ) -> std::result::Result<ListPromptsResult, Self::Error> {
328        Ok(ListPromptsResult {
329            prompts: vec![],
330            next_cursor: None,
331        })
332    }
333
334    async fn get_prompt(
335        &self,
336        request: GetPromptRequestParam,
337    ) -> std::result::Result<GetPromptResult, Self::Error> {
338        Err(BackendError::not_supported(format!("Prompt not found: {}", request.name)).into())
339    }
340}