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}