origin-mcp 0.8.3

MCP server for Origin, the local-first personal agent memory layer
Documentation
//! Read `ORIGIN_SPACE` at startup; expose the value as the "locked space".
//!
//! When set, every outbound daemon call attaches `X-Origin-Space: <value>`,
//! the MCP tool handlers ignore any inbound `space` arg from the model, and
//! the schema-gating layer (later wired in tools.rs) omits the `space`
//! field from tool definitions.
//!
//! Implementation uses `RwLock<Option<String>>` rather than `OnceLock` so that
//! the test suite can reset state between cases without spawning separate
//! processes.

use std::sync::RwLock;

static LOCKED: RwLock<Option<String>> = RwLock::new(None);

/// Initialise from the environment. Call once at process startup before
/// accepting any requests. Subsequent calls overwrite the value, which is
/// intentional for test isolation.
pub fn init_from_env() {
    let value = std::env::var("ORIGIN_SPACE")
        .ok()
        .map(|s| s.trim().to_string())
        .filter(|s| !s.is_empty());
    *LOCKED.write().expect("lock_state write lock poisoned") = value;
}

/// Return the locked space slug, or `None` if `ORIGIN_SPACE` was not set.
pub fn locked_space() -> Option<String> {
    LOCKED
        .read()
        .expect("lock_state read lock poisoned")
        .clone()
}

/// Return `true` if a space lock is active.
pub fn is_locked() -> bool {
    LOCKED
        .read()
        .expect("lock_state read lock poisoned")
        .is_some()
}

/// Shared mutex for test modules that mutate `ORIGIN_SPACE` env var + the
/// `LOCKED` RwLock. Exposed as `pub(crate)` so `client::tests` can share the
/// same serialisation lock and avoid races between test threads.
#[cfg(test)]
pub(crate) static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn locked_space_returns_none_when_env_unset() {
        let _guard = ENV_LOCK.lock().unwrap();
        std::env::remove_var("ORIGIN_SPACE");
        init_from_env();
        assert_eq!(locked_space(), None);
        assert!(!is_locked());
    }

    #[test]
    fn locked_space_returns_value_when_env_set() {
        let _guard = ENV_LOCK.lock().unwrap();
        std::env::set_var("ORIGIN_SPACE", "work");
        init_from_env();
        assert_eq!(locked_space().as_deref(), Some("work"));
        assert!(is_locked());
        // Clean up.
        std::env::remove_var("ORIGIN_SPACE");
        init_from_env();
    }

    #[test]
    fn whitespace_only_value_treated_as_unset() {
        let _guard = ENV_LOCK.lock().unwrap();
        std::env::set_var("ORIGIN_SPACE", "   ");
        init_from_env();
        assert_eq!(locked_space(), None);
        std::env::remove_var("ORIGIN_SPACE");
        init_from_env();
    }
}