use axum::Router;
use dashmap::DashMap;
use log::{debug, error, info};
use moka::future::Cache;
use once_cell::sync::Lazy;
use std::sync::Arc;
use tokio::sync::{Mutex, OwnedMutexGuard};
use tokio::time::{Duration, Instant};
use tokio_util::sync::CancellationToken;
use tracing::warn;
use anyhow::Result;
use crate::proxy::McpHandler;
use super::{CheckMcpStatusResponseStatus, McpConfig, McpProtocol, McpRouterPath, McpType};
pub static GLOBAL_ROUTES: Lazy<Arc<DashMap<String, Router>>> =
Lazy::new(|| Arc::new(DashMap::new()));
pub static GLOBAL_PROXY_MANAGER: Lazy<ProxyHandlerManager> =
Lazy::new(ProxyHandlerManager::default);
#[derive(Clone)]
pub struct DynamicRouterService(pub McpProtocol);
impl DynamicRouterService {
pub fn register_route(path: &str, handler: Router) {
debug!("=== Register Route ===");
debug!("Registration path: {}", path);
GLOBAL_ROUTES.insert(path.to_string(), handler);
debug!("=== Route registration completed ===");
}
pub fn delete_route(path: &str) {
debug!("=== Delete route ===");
debug!("Delete path: {}", path);
GLOBAL_ROUTES.remove(path);
debug!("=== Route deletion completed ===");
}
pub fn get_route(path: &str) -> Option<Router> {
let result = GLOBAL_ROUTES.get(path).map(|entry| entry.value().clone());
if result.is_some() {
debug!("get_route('{}') = Some(Router)", path);
} else {
debug!("get_route('{}') = None", path);
}
result
}
pub fn get_all_routes() -> Vec<String> {
GLOBAL_ROUTES
.iter()
.map(|entry| entry.key().clone())
.collect()
}
}
impl std::fmt::Debug for DynamicRouterService {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let routes = GLOBAL_ROUTES
.iter()
.map(|entry| entry.key().clone())
.collect::<Vec<_>>();
write!(f, "DynamicRouterService {{ routes: {routes:?} }}")
}
}
pub struct McpProcessGuard {
mcp_id: String,
cancellation_token: CancellationToken,
}
impl McpProcessGuard {
pub fn new(mcp_id: String, cancellation_token: CancellationToken) -> Self {
debug!("[RAII] Create process daemon: mcp_id={}", mcp_id);
Self {
mcp_id,
cancellation_token,
}
}
pub fn clone_token(&self) -> CancellationToken {
self.cancellation_token.clone()
}
}
impl Drop for McpProcessGuard {
fn drop(&mut self) {
info!(
"[RAII] The process daemon was dropped and canceled CancellationToken: mcp_id={}",
self.mcp_id
);
self.cancellation_token.cancel();
}
}
impl std::fmt::Debug for McpProcessGuard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("McpProcessGuard")
.field("mcp_id", &self.mcp_id)
.field("is_cancelled", &self.cancellation_token.is_cancelled())
.finish()
}
}
pub struct McpService {
process_guard: McpProcessGuard,
handler: Option<McpHandler>,
status: McpServiceStatusInfo,
}
#[derive(Debug, Clone)]
pub struct McpServiceStatusInfo {
pub mcp_id: String,
pub mcp_type: McpType,
pub mcp_router_path: McpRouterPath,
pub check_mcp_status_response_status: CheckMcpStatusResponseStatus,
pub last_accessed: Instant,
pub mcp_config: Option<McpConfig>,
pub consecutive_probe_failures: u32,
}
impl McpServiceStatusInfo {
pub fn new(
mcp_id: String,
mcp_type: McpType,
mcp_router_path: McpRouterPath,
check_mcp_status_response_status: CheckMcpStatusResponseStatus,
) -> Self {
Self {
mcp_id,
mcp_type,
mcp_router_path,
check_mcp_status_response_status,
last_accessed: Instant::now(),
mcp_config: None,
consecutive_probe_failures: 0,
}
}
pub fn update_last_accessed(&mut self) {
self.last_accessed = Instant::now();
}
pub fn reset_probe_failures(&mut self) {
self.consecutive_probe_failures = 0;
}
pub fn increment_probe_failures(&mut self) -> u32 {
self.consecutive_probe_failures += 1;
self.consecutive_probe_failures
}
}
impl McpService {
pub fn new(
mcp_id: String,
mcp_type: McpType,
mcp_router_path: McpRouterPath,
cancellation_token: CancellationToken,
) -> Self {
let process_guard = McpProcessGuard::new(mcp_id.clone(), cancellation_token);
let status = McpServiceStatusInfo::new(
mcp_id,
mcp_type,
mcp_router_path,
CheckMcpStatusResponseStatus::Pending,
);
Self {
process_guard,
handler: None,
status,
}
}
pub fn set_handler(&mut self, handler: McpHandler) {
self.handler = Some(handler);
}
pub fn handler(&self) -> Option<&McpHandler> {
self.handler.as_ref()
}
pub fn status(&self) -> &McpServiceStatusInfo {
&self.status
}
pub fn status_mut(&mut self) -> &mut McpServiceStatusInfo {
&mut self.status
}
pub fn clone_token(&self) -> CancellationToken {
self.process_guard.clone_token()
}
pub fn update_status(&mut self, status: CheckMcpStatusResponseStatus) {
self.status.check_mcp_status_response_status = status;
}
pub fn update_last_accessed(&mut self) {
self.status.update_last_accessed();
}
pub fn set_mcp_config(&mut self, config: McpConfig) {
self.status.mcp_config = Some(config);
}
}
impl std::fmt::Debug for McpService {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("McpService")
.field("process_guard", &self.process_guard)
.field("handler", &self.handler.is_some())
.field("status", &self.status)
.finish()
}
}
#[derive(Debug, Clone)]
pub struct McpServiceStatus {
pub mcp_id: String,
pub mcp_type: McpType,
pub mcp_router_path: McpRouterPath,
pub cancellation_token: CancellationToken,
pub check_mcp_status_response_status: CheckMcpStatusResponseStatus,
pub last_accessed: Instant,
pub mcp_config: Option<McpConfig>,
pub consecutive_probe_failures: u32,
}
impl McpServiceStatus {
pub fn new(
mcp_id: String,
mcp_type: McpType,
mcp_router_path: McpRouterPath,
cancellation_token: CancellationToken,
check_mcp_status_response_status: CheckMcpStatusResponseStatus,
) -> Self {
Self {
mcp_id,
mcp_type,
mcp_router_path,
cancellation_token,
check_mcp_status_response_status,
last_accessed: Instant::now(),
mcp_config: None,
consecutive_probe_failures: 0,
}
}
pub fn with_mcp_config(mut self, mcp_config: McpConfig) -> Self {
self.mcp_config = Some(mcp_config);
self
}
pub fn update_last_accessed(&mut self) {
self.last_accessed = Instant::now();
}
}
#[derive(Debug)]
pub struct ProxyHandlerManager {
services: DashMap<String, McpService>,
}
impl Default for ProxyHandlerManager {
fn default() -> Self {
ProxyHandlerManager {
services: DashMap::new(),
}
}
}
impl ProxyHandlerManager {
pub fn add_mcp_service(
&self,
mcp_id: String,
mcp_type: McpType,
mcp_router_path: McpRouterPath,
cancellation_token: CancellationToken,
) {
let service = McpService::new(
mcp_id.clone(),
mcp_type,
mcp_router_path,
cancellation_token,
);
if let Some(old_service) = self.services.insert(mcp_id.clone(), service) {
info!(
"[RAII] Overwrite existing services, old services will be automatically cleaned up: mcp_id={}",
mcp_id
);
drop(old_service);
}
}
pub fn add_mcp_service_status_and_proxy(
&self,
mcp_service_status: McpServiceStatus,
proxy_handler: Option<McpHandler>,
) {
let mcp_id = mcp_service_status.mcp_id.clone();
let mut service = McpService::new(
mcp_id.clone(),
mcp_service_status.mcp_type,
mcp_service_status.mcp_router_path,
mcp_service_status.cancellation_token,
);
service.status_mut().check_mcp_status_response_status =
mcp_service_status.check_mcp_status_response_status;
if let Some(config) = mcp_service_status.mcp_config {
service.set_mcp_config(config);
}
if let Some(handler) = proxy_handler {
service.set_handler(handler);
}
if let Some(old_service) = self.services.insert(mcp_id.clone(), service) {
info!(
"[RAII] Overwrite existing services, old services will be automatically cleaned up: mcp_id={}",
mcp_id
);
drop(old_service);
}
}
pub fn get_all_mcp_service_status(&self) -> Vec<McpServiceStatus> {
let keys: Vec<String> = self
.services
.iter()
.map(|entry| entry.key().clone())
.collect();
keys.into_iter()
.filter_map(|mcp_id| self.get_mcp_service_status(&mcp_id))
.collect()
}
pub fn get_mcp_service_status(&self, mcp_id: &str) -> Option<McpServiceStatus> {
self.services.get(mcp_id).map(|entry| {
let service = entry.value();
let status = service.status();
McpServiceStatus {
mcp_id: status.mcp_id.clone(),
mcp_type: status.mcp_type.clone(),
mcp_router_path: status.mcp_router_path.clone(),
cancellation_token: service.clone_token(),
check_mcp_status_response_status: status.check_mcp_status_response_status.clone(),
last_accessed: status.last_accessed,
mcp_config: status.mcp_config.clone(),
consecutive_probe_failures: status.consecutive_probe_failures,
}
})
}
pub fn update_last_accessed(&self, mcp_id: &str) {
self.services
.entry(mcp_id.to_string())
.and_modify(|service| service.update_last_accessed());
}
pub fn update_mcp_service_status(&self, mcp_id: &str, status: CheckMcpStatusResponseStatus) {
self.services
.entry(mcp_id.to_string())
.and_modify(|service| service.update_status(status));
}
pub fn get_proxy_handler(&self, mcp_id: &str) -> Option<McpHandler> {
self.services
.get(mcp_id)
.and_then(|entry| entry.value().handler().cloned())
}
pub fn get_mcp_config(&self, mcp_id: &str) -> Option<McpConfig> {
self.services
.get(mcp_id)
.and_then(|entry| entry.value().status().mcp_config.clone())
}
pub fn add_proxy_handler(&self, mcp_id: &str, proxy_handler: McpHandler) {
match self.services.entry(mcp_id.to_string()) {
dashmap::mapref::entry::Entry::Occupied(mut entry) => {
entry.get_mut().set_handler(proxy_handler);
}
dashmap::mapref::entry::Entry::Vacant(_) => {
warn!(
"[RAII] Trying to add handler to non-existent service: mcp_id={}",
mcp_id
);
}
}
}
pub fn contains_service(&self, mcp_id: &str) -> bool {
self.services.contains_key(mcp_id)
}
pub fn service_count(&self) -> usize {
self.services.len()
}
pub async fn register_mcp_config(&self, mcp_id: &str, config: McpConfig) {
GLOBAL_MCP_CONFIG_CACHE
.insert(mcp_id.to_string(), config)
.await;
info!("MCP configuration registered in cache: {}", mcp_id);
}
pub async fn get_mcp_config_from_cache(&self, mcp_id: &str) -> Option<McpConfig> {
if let Some(config) = GLOBAL_MCP_CONFIG_CACHE.get(mcp_id).await {
debug!("Get MCP configuration from cache: {}", mcp_id);
Some(config)
} else {
debug!("MCP configuration not found in cache: {}", mcp_id);
None
}
}
pub async fn unregister_mcp_config(&self, mcp_id: &str) {
GLOBAL_MCP_CONFIG_CACHE.invalidate(mcp_id).await;
info!("MCP configuration removed from cache: {}", mcp_id);
}
pub async fn cleanup_resources(&self, mcp_id: &str) -> Result<()> {
info!("[RAII] Start cleaning up resources: mcp_id={}", mcp_id);
let mcp_sse_router_path = McpRouterPath::new(mcp_id.to_string(), McpProtocol::Sse)
.map_err(|e| {
anyhow::anyhow!("Failed to create SSE router path for {}: {}", mcp_id, e)
})?;
let base_sse_path = mcp_sse_router_path.base_path;
let mcp_stream_router_path = McpRouterPath::new(mcp_id.to_string(), McpProtocol::Stream)
.map_err(|e| {
anyhow::anyhow!("Failed to create Stream router path for {}: {}", mcp_id, e)
})?;
let base_stream_path = mcp_stream_router_path.base_path;
DynamicRouterService::delete_route(&base_sse_path);
DynamicRouterService::delete_route(&base_stream_path);
if self.services.remove(mcp_id).is_some() {
info!(
"[RAII] The service has been removed from the map, McpProcessGuard will automatically cancel the token: mcp_id={}",
mcp_id
);
} else {
debug!(
"[RAII] Service does not exist, skip removal: mcp_id={}",
mcp_id
);
}
self.unregister_mcp_config(mcp_id).await;
GLOBAL_RESTART_TRACKER.clear_health_status(mcp_id);
GLOBAL_RESTART_TRACKER.cleanup_startup_lock(mcp_id);
GLOBAL_RESTART_TRACKER.prune_stale_restarts();
info!(
"[RAII] MCP service resource cleanup completed: mcp_id={}",
mcp_id
);
Ok(())
}
pub async fn cleanup_all_resources(&self) -> Result<()> {
info!("[RAII] Start cleaning up all MCP service resources");
let mcp_ids: Vec<String> = self
.services
.iter()
.map(|entry| entry.key().clone())
.collect();
let count = mcp_ids.len();
for mcp_id in mcp_ids {
if let Err(e) = self.cleanup_resources(&mcp_id).await {
error!(
"[RAII] Failed to clean up resources: mcp_id={}, error={}",
mcp_id, e
);
}
}
info!(
"[RAII] All MCP service resources have been cleaned up, and a total of {} services have been cleaned up.",
count
);
Ok(())
}
pub fn remove_service(&self, mcp_id: &str) -> bool {
if self.services.remove(mcp_id).is_some() {
info!(
"[RAII] The service has been removed and the process will be automatically cleaned up: mcp_id={}",
mcp_id
);
true
} else {
debug!("[RAII] Service does not exist: mcp_id={}", mcp_id);
false
}
}
pub fn reset_probe_failures(&self, mcp_id: &str) {
if let Some(mut entry) = self.services.get_mut(mcp_id) {
entry.value_mut().status_mut().reset_probe_failures();
}
}
pub fn increment_probe_failures(&self, mcp_id: &str) -> u32 {
self.services
.get_mut(mcp_id)
.map(|mut entry| entry.value_mut().status_mut().increment_probe_failures())
.unwrap_or(0)
}
pub async fn cleanup_resources_for_restart(&self, mcp_id: &str) -> Result<()> {
info!(
"[RAII] Start cleaning up resources for restart: mcp_id={}",
mcp_id
);
let mcp_sse_router_path = McpRouterPath::new(mcp_id.to_string(), McpProtocol::Sse)
.map_err(|e| {
anyhow::anyhow!("Failed to create SSE router path for {}: {}", mcp_id, e)
})?;
let base_sse_path = mcp_sse_router_path.base_path;
let mcp_stream_router_path = McpRouterPath::new(mcp_id.to_string(), McpProtocol::Stream)
.map_err(|e| {
anyhow::anyhow!("Failed to create Stream router path for {}: {}", mcp_id, e)
})?;
let base_stream_path = mcp_stream_router_path.base_path;
DynamicRouterService::delete_route(&base_sse_path);
DynamicRouterService::delete_route(&base_stream_path);
if self.services.remove(mcp_id).is_some() {
info!(
"[RAII] The service has been removed from the map (for restart), McpProcessGuard will automatically cancel the token: mcp_id={}",
mcp_id
);
} else {
debug!(
"[RAII] Service does not exist, skip removal: mcp_id={}",
mcp_id
);
}
GLOBAL_RESTART_TRACKER.clear_health_status(mcp_id);
info!(
"[RAII] MCP service resource cleanup completed (for restart): mcp_id={}",
mcp_id
);
Ok(())
}
}
pub struct McpConfigCache {
cache: Cache<String, McpConfig>,
}
impl McpConfigCache {
pub fn new() -> Self {
Self {
cache: Cache::builder()
.time_to_live(Duration::from_secs(24 * 60 * 60)) .max_capacity(1000) .build(),
}
}
pub async fn insert(&self, mcp_id: String, config: McpConfig) {
self.cache.insert(mcp_id.clone(), config).await;
info!("MCP configuration cached: {} (TTL: 24h)", mcp_id);
}
pub async fn get(&self, mcp_id: &str) -> Option<McpConfig> {
self.cache.get(mcp_id).await
}
pub async fn invalidate(&self, mcp_id: &str) {
self.cache.invalidate(mcp_id).await;
}
#[allow(dead_code)]
pub fn invalidate_all(&self) {
self.cache.invalidate_all();
}
}
impl Default for McpConfigCache {
fn default() -> Self {
Self::new()
}
}
pub static GLOBAL_MCP_CONFIG_CACHE: Lazy<McpConfigCache> = Lazy::new(McpConfigCache::default);
pub struct RestartTracker {
last_restart: DashMap<String, Instant>,
health_status: DashMap<String, (bool, Instant)>,
startup_locks: DashMap<String, Arc<Mutex<()>>>,
}
impl RestartTracker {
pub fn new() -> Self {
Self {
last_restart: DashMap::new(),
health_status: DashMap::new(),
startup_locks: DashMap::new(),
}
}
pub fn get_cached_health_status(&self, mcp_id: &str) -> Option<bool> {
let cache_duration = Duration::from_secs(5); let now = Instant::now();
self.health_status.get(mcp_id).and_then(|entry| {
let (is_healthy, check_time) = *entry.value();
if now.duration_since(check_time) < cache_duration {
Some(is_healthy)
} else {
None
}
})
}
pub fn update_health_status(&self, mcp_id: &str, is_healthy: bool) {
self.health_status
.insert(mcp_id.to_string(), (is_healthy, Instant::now()));
}
pub fn clear_health_status(&self, mcp_id: &str) {
self.health_status.remove(mcp_id);
}
pub fn can_restart(&self, mcp_id: &str) -> bool {
let now = Instant::now();
let min_restart_interval = Duration::from_secs(30);
if let Some(last_restart) = self.last_restart.get(mcp_id) {
let elapsed = now.duration_since(*last_restart);
if elapsed < min_restart_interval {
warn!(
"Service {} is in the cooldown period and is only {} seconds since its last restart. Restart is skipped.",
mcp_id,
elapsed.as_secs()
);
return false;
}
}
true
}
pub fn record_restart(&self, mcp_id: &str) {
self.last_restart.insert(mcp_id.to_string(), Instant::now());
info!(
"The service started successfully and the restart time was recorded: {}",
mcp_id
);
}
#[allow(dead_code)]
pub fn clear_restart(&self, mcp_id: &str) {
self.last_restart.remove(mcp_id);
info!("Restart timestamp cleared for service {}", mcp_id);
}
pub fn try_acquire_startup_lock(&self, mcp_id: &str) -> Option<OwnedMutexGuard<()>> {
let lock = self
.startup_locks
.entry(mcp_id.to_string())
.or_insert_with(|| Arc::new(Mutex::new(())))
.clone();
match lock.try_lock_owned() {
Ok(guard) => Some(guard),
Err(_) => {
debug!("Service {} is starting, skip this startup", mcp_id);
None
}
}
}
pub fn cleanup_startup_lock(&self, mcp_id: &str) {
self.startup_locks.remove(mcp_id);
debug!("Cleaned startup lock for service {}", mcp_id);
}
pub fn prune_stale_restarts(&self) {
let cutoff = Instant::now() - Duration::from_secs(60);
let before = self.last_restart.len();
self.last_restart.retain(|_, t| *t > cutoff);
let removed = before - self.last_restart.len();
if removed > 0 {
debug!("Pruned {removed} stale restart timestamps");
}
}
}
impl Default for RestartTracker {
fn default() -> Self {
Self::new()
}
}
pub static GLOBAL_RESTART_TRACKER: Lazy<RestartTracker> = Lazy::new(RestartTracker::default);
pub fn get_proxy_manager() -> &'static ProxyHandlerManager {
&GLOBAL_PROXY_MANAGER
}