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 { router: custom_router_inner, config } = custom_router;
253            
254            let final_router = if let Some(path_prefix) = config.path_prefix {
255                // If a path prefix is specified, nest the router under that path
256                Router::new().nest(&path_prefix, custom_router_inner)
257            } else {
258                // Otherwise, merge directly
259                custom_router_inner
260            };
261
262            router = router.merge(final_router);
263            
264            // Log with custom name if provided, otherwise use index
265            if let Some(name) = config.name {
266                tracing::debug!("✓ Custom router '{}' composed", name);
267            } else {
268                tracing::debug!("✓ Custom router #{} composed", index + 1);
269            }
270        }
271
272        Ok(router)
273    }
274
275    /// Start the microkernel server
276    pub async fn serve(self, bind_address: SocketAddr) -> AppResult<()> {
277        let router = self.build_router()?;
278
279        let listener = tokio::net::TcpListener::bind(bind_address)
280            .await
281            .map_err(|e| crate::error::AppError::Internal(format!("Failed to bind: {}", e)))?;
282
283        tracing::info!("🏛️  Microkernel server listening on {}", bind_address);
284
285        axum::serve(listener, router)
286            .await
287            .map_err(|e| crate::error::AppError::Internal(format!("Server error: {}", e)))?;
288
289        Ok(())
290    }
291}
292
293impl<
294    P: OAuthProviderTrait<S, DefaultClientManager<S>>,
295    S: OAuthStorage + Clone + 'static,
296    M: McpServerHandler,
297> Default for MicrokernelServer<P, S, M>
298{
299    fn default() -> Self {
300        Self::new()
301    }
302}
303
304impl CustomRouterConfig {
305    /// Create a new custom router configuration with a name
306    pub fn with_name(name: impl Into<String>) -> Self {
307        Self {
308            name: Some(name.into()),
309            path_prefix: None,
310        }
311    }
312
313    /// Create a new custom router configuration with a path prefix
314    pub fn with_prefix(path_prefix: impl Into<String>) -> Self {
315        Self {
316            name: None,
317            path_prefix: Some(path_prefix.into()),
318        }
319    }
320
321    /// Create a new custom router configuration with both name and path prefix
322    pub fn with_name_and_prefix(name: impl Into<String>, path_prefix: impl Into<String>) -> Self {
323        Self {
324            name: Some(name.into()),
325            path_prefix: Some(path_prefix.into()),
326        }
327    }
328}