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    // Logging control (optional)
186
187    /// Set logging level
188    async fn set_level(
189        &self,
190        request: SetLevelRequestParam,
191    ) -> std::result::Result<(), Self::Error> {
192        let _ = request;
193        Err(BackendError::not_supported("Logging level control not supported").into())
194    }
195
196    // Lifecycle hooks
197
198    /// Called when the server is starting up
199    async fn on_startup(&self) -> std::result::Result<(), Self::Error> {
200        Ok(())
201    }
202
203    /// Called when the server is shutting down
204    async fn on_shutdown(&self) -> std::result::Result<(), Self::Error> {
205        Ok(())
206    }
207
208    /// Called when a client connects
209    async fn on_client_connect(
210        &self,
211        client_info: &Implementation,
212    ) -> std::result::Result<(), Self::Error> {
213        let _ = client_info;
214        Ok(())
215    }
216
217    /// Called when a client disconnects
218    async fn on_client_disconnect(
219        &self,
220        client_info: &Implementation,
221    ) -> std::result::Result<(), Self::Error> {
222        let _ = client_info;
223        Ok(())
224    }
225
226    // Custom method handlers (for domain-specific extensions)
227
228    /// Handle custom methods not part of the standard MCP protocol
229    async fn handle_custom_method(
230        &self,
231        method: &str,
232        params: serde_json::Value,
233    ) -> std::result::Result<serde_json::Value, Self::Error> {
234        let _ = (method, params);
235        Err(BackendError::not_supported(format!("Custom method not supported: {method}")).into())
236    }
237}
238
239/// Convenience trait for backends that don't need all capabilities
240#[async_trait]
241pub trait SimpleBackend: Send + Sync + Clone {
242    type Error: StdError + Send + Sync + Into<Error> + From<BackendError> + 'static;
243    type Config: Clone + Send + Sync;
244
245    async fn initialize(config: Self::Config) -> std::result::Result<Self, Self::Error>;
246    fn get_server_info(&self) -> ServerInfo;
247    async fn health_check(&self) -> std::result::Result<(), Self::Error>;
248
249    // Only require tools - other methods have default implementations
250    async fn list_tools(
251        &self,
252        request: PaginatedRequestParam,
253    ) -> std::result::Result<ListToolsResult, Self::Error>;
254    async fn call_tool(
255        &self,
256        request: CallToolRequestParam,
257    ) -> std::result::Result<CallToolResult, Self::Error>;
258}
259
260/// Blanket implementation to convert SimpleBackend to McpBackend
261#[async_trait]
262impl<T> McpBackend for T
263where
264    T: SimpleBackend,
265{
266    type Error = T::Error;
267    type Config = T::Config;
268
269    async fn initialize(config: Self::Config) -> std::result::Result<Self, Self::Error> {
270        T::initialize(config).await
271    }
272
273    fn get_server_info(&self) -> ServerInfo {
274        T::get_server_info(self)
275    }
276
277    async fn health_check(&self) -> std::result::Result<(), Self::Error> {
278        T::health_check(self).await
279    }
280
281    async fn list_tools(
282        &self,
283        request: PaginatedRequestParam,
284    ) -> std::result::Result<ListToolsResult, Self::Error> {
285        T::list_tools(self, request).await
286    }
287
288    async fn call_tool(
289        &self,
290        request: CallToolRequestParam,
291    ) -> std::result::Result<CallToolResult, Self::Error> {
292        T::call_tool(self, request).await
293    }
294
295    // Default implementations for optional capabilities
296    async fn list_resources(
297        &self,
298        _request: PaginatedRequestParam,
299    ) -> std::result::Result<ListResourcesResult, Self::Error> {
300        Ok(ListResourcesResult {
301            resources: vec![],
302            next_cursor: None,
303        })
304    }
305
306    async fn read_resource(
307        &self,
308        request: ReadResourceRequestParam,
309    ) -> std::result::Result<ReadResourceResult, Self::Error> {
310        Err(BackendError::not_supported(format!("Resource not found: {}", request.uri)).into())
311    }
312
313    async fn list_prompts(
314        &self,
315        _request: PaginatedRequestParam,
316    ) -> std::result::Result<ListPromptsResult, Self::Error> {
317        Ok(ListPromptsResult {
318            prompts: vec![],
319            next_cursor: None,
320        })
321    }
322
323    async fn get_prompt(
324        &self,
325        request: GetPromptRequestParam,
326    ) -> std::result::Result<GetPromptResult, Self::Error> {
327        Err(BackendError::not_supported(format!("Prompt not found: {}", request.name)).into())
328    }
329}