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}