Skip to main content

horto_os_ui_mcp/
config.rs

1//! Runtime settings from the environment.
2
3use std::env;
4use std::path::PathBuf;
5
6/// Where the MCP process runs.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum McpMode {
9    /// PC: day-2 HTTP to status-api + privileged tools via OpenSSH remote runner.
10    Pc,
11    /// Box: day-2 HTTP to loopback status-api + privileged tools in-process.
12    Box,
13}
14
15impl McpMode {
16    /// Parse `HORTO_MCP_MODE` (`pc` / `box`). Default: `pc`.
17    #[must_use]
18    pub fn from_env() -> Self {
19        match env::var("HORTO_MCP_MODE")
20            .unwrap_or_default()
21            .trim()
22            .to_ascii_lowercase()
23            .as_str()
24        {
25            "box" => Self::Box,
26            _ => Self::Pc,
27        }
28    }
29}
30
31/// Settings shared by tools and HTTP server.
32#[derive(Debug, Clone)]
33pub struct McpSettings {
34    /// PC vs box execution mode (`HORTO_MCP_MODE`).
35    pub mode: McpMode,
36    /// Base URL of `horto-os-ui-status-api` (`HORTO_STATUS_API_URL`).
37    pub status_api_url: String,
38    /// Status-api bearer (`HORTO_API_TOKEN`).
39    pub api_token: Option<String>,
40    /// Bearer required for Streamable HTTP. Falls back to `api_token`.
41    pub mcp_token: Option<String>,
42    /// SSH target host for PC privileged tools (`HORTO_REMOTE_HOST`).
43    pub remote_host: Option<String>,
44    /// Release tag for remote CLI install (`HORTO_RELEASE_TAG`).
45    pub release_tag: Option<String>,
46    /// Local directory of ecosystem binaries (`HORTO_BIN_DIR`).
47    pub bin_dir: Option<PathBuf>,
48    /// When true, offer SSH key install on remote probe (`HORTO_INSTALL_SSH_KEY`).
49    pub install_ssh_key: bool,
50}
51
52impl McpSettings {
53    /// Load from process environment.
54    #[must_use]
55    pub fn from_env() -> Self {
56        let mode = McpMode::from_env();
57        let status_api_url = status_api_url_from_env(mode);
58        let api_token = env::var("HORTO_API_TOKEN")
59            .ok()
60            .map(|s| s.trim().to_owned())
61            .filter(|s| !s.is_empty());
62        let mcp_token = env::var("HORTO_MCP_TOKEN")
63            .ok()
64            .map(|s| s.trim().to_owned())
65            .filter(|s| !s.is_empty())
66            .or_else(|| api_token.clone());
67        let remote_host = env::var("HORTO_REMOTE_HOST")
68            .ok()
69            .map(|s| s.trim().to_owned())
70            .filter(|s| !s.is_empty());
71        let release_tag = env::var("HORTO_RELEASE_TAG")
72            .ok()
73            .map(|s| s.trim().to_owned())
74            .filter(|s| !s.is_empty());
75        let bin_dir = env::var("HORTO_BIN_DIR")
76            .ok()
77            .map(|s| s.trim().to_owned())
78            .filter(|s| !s.is_empty())
79            .map(PathBuf::from);
80        let install_ssh_key = matches!(
81            env::var("HORTO_INSTALL_SSH_KEY")
82                .unwrap_or_default()
83                .trim()
84                .to_ascii_lowercase()
85                .as_str(),
86            "1" | "true" | "yes" | "on"
87        );
88        Self {
89            mode,
90            status_api_url,
91            api_token,
92            mcp_token,
93            remote_host,
94            release_tag,
95            bin_dir,
96            install_ssh_key,
97        }
98    }
99
100    /// Token required when serving Streamable HTTP.
101    #[must_use]
102    pub fn http_bearer(&self) -> Option<&str> {
103        self.mcp_token.as_deref()
104    }
105}
106
107/// `HORTO_STATUS_API_URL`, else mode default.
108fn status_api_url_from_env(mode: McpMode) -> String {
109    env::var("HORTO_STATUS_API_URL")
110        .ok()
111        .map(|s| s.trim().to_owned())
112        .filter(|s| !s.is_empty())
113        .unwrap_or_else(|| match mode {
114            McpMode::Box => "http://127.0.0.1:8787".into(),
115            McpMode::Pc => "http://localhost:8787".into(),
116        })
117}