systemprompt-mcp 0.10.3

Native Model Context Protocol (MCP) implementation for systemprompt.io. Orchestration, per-server OAuth2, RBAC middleware, and tool-call governance — the core of the AI governance pipeline.
Documentation
use std::path::Path;

use crate::error::McpDomainResult;
use systemprompt_database::{CreateServiceInput, ServiceRepository};
use systemprompt_models::AppPaths;

use super::ServiceInfo;
use crate::McpServerConfig;

pub fn get_binary_mtime(binary_path: &Path) -> Option<i64> {
    binary_path
        .metadata()
        .ok()
        .and_then(|m| m.modified().ok())
        .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
        .map(|d| d.as_secs() as i64)
}

pub fn get_binary_mtime_for_service(paths: &AppPaths, service_name: &str) -> Option<i64> {
    paths
        .build()
        .resolve_binary(service_name)
        .ok()
        .and_then(|path| get_binary_mtime(path.as_path()))
}

pub async fn register_service(
    db_pool: &systemprompt_database::DbPool,
    paths: &AppPaths,
    config: &McpServerConfig,
    pid: u32,
) -> McpDomainResult<String> {
    let repo = ServiceRepository::new(db_pool)?;
    let binary_mtime = get_binary_mtime_for_service(paths, &config.name);

    tracing::debug!(
        service = %config.name,
        pid = pid,
        port = config.port,
        binary_mtime = ?binary_mtime,
        "Registering MCP service"
    );

    repo.create_service(CreateServiceInput {
        name: &config.name,
        module_name: "mcp",
        status: "running",
        port: config.port,
        binary_mtime,
    })
    .await
    .inspect_err(|e| {
        tracing::error!(service = %config.name, error = %e, "Failed to create service record");
    })?;

    repo.update_service_pid(&config.name, pid as i32)
        .await
        .inspect_err(|e| {
            tracing::error!(service = %config.name, error = %e, "Failed to update PID for service");
        })?;

    tracing::debug!(service = %config.name, pid = pid, "Service registered in database");
    Ok(config.name.clone())
}

pub async fn unregister_service(
    db_pool: &systemprompt_database::DbPool,
    service_name: &str,
) -> McpDomainResult<()> {
    let repo = ServiceRepository::new(db_pool)?;
    repo.delete_service(service_name).await.map_err(Into::into)
}

pub async fn get_service_by_name(
    db_pool: &systemprompt_database::DbPool,
    name: &str,
) -> McpDomainResult<Option<ServiceInfo>> {
    let repo = ServiceRepository::new(db_pool)?;
    let result = repo.get_service_by_name(name).await?;

    Ok(result.map(|r| ServiceInfo {
        name: r.name,
        status: r.status,
        pid: r.pid,
        port: r.port as u16,
        binary_mtime: r.binary_mtime,
    }))
}

pub async fn get_running_servers(
    db_pool: &systemprompt_database::DbPool,
) -> McpDomainResult<Vec<McpServerConfig>> {
    use crate::services::registry::RegistryManager;

    let repo = ServiceRepository::new(db_pool)?;
    let all_services = repo.get_mcp_services().await?;

    RegistryManager::validate()?;
    let mut running_configs = Vec::new();

    for service in all_services {
        if service.status == "running" {
            if let Some(config) = RegistryManager::find_server(&service.name)? {
                running_configs.push(config);
            }
        }
    }

    Ok(running_configs)
}

pub async fn register_existing_process(
    db_pool: &systemprompt_database::DbPool,
    paths: &AppPaths,
    config: &McpServerConfig,
    pid: u32,
) -> McpDomainResult<String> {
    let repo = ServiceRepository::new(db_pool)?;

    let binary_mtime = get_binary_mtime_for_service(paths, &config.name);

    repo.create_service(CreateServiceInput {
        name: &config.name,
        module_name: "mcp",
        status: "running",
        port: config.port,
        binary_mtime,
    })
    .await?;

    repo.update_service_pid(&config.name, pid as i32).await?;

    Ok(config.name.clone())
}