lean-ctx 3.9.17

Context Runtime for AI Agents with CCP. 71 MCP tools, 10 read modes, 95+ compression patterns, cross-session memory (CCP), persistent AI knowledge with temporal facts + contradiction detection, multi-agent context sharing, LITM-aware positioning, AAAK compact format, adaptive compression with Thompson Sampling bandits. Supports 24+ AI tools. Reduces LLM token consumption by up to 99%.
Documentation
//! Grok Build MCP registration.
//!
//! Grok Build natively discovers `AGENTS.md`, skills and MCP servers. Its
//! documented user config is `$GROK_HOME/config.toml` (or `~/.grok/config.toml`)
//! with stdio servers under `[mcp_servers.<name>]`, so no unsupported hook
//! format is installed here.

use super::super::{
    mcp_server_env_pairs, mcp_server_quiet_mode, resolve_binary_path, should_register_mcp,
    write_file,
};

pub(crate) fn install_grok_mcp() {
    if !should_register_mcp() {
        return;
    }
    let Some(home) = crate::core::home::resolve_home_dir() else {
        tracing::error!("Cannot resolve home directory");
        return;
    };
    let grok_home = std::env::var_os("GROK_HOME")
        .filter(|path| !path.is_empty())
        .map_or_else(|| home.join(".grok"), std::path::PathBuf::from);
    let path = grok_home.join("config.toml");
    let existing = std::fs::read_to_string(&path).unwrap_or_default();
    let Some(updated) =
        ensure_grok_mcp_server(&existing, &resolve_binary_path(), &mcp_server_env_pairs())
    else {
        return;
    };
    write_file(&path, &updated);
    if !mcp_server_quiet_mode() {
        eprintln!("Installed Grok Build MCP server at {}", path.display());
    }
}

fn ensure_grok_mcp_server(
    config_content: &str,
    binary: &str,
    env_pairs: &[(String, String)],
) -> Option<String> {
    let mut doc = config_content.parse::<toml_edit::DocumentMut>().ok()?;
    let original = doc.to_string();
    let servers = doc["mcp_servers"].or_insert(toml_edit::table());
    servers.as_table_mut()?.set_implicit(true);
    let lean = servers["lean-ctx"].or_insert(toml_edit::table());
    let lean_tbl = lean.as_table_mut()?;
    lean_tbl.set_implicit(false);
    if !lean_tbl.contains_key("command") {
        lean_tbl["command"] = toml_edit::value(binary);
    }
    if !lean_tbl.contains_key("args") {
        lean_tbl["args"] = toml_edit::value(toml_edit::Array::new());
    }
    let env = lean_tbl["env"].or_insert(toml_edit::table());
    let env_tbl = env.as_table_mut()?;
    for (key, value) in env_pairs {
        if env_tbl.get(key).and_then(toml_edit::Item::as_str) != Some(value.as_str()) {
            env_tbl[key] = toml_edit::value(value);
        }
    }
    let updated = doc.to_string();
    (updated != original).then_some(updated)
}

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

    fn env_pairs() -> Vec<(String, String)> {
        vec![(
            "LEAN_CTX_PROJECT_ROOT".to_string(),
            "/work/repo".to_string(),
        )]
    }

    #[test]
    fn adds_valid_grok_mcp_server() {
        let output = ensure_grok_mcp_server("", "/usr/local/bin/lean-ctx", &env_pairs())
            .expect("empty config must gain the lean-ctx server");
        let doc = output
            .parse::<toml_edit::DocumentMut>()
            .expect("generated Grok config must be valid TOML");
        assert_eq!(
            doc["mcp_servers"]["lean-ctx"]["command"].as_str(),
            Some("/usr/local/bin/lean-ctx")
        );
        assert_eq!(
            doc["mcp_servers"]["lean-ctx"]["env"]["LEAN_CTX_PROJECT_ROOT"].as_str(),
            Some("/work/repo")
        );
    }

    #[test]
    fn preserves_user_config_and_is_idempotent() {
        let input =
            "[models]\ndefault = \"grok-build\"\n\n[mcp_servers.other]\ncommand = \"other\"\n";
        let output = ensure_grok_mcp_server(input, "lean-ctx", &env_pairs()).unwrap();
        assert!(output.contains("default = \"grok-build\""));
        assert!(output.contains("[mcp_servers.other]"));
        assert!(ensure_grok_mcp_server(&output, "lean-ctx", &env_pairs()).is_none());
    }

    #[test]
    fn never_overwrites_user_managed_lean_ctx_command() {
        let input = "[mcp_servers.lean-ctx]\ncommand = \"custom-lean-ctx\"\n";
        let output = ensure_grok_mcp_server(input, "lean-ctx", &[]).unwrap();
        let doc = output.parse::<toml_edit::DocumentMut>().unwrap();
        assert_eq!(
            doc["mcp_servers"]["lean-ctx"]["command"].as_str(),
            Some("custom-lean-ctx")
        );
    }
}