systemprompt-cli 0.1.22

systemprompt.io OS - CLI for agent orchestration, AI operations, and system management
Documentation
use std::sync::Arc;

use anyhow::{Context, Result};
use clap::Args;

use super::types::McpStatusOutput;
use crate::CliConfig;
use crate::shared::CommandResult;
use systemprompt_loader::ConfigLoader;
use systemprompt_mcp::services::McpManager;
use systemprompt_models::AppPaths;
use systemprompt_runtime::AppContext;

#[derive(Debug, Clone, Args)]
pub struct StatusArgs {
    #[arg(long, short, help = "Show detailed output including binary paths")]
    pub detailed: bool,

    #[arg(long, help = "Filter to specific server")]
    pub server: Option<String>,
}

pub async fn execute(
    args: StatusArgs,
    _config: &CliConfig,
) -> Result<CommandResult<McpStatusOutput>> {
    let services_config = ConfigLoader::load().context("Failed to load services configuration")?;
    let paths = AppPaths::get().context("Failed to get application paths")?;
    let bin_path = paths.build().bin().to_path_buf();

    let ctx = AppContext::new()
        .await
        .context("Failed to initialize application context")?;

    let manager =
        McpManager::new(Arc::clone(ctx.db_pool())).context("Failed to initialize MCP manager")?;
    let running_servers = manager
        .get_running_servers()
        .await
        .context("Failed to get running servers")?;

    let mut servers = Vec::new();

    for (name, deployment) in &services_config.mcp_servers {
        if let Some(ref filter) = args.server {
            if name != filter {
                continue;
            }
        }

        let running_info = running_servers.iter().find(|s| &s.name == name);
        let is_running = running_info.is_some();

        let binary_name = &deployment.binary;
        let release_path = bin_path
            .parent()
            .map(|p| p.join("release").join(binary_name))
            .filter(|p| p.exists());
        let debug_path = bin_path
            .parent()
            .map(|p| p.join("debug").join(binary_name))
            .filter(|p| p.exists());

        let pid = manager
            .get_service_info(name)
            .await
            .ok()
            .flatten()
            .and_then(|info| info.pid.map(|p| p as u32));

        let status_entry = super::types::McpStatusEntry {
            name: name.clone(),
            port: deployment.port,
            enabled: deployment.enabled,
            running: is_running,
            pid,
            binary: binary_name.clone(),
            release_binary: release_path.map(|p| {
                if args.detailed {
                    p.display().to_string()
                } else {
                    "exists".to_string()
                }
            }),
            debug_binary: debug_path.map(|p| {
                if args.detailed {
                    p.display().to_string()
                } else {
                    "exists".to_string()
                }
            }),
        };

        servers.push(status_entry);
    }

    servers.sort_by(|a, b| a.name.cmp(&b.name));

    let running_count = servers.iter().filter(|s| s.running).count();
    let enabled_count = servers.iter().filter(|s| s.enabled).count();

    let output = McpStatusOutput {
        servers,
        summary: super::types::McpStatusSummary {
            total: services_config.mcp_servers.len(),
            enabled: enabled_count,
            running: running_count,
        },
    };

    let columns = vec![
        "name".to_string(),
        "port".to_string(),
        "enabled".to_string(),
        "running".to_string(),
        "pid".to_string(),
        "release_binary".to_string(),
        "debug_binary".to_string(),
    ];

    Ok(CommandResult::table(output)
        .with_title("MCP Server Status")
        .with_columns(columns))
}