Skip to main content

systemprompt_api/services/proxy/
resolver.rs

1//! Service resolution for the MCP proxy, with restart-on-dead-backend.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use std::sync::Arc;
7
8use systemprompt_database::ServiceConfig;
9use systemprompt_mcp::services::McpOrchestrator;
10use systemprompt_runtime::AppContext;
11
12use super::backend::ProxyError;
13
14#[derive(Debug, Clone, Copy)]
15pub struct ServiceResolver;
16
17impl ServiceResolver {
18    pub async fn resolve(
19        service_name: &str,
20        ctx: &AppContext,
21    ) -> Result<ServiceConfig, ProxyError> {
22        let service_repo = ctx.service_repository();
23
24        let service = match service_repo.find_service_by_name(service_name).await {
25            Ok(svc) => svc,
26            Err(e) => {
27                tracing::error!(service = %service_name, error = %e, "Database error when looking up service");
28                return Err(ProxyError::DatabaseError {
29                    service: service_name.to_owned(),
30                    source: e,
31                });
32            },
33        };
34
35        let Some(service) = service else {
36            tracing::warn!(service = %service_name, "Service not found");
37            return Err(ProxyError::ServiceNotFound {
38                service: service_name.to_owned(),
39            });
40        };
41
42        if service.status != "running" {
43            if service.status == "crashed" {
44                tracing::info!(service = %service_name, "Service crashed, attempting restart");
45
46                if Self::attempt_restart(service_name, ctx).await.is_ok() {
47                    let restarted = service_repo
48                        .find_service_by_name(service_name)
49                        .await
50                        .map_err(|e| ProxyError::DatabaseError {
51                            service: service_name.to_owned(),
52                            source: e,
53                        })?;
54
55                    if let Some(restarted) = restarted
56                        && restarted.status == "running"
57                    {
58                        tracing::info!(service = %service_name, "Service restarted, retrying proxy");
59                        return Ok(restarted);
60                    }
61
62                    tracing::warn!(
63                        service = %service_name,
64                        "Restart reported success but the service is not running"
65                    );
66                }
67            }
68
69            tracing::warn!(service = %service_name, status = %service.status, "Service not running");
70            return Err(ProxyError::ServiceNotRunning {
71                service: service_name.to_owned(),
72                status: service.status.clone(),
73            });
74        }
75
76        Ok(service)
77    }
78
79    async fn attempt_restart(service_name: &str, ctx: &AppContext) -> Result<(), ProxyError> {
80        let orchestrator = McpOrchestrator::new(
81            (**ctx.service_repository()).clone(),
82            Arc::clone(ctx.app_paths_arc()),
83            ctx.mcp_registry().clone(),
84        )
85        .map_err(|e| ProxyError::ServiceNotRunning {
86            service: service_name.to_owned(),
87            status: format!("Failed to create orchestrator: {e}"),
88        })?;
89
90        match orchestrator
91            .start_services(Some(service_name.to_owned()))
92            .await
93        {
94            Ok(()) => {},
95            Err(e) => {
96                tracing::error!(service = %service_name, error = %e, "Failed to restart service");
97                return Err(ProxyError::ServiceNotRunning {
98                    service: service_name.to_owned(),
99                    status: format!("Restart failed: {e}"),
100                });
101            },
102        }
103
104        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
105        Ok(())
106    }
107}