remote_mcp_kernel/microkernel/
core.rs

1//! Core microkernel server implementation
2//!
3//! This module contains the main MicrokernelServer implementation
4//! demonstrating the microkernel design pattern.
5
6use axum::Router;
7use std::net::SocketAddr;
8
9use crate::{
10    error::AppResult,
11    handlers::{McpServerHandler, SseHandler, SseHandlerConfig, StreamableHttpHandler},
12};
13use oauth_provider_rs::{DefaultClientManager, OAuthProvider, OAuthProviderTrait, OAuthStorage};
14
15/// Configuration for custom router attachment
16#[derive(Debug, Clone)]
17pub struct CustomRouterConfig {
18    /// Optional name for the custom router (for logging/debugging)
19    pub name: Option<String>,
20    /// Optional path prefix for the router
21    pub path_prefix: Option<String>,
22}
23
24impl Default for CustomRouterConfig {
25    fn default() -> Self {
26        Self {
27            name: None,
28            path_prefix: None,
29        }
30    }
31}
32
33/// Container for custom router with its configuration
34pub struct CustomRouter {
35    router: Router,
36    config: CustomRouterConfig,
37}
38
39/// Microkernel server builder that composes independent handlers
40///
41/// This builder demonstrates the microkernel principle where services
42/// are composed from independent, single-responsibility components.
43/// Now supports any OAuth provider, storage backend, MCP server, and custom routers through trait abstraction.
44pub struct MicrokernelServer<
45    P: OAuthProviderTrait<S, DefaultClientManager<S>> + 'static,
46    S: OAuthStorage + Clone + 'static,
47    M: McpServerHandler = crate::handlers::McpServer,
48> {
49    oauth_provider: Option<OAuthProvider<P, S>>,
50    streamable_handler: Option<StreamableHttpHandler<M>>,
51    sse_handler: Option<SseHandler<M>>,
52    sse_config: SseHandlerConfig,
53    custom_routers: Vec<CustomRouter>,
54}
55
56impl<
57    P: OAuthProviderTrait<S, DefaultClientManager<S>> + 'static,
58    S: OAuthStorage + Clone + 'static,
59    M: McpServerHandler,
60> MicrokernelServer<P, S, M>
61{
62    /// Create a new microkernel server builder
63    pub fn new() -> Self {
64        Self {
65            oauth_provider: None,
66            streamable_handler: None,
67            sse_handler: None,
68            sse_config: SseHandlerConfig::default(),
69            custom_routers: Vec::new(),
70        }
71    }
72
73    /// Add OAuth provider handler
74    pub fn with_oauth_provider(mut self, oauth_provider: OAuthProvider<P, S>) -> Self {
75        self.oauth_provider = Some(oauth_provider);
76        self
77    }
78
79    /// Add streamable HTTP handler
80    pub fn with_streamable_handler(mut self, streamable_handler: StreamableHttpHandler<M>) -> Self {
81        self.streamable_handler = Some(streamable_handler);
82        self
83    }
84
85    /// Add SSE handler with configuration
86    pub fn with_sse_handler(
87        mut self,
88        sse_handler: SseHandler<M>,
89        config: SseHandlerConfig,
90    ) -> Self {
91        self.sse_handler = Some(sse_handler);
92        self.sse_config = config;
93        self
94    }
95
96    /// Create SSE handler with MCP server and configuration
97    pub fn with_mcp_sse_handler(mut self, mcp_server: M, config: SseHandlerConfig) -> Self {
98        let sse_handler = SseHandler::new(mcp_server);
99        self.sse_handler = Some(sse_handler);
100        self.sse_config = config;
101        self
102    }
103
104    /// Create streamable HTTP handler with MCP server
105    pub fn with_mcp_streamable_handler(mut self, mcp_server: M) -> Self {
106        let streamable_handler = StreamableHttpHandler::new(mcp_server);
107        self.streamable_handler = Some(streamable_handler);
108        self
109    }
110
111    /// Attach a custom router to the microkernel server
112    ///
113    /// This method allows attaching arbitrary axum Routers to the microkernel server,
114    /// enabling extension of the server with custom endpoints while maintaining
115    /// microkernel architecture principles.
116    ///
117    /// # Arguments
118    /// * `router` - The axum Router to attach
119    ///
120    /// # Examples
121    /// ```rust
122    /// use axum::{Router, routing::get, response::Html};
123    /// use remote_mcp_kernel::microkernel::GitHubMicrokernelServer;
124    ///
125    /// async fn hello() -> Html<&'static str> {
126    ///     Html("<h1>Hello, World!</h1>")
127    /// }
128    ///
129    /// let custom_router = Router::new()
130    ///     .route("/hello", get(hello));
131    ///
132    /// let server: GitHubMicrokernelServer = GitHubMicrokernelServer::new()
133    ///     .with_custom_router(custom_router);
134    /// ```
135    pub fn with_custom_router(mut self, router: Router) -> Self {
136        let custom_router = CustomRouter {
137            router,
138            config: CustomRouterConfig::default(),
139        };
140        self.custom_routers.push(custom_router);
141        self
142    }
143
144    /// Attach a custom router with configuration to the microkernel server
145    ///
146    /// This method provides more control over how the custom router is attached,
147    /// allowing for custom configuration such as path prefixes and names.
148    ///
149    /// # Arguments
150    /// * `router` - The axum Router to attach
151    /// * `config` - Configuration for the custom router
152    ///
153    /// # Examples
154    /// ```rust
155    /// use axum::{Router, routing::get, response::Html};
156    /// use remote_mcp_kernel::microkernel::{GitHubMicrokernelServer, CustomRouterConfig};
157    ///
158    /// async fn api_status() -> Html<&'static str> {
159    ///     Html("<h1>API Status: OK</h1>")
160    /// }
161    ///
162    /// let custom_router = Router::new()
163    ///     .route("/status", get(api_status));
164    ///
165    /// let config = CustomRouterConfig {
166    ///     name: Some("API Router".to_string()),
167    ///     path_prefix: Some("/api".to_string()),
168    /// };
169    ///
170    /// let server: GitHubMicrokernelServer = GitHubMicrokernelServer::new()
171    ///     .with_custom_router_config(custom_router, config);
172    /// ```
173    pub fn with_custom_router_config(mut self, router: Router, config: CustomRouterConfig) -> Self {
174        let custom_router = CustomRouter { router, config };
175        self.custom_routers.push(custom_router);
176        self
177    }
178
179    /// Attach multiple custom routers at once
180    ///
181    /// This method allows attaching multiple custom routers efficiently.
182    ///
183    /// # Arguments
184    /// * `routers` - A vector of tuples containing (Router, CustomRouterConfig)
185    ///
186    /// # Examples
187    /// ```rust
188    /// use axum::{Router, routing::get, response::Html};
189    /// use remote_mcp_kernel::microkernel::{GitHubMicrokernelServer, CustomRouterConfig};
190    ///
191    /// async fn health() -> Html<&'static str> {
192    ///     Html("<h1>Health: OK</h1>")
193    /// }
194    ///
195    /// async fn metrics() -> Html<&'static str> {
196    ///     Html("<h1>Metrics: OK</h1>")
197    /// }
198    ///
199    /// let health_router = Router::new().route("/health", get(health));
200    /// let metrics_router = Router::new().route("/metrics", get(metrics));
201    ///
202    /// let routers = vec![
203    ///     (health_router, CustomRouterConfig::default()),
204    ///     (metrics_router, CustomRouterConfig {
205    ///         name: Some("Metrics".to_string()),
206    ///         path_prefix: Some("/monitoring".to_string()),
207    ///     }),
208    /// ];
209    ///
210    /// let server: GitHubMicrokernelServer = GitHubMicrokernelServer::new()
211    ///     .with_custom_routers(routers);
212    /// ```
213    pub fn with_custom_routers(mut self, routers: Vec<(Router, CustomRouterConfig)>) -> Self {
214        for (router, config) in routers {
215            let custom_router = CustomRouter { router, config };
216            self.custom_routers.push(custom_router);
217        }
218        self
219    }
220
221    /// Build the composed router from all registered handlers
222    ///
223    /// This method demonstrates the microkernel composition principle
224    /// where independent components are combined into a unified service.
225    /// Now includes support for custom routers with path prefixes and configuration.
226    pub fn build_router(self) -> AppResult<Router> {
227        let mut router = Router::new();
228
229        // Compose OAuth handler if present
230        if let Some(oauth_provider) = self.oauth_provider {
231            let oauth_router = oauth_provider.router();
232            router = router.merge(oauth_router);
233            tracing::debug!("✓ OAuth provider router composed");
234        }
235
236        // Compose Streamable HTTP handler if present
237        if let Some(streamable_handler) = self.streamable_handler {
238            let streamable_router = streamable_handler.router();
239            router = router.merge(streamable_router);
240            tracing::debug!("✓ Streamable HTTP handler router composed");
241        }
242
243        // Compose SSE handler if present
244        if let Some(sse_handler) = self.sse_handler {
245            let sse_router = sse_handler.router(self.sse_config)?;
246            router = router.merge(sse_router);
247            tracing::debug!("✓ SSE handler router composed");
248        }
249
250        // Compose custom routers
251        for (index, custom_router) in self.custom_routers.into_iter().enumerate() {
252            let CustomRouter {
253                router: custom_router_inner,
254                config,
255            } = custom_router;
256
257            let final_router = if let Some(path_prefix) = config.path_prefix {
258                // If a path prefix is specified, nest the router under that path
259                Router::new().nest(&path_prefix, custom_router_inner)
260            } else {
261                // Otherwise, merge directly
262                custom_router_inner
263            };
264
265            router = router.merge(final_router);
266
267            // Log with custom name if provided, otherwise use index
268            if let Some(name) = config.name {
269                tracing::debug!("✓ Custom router '{}' composed", name);
270            } else {
271                tracing::debug!("✓ Custom router #{} composed", index + 1);
272            }
273        }
274
275        Ok(router)
276    }
277
278    /// Start the microkernel server
279    pub async fn serve(self, bind_address: SocketAddr) -> AppResult<()> {
280        let router = self.build_router()?;
281
282        let listener = tokio::net::TcpListener::bind(bind_address)
283            .await
284            .map_err(|e| crate::error::AppError::Internal(format!("Failed to bind: {}", e)))?;
285
286        tracing::info!("🏛️  Microkernel server listening on {}", bind_address);
287
288        axum::serve(listener, router)
289            .await
290            .map_err(|e| crate::error::AppError::Internal(format!("Server error: {}", e)))?;
291
292        Ok(())
293    }
294}
295
296impl<
297    P: OAuthProviderTrait<S, DefaultClientManager<S>>,
298    S: OAuthStorage + Clone + 'static,
299    M: McpServerHandler,
300> Default for MicrokernelServer<P, S, M>
301{
302    fn default() -> Self {
303        Self::new()
304    }
305}
306
307impl CustomRouterConfig {
308    /// Create a new custom router configuration with a name
309    pub fn with_name(name: impl Into<String>) -> Self {
310        Self {
311            name: Some(name.into()),
312            path_prefix: None,
313        }
314    }
315
316    /// Create a new custom router configuration with a path prefix
317    pub fn with_prefix(path_prefix: impl Into<String>) -> Self {
318        Self {
319            name: None,
320            path_prefix: Some(path_prefix.into()),
321        }
322    }
323
324    /// Create a new custom router configuration with both name and path prefix
325    pub fn with_name_and_prefix(name: impl Into<String>, path_prefix: impl Into<String>) -> Self {
326        Self {
327            name: Some(name.into()),
328            path_prefix: Some(path_prefix.into()),
329        }
330    }
331}