1use crate::{
10 AppError, DynamicRouterService, get_proxy_manager,
11 model::GLOBAL_RESTART_TRACKER,
12 model::{
13 CheckMcpStatusResponseStatus, McpConfig, McpProtocol, McpProtocolPath, McpRouterPath,
14 McpServerCommandConfig, McpServerConfig, McpServiceStatus, McpType,
15 },
16 proxy::{
17 McpHandler, SseBackendConfig, SseServerBuilder, StreamBackendConfig, StreamServerBuilder,
18 },
19};
20
21use anyhow::{Context, Result};
22use log::{debug, info};
23
24pub async fn mcp_start_task(
30 mcp_config: McpConfig,
31) -> Result<(axum::Router, tokio_util::sync::CancellationToken)> {
32 let mcp_id = mcp_config.mcp_id.clone();
33 let client_protocol = mcp_config.client_protocol.clone();
34
35 let mcp_router_path: McpRouterPath = McpRouterPath::new(mcp_id, client_protocol)
37 .map_err(|e| AppError::mcp_server_error(e.to_string()))?;
38
39 let mcp_json_config = mcp_config
40 .mcp_json_config
41 .clone()
42 .expect("mcp_json_config is required");
43
44 let mcp_server_config = McpServerConfig::try_from(mcp_json_config)?;
45
46 integrate_server_with_axum(
48 mcp_server_config.clone(),
49 mcp_router_path.clone(),
50 mcp_config.clone(),
51 )
52 .await
53}
54
55pub async fn integrate_server_with_axum(
63 mcp_config: McpServerConfig,
64 mcp_router_path: McpRouterPath,
65 full_mcp_config: McpConfig,
66) -> Result<(axum::Router, tokio_util::sync::CancellationToken)> {
67 let mcp_type = full_mcp_config.mcp_type.clone();
68 let base_path = mcp_router_path.base_path.clone();
69 let mcp_id = mcp_router_path.mcp_id.clone();
70
71 let backend_protocol = match &mcp_config {
73 McpServerConfig::Command(_) => McpProtocol::Stdio,
75 McpServerConfig::Url(url_config) => {
77 let mut detection_headers = url_config.headers.clone().unwrap_or_default();
79 if let Some(auth_token) = &url_config.auth_token {
80 detection_headers.insert("Authorization".to_string(), auth_token.clone());
81 }
82 let detection_headers_ref = if detection_headers.is_empty() {
83 None
84 } else {
85 Some(&detection_headers)
86 };
87
88 if let Some(type_str) = &url_config.r#type {
90 match type_str.parse::<McpProtocol>() {
91 Ok(protocol) => {
92 debug!(
93 "Using configured protocol type: {} -> {:?}",
94 type_str, protocol
95 );
96 protocol
97 }
98 Err(_) => {
99 debug!("Protocol type '{}' unrecognized, auto-detecting", type_str);
101 let detected_protocol = crate::server::detect_mcp_protocol_with_headers(
102 url_config.get_url(),
103 detection_headers_ref,
104 )
105 .await
106 .map_err(|e| {
107 anyhow::anyhow!(
108 "Protocol type '{}' unrecognized and auto-detection failed: {}",
109 type_str,
110 e
111 )
112 })?;
113 debug!(
114 "Auto-detected protocol: {:?} (original config: '{}')",
115 detected_protocol, type_str
116 );
117 detected_protocol
118 }
119 }
120 } else {
121 debug!("No type field specified, auto-detecting protocol");
123
124 crate::server::detect_mcp_protocol_with_headers(
125 url_config.get_url(),
126 detection_headers_ref,
127 )
128 .await
129 .map_err(|e| anyhow::anyhow!("Auto-detection failed: {}", e))?
130 }
131 }
132 };
133
134 debug!(
135 "MCP ID: {}, client protocol: {:?}, backend protocol: {:?}",
136 mcp_id, mcp_router_path.mcp_protocol, backend_protocol
137 );
138
139 let (router, ct, handler) = match mcp_router_path.mcp_protocol.clone() {
141 McpProtocol::Sse => {
143 let sse_path = match &mcp_router_path.mcp_protocol_path {
144 McpProtocolPath::SsePath(sse_path) => sse_path,
145 _ => unreachable!(),
146 };
147
148 let backend_config = if matches!(backend_protocol, McpProtocol::Stream) {
150 let bridge = connect_stream_backend(&mcp_config, &mcp_id).await?;
153 SseBackendConfig::BackendBridge(bridge)
154 } else {
155 build_sse_backend_config(&mcp_config, backend_protocol)?
156 };
157
158 debug!(
159 "Creating SSE server, sse_path={}, post_path={}",
160 sse_path.sse_path, sse_path.message_path
161 );
162
163 let keep_alive_secs = if matches!(mcp_type, McpType::OneShot) {
166 5
167 } else {
168 15
169 };
170
171 let stateful = !matches!(mcp_type, McpType::OneShot);
174
175 let (router, ct, handler) = SseServerBuilder::new(backend_config)
176 .mcp_id(mcp_id.clone())
177 .sse_path(sse_path.sse_path.clone())
178 .post_path(sse_path.message_path.clone())
179 .keep_alive(keep_alive_secs)
180 .stateful(stateful)
181 .build()
182 .await
183 .with_context(|| {
184 format!(
185 "SSE server build failed - MCP ID: {}, type: {:?}",
186 mcp_id, mcp_type
187 )
188 })?;
189
190 info!(
191 "SSE server started - MCP ID: {}, type: {:?}",
192 mcp_router_path.mcp_id, mcp_type
193 );
194
195 (router, ct, McpHandler::Sse(Box::new(handler)))
196 }
197
198 McpProtocol::Stream => {
200 let backend_config = build_stream_backend_config(&mcp_config, backend_protocol)?;
202
203 let (router, ct, handler) = StreamServerBuilder::new(backend_config)
204 .mcp_id(mcp_id.clone())
205 .stateful(false)
206 .build()
207 .await
208 .with_context(|| {
209 format!(
210 "Stream server build failed - MCP ID: {}, type: {:?}",
211 mcp_id, mcp_type
212 )
213 })?;
214
215 info!(
216 "Streamable HTTP server started - MCP ID: {}, type: {:?}",
217 mcp_router_path.mcp_id, mcp_type
218 );
219
220 (router, ct, McpHandler::Stream(Box::new(handler)))
221 }
222
223 McpProtocol::Stdio => {
225 return Err(anyhow::anyhow!(
226 "Client protocol cannot be Stdio. McpRouterPath::new does not support creating Stdio protocol router paths"
227 ));
228 }
229 };
230
231 let ct_clone = ct.clone();
233 let mcp_id_clone = mcp_id.clone();
234
235 let mcp_service_status = McpServiceStatus::new(
237 mcp_id_clone.clone(),
238 mcp_type.clone(),
239 mcp_router_path.clone(),
240 ct_clone.clone(),
241 CheckMcpStatusResponseStatus::Ready,
242 )
243 .with_mcp_config(full_mcp_config.clone());
244
245 let proxy_manager = get_proxy_manager();
247 proxy_manager.add_mcp_service_status_and_proxy(mcp_service_status, Some(handler));
248
249 proxy_manager
251 .register_mcp_config(&mcp_id, full_mcp_config.clone())
252 .await;
253
254 let router = if matches!(mcp_router_path.mcp_protocol, McpProtocol::Sse) {
256 let modified_router = router.fallback(base_path_fallback_handler);
257 info!("SSE base path handler added, base_path: {}", base_path);
258 modified_router
259 } else {
260 router
261 };
262
263 info!(
265 "Registering route: base_path={}, mcp_id={}",
266 base_path, mcp_id
267 );
268 info!(
269 "SSE path config: sse_path={}, post_path={}",
270 match &mcp_router_path.mcp_protocol_path {
271 McpProtocolPath::SsePath(sse_path) => &sse_path.sse_path,
272 _ => "N/A",
273 },
274 match &mcp_router_path.mcp_protocol_path {
275 McpProtocolPath::SsePath(sse_path) => &sse_path.message_path,
276 _ => "N/A",
277 }
278 );
279 DynamicRouterService::register_route(&base_path, router.clone());
280 info!("Route registration complete: base_path={}", base_path);
281
282 GLOBAL_RESTART_TRACKER.record_restart(&mcp_id);
284
285 Ok((router, ct))
286}
287
288async fn connect_stream_backend(
294 mcp_config: &McpServerConfig,
295 mcp_id: &str,
296) -> Result<std::sync::Arc<dyn mcp_common::BackendBridge>> {
297 use crate::proxy::{StreamClientConnection, StreamProxyHandler};
298
299 let url_config = match mcp_config {
300 McpServerConfig::Url(url_config) => url_config,
301 _ => {
302 return Err(anyhow::anyhow!(
303 "Stream backend requires URL-based config"
304 ))
305 }
306 };
307
308 let url = url_config.get_url();
309 info!(
310 "Connecting to Streamable HTTP backend (SSE frontend) \
311 - MCP ID: {}, URL: {}",
312 mcp_id, url
313 );
314
315 let mut config = mcp_common::McpClientConfig::new(url.to_string());
316 if let Some(ref headers) = url_config.headers {
317 for (k, v) in headers {
318 config = config.with_header(k, v);
319 }
320 }
321 if let Some(ref auth_token) = url_config.auth_token {
323 config = config.with_header("Authorization", auth_token);
324 }
325
326 let conn = StreamClientConnection::connect(config).await?;
327 let proxy_handler =
328 StreamProxyHandler::with_mcp_id(conn.into_running_service(), mcp_id.to_string());
329
330 Ok(std::sync::Arc::new(proxy_handler))
331}
332
333fn build_sse_backend_config(
335 mcp_config: &McpServerConfig,
336 backend_protocol: McpProtocol,
337) -> Result<SseBackendConfig> {
338 match mcp_config {
339 McpServerConfig::Command(cmd_config) => {
340 log_command_details(cmd_config);
341 Ok(SseBackendConfig::Stdio {
342 command: cmd_config.command.clone(),
343 args: cmd_config.args.clone(),
344 env: cmd_config.env.clone(),
345 })
346 }
347 McpServerConfig::Url(url_config) => match backend_protocol {
348 McpProtocol::Stdio => Err(anyhow::anyhow!(
349 "URL-based MCP service cannot use Stdio protocol"
350 )),
351 McpProtocol::Sse => {
352 info!("Connecting to SSE backend: {}", url_config.get_url());
353 Ok(SseBackendConfig::SseUrl {
354 url: url_config.get_url().to_string(),
355 headers: url_config.headers.clone(),
356 })
357 }
358 McpProtocol::Stream => Err(anyhow::anyhow!(
359 "Stream backend should be handled via connect_stream_backend(), \
360 not build_sse_backend_config()"
361 ))
362 },
363 }
364}
365
366fn build_stream_backend_config(
368 mcp_config: &McpServerConfig,
369 backend_protocol: McpProtocol,
370) -> Result<StreamBackendConfig> {
371 match mcp_config {
372 McpServerConfig::Command(cmd_config) => {
373 log_command_details(cmd_config);
374 Ok(StreamBackendConfig::Stdio {
375 command: cmd_config.command.clone(),
376 args: cmd_config.args.clone(),
377 env: cmd_config.env.clone(),
378 })
379 }
380 McpServerConfig::Url(url_config) => {
381 match backend_protocol {
382 McpProtocol::Stdio => Err(anyhow::anyhow!(
383 "URL-based MCP service cannot use Stdio protocol"
384 )),
385 McpProtocol::Sse => {
386 Err(anyhow::anyhow!(
390 "SSE backend with Streamable HTTP frontend is not yet supported. \
391 Please use SSE frontend or configure a Streamable HTTP backend."
392 ))
393 }
394 McpProtocol::Stream => {
395 info!(
396 "Connecting to Streamable HTTP backend: {}",
397 url_config.get_url()
398 );
399 Ok(StreamBackendConfig::Url {
400 url: url_config.get_url().to_string(),
401 headers: url_config.headers.clone(),
402 })
403 }
404 }
405 }
406 }
407}
408
409fn log_command_details(mcp_config: &McpServerCommandConfig) {
411 let args_str = mcp_config
412 .args
413 .as_ref()
414 .map_or(String::new(), |args| args.join(" "));
415
416 info!("Executing command: {} {}", mcp_config.command, args_str);
417
418 if let Some(env_vars) = &mcp_config.env {
420 let keys: Vec<&String> = env_vars.keys().collect();
421 if !keys.is_empty() {
422 debug!("Config env keys: {:?}", keys);
423 }
424 }
425
426 debug!(
428 "Process PATH: {}",
429 mcp_common::diagnostic::format_path_summary(3)
430 );
431 for (key, val) in mcp_common::diagnostic::collect_mirror_env_vars() {
432 debug!("Process env: {}={}", key, val);
433 }
434}
435
436#[axum::debug_handler]
438async fn base_path_fallback_handler(
439 method: axum::http::Method,
440 uri: axum::http::Uri,
441 headers: axum::http::HeaderMap,
442) -> impl axum::response::IntoResponse {
443 let path = uri.path();
444 info!("Base path handler: {} {}", method, path);
445
446 if path.contains("/sse/proxy/") {
448 match method {
450 axum::http::Method::GET => {
451 let mcp_id = path.split("/sse/proxy/").nth(1);
453
454 if let Some(mcp_id) = mcp_id {
455 let proxy_manager = get_proxy_manager();
457 if proxy_manager.get_mcp_service_status(mcp_id).is_none() {
458 (
460 axum::http::StatusCode::NOT_FOUND,
461 [("Content-Type", "text/plain".to_string())],
462 format!("MCP service '{}' not found", mcp_id).to_string(),
463 )
464 } else {
465 let accept_header = headers.get("accept");
467 if let Some(accept) = accept_header {
468 let accept_str = accept.to_str().unwrap_or("");
469 if accept_str.contains("text/event-stream") {
470 let redirect_uri = format!("{}/sse", path);
472 info!("SSE redirect to: {}", redirect_uri);
473 (
474 axum::http::StatusCode::FOUND,
475 [("Location", redirect_uri.to_string())],
476 "Redirecting to SSE endpoint".to_string(),
477 )
478 } else {
479 (
481 axum::http::StatusCode::BAD_REQUEST,
482 [("Content-Type", "text/plain".to_string())],
483 "SSE error: Invalid Accept header, expected 'text/event-stream'".to_string(),
484 )
485 }
486 } else {
487 (
489 axum::http::StatusCode::BAD_REQUEST,
490 [("Content-Type", "text/plain".to_string())],
491 "SSE error: Missing Accept header, expected 'text/event-stream'"
492 .to_string(),
493 )
494 }
495 }
496 } else {
497 (
499 axum::http::StatusCode::BAD_REQUEST,
500 [("Content-Type", "text/plain".to_string())],
501 "SSE error: Invalid SSE path".to_string(),
502 )
503 }
504 }
505 axum::http::Method::POST => {
506 let redirect_uri = format!("{}/message", path);
508 info!("SSE redirect to: {}", redirect_uri);
509 (
510 axum::http::StatusCode::FOUND,
511 [("Location", redirect_uri.to_string())],
512 "Redirecting to message endpoint".to_string(),
513 )
514 }
515 _ => {
516 (
518 axum::http::StatusCode::METHOD_NOT_ALLOWED,
519 [("Allow", "GET, POST".to_string())],
520 "Only GET and POST methods are allowed".to_string(),
521 )
522 }
523 }
524 } else if path.contains("/stream/proxy/") {
525 match method {
527 axum::http::Method::GET => {
528 (
530 axum::http::StatusCode::OK,
531 [("Content-Type", "application/json".to_string())],
532 r#"{"jsonrpc":"2.0","result":{"info":"Streamable MCP Server","version":"1.0"}}"#.to_string(),
533 )
534 }
535 axum::http::Method::POST => {
536 (
538 axum::http::StatusCode::OK,
539 [("Content-Type", "application/json".to_string())],
540 r#"{"jsonrpc":"2.0","result":{"message":"Stream request received","protocol":"streamable-http"}}"#.to_string(),
541 )
542 }
543 _ => {
544 (
546 axum::http::StatusCode::METHOD_NOT_ALLOWED,
547 [("Allow", "GET, POST".to_string())],
548 "Only GET and POST methods are allowed".to_string(),
549 )
550 }
551 }
552 } else {
553 (
555 axum::http::StatusCode::BAD_REQUEST,
556 [("Content-Type", "text/plain".to_string())],
557 "Unknown protocol or path".to_string(),
558 )
559 }
560}