From d2cb66470363ed444bb341e403fdd21082d3e2be Mon Sep 17 00:00:00 2001 From: "Muh. Fani Akbar" Date: Thu, 5 Feb 2026 12:35:50 +0700 Subject: [PATCH] add disable/enabled in commands and mcp --- src-tauri/src/helper.rs | 186 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 src-tauri/src/helper.rs diff --git a/src-tauri/src/helper.rs b/src-tauri/src/helper.rs new file mode 100644 index 0000000..2e9c064 --- /dev/null +++ b/src-tauri/src/helper.rs @@ -0,0 +1,186 @@ +use serde_json::Value; +use std::path::PathBuf; + +/// Get home directory +pub(crate) fn home_dir() -> Result { + dirs::home_dir().ok_or_else(|| "Could not find home directory".to_string()) +} + +/// Ensure directory exists, creating if needed +pub(crate) fn ensure_dir(path: &std::path::Path, dir_name: &str) -> Result<(), String> { + std::fs::create_dir_all(path) + .map_err(|e| format!("Failed to create {}: {}", dir_name, e))?; + Ok(()) +} + +/// Path to string (lossy) +pub(crate) fn path_to_string(path: &std::path::Path) -> String { + path.to_string_lossy().into_owned() +} + +/// Read JSON file and extract mcpServers object +pub(crate) fn read_json_file_mcp_servers( + file_path: &std::path::Path, + file_name: &str, +) -> Result, String> { + if !file_path.exists() { + return Ok(serde_json::Map::new()); + } + + let content = std::fs::read_to_string(file_path) + .map_err(|e| format!("Failed to read {}: {}", file_name, e))?; + let json_value: Value = serde_json::from_str(&content) + .map_err(|e| format!("Failed to parse {}: {}", file_name, e))?; + + Ok(json_value + .get("mcpServers") + .and_then(|s| s.as_object()) + .cloned() + .unwrap_or_else(serde_json::Map::new)) +} + +/// Read JSON file and parse it +pub(crate) fn read_json_file( + file_path: &std::path::Path, + file_name: &str, +) -> Result { + if !file_path.exists() { + return Ok(Value::Object(serde_json::Map::new())); + } + + let content = std::fs::read_to_string(file_path) + .map_err(|e| format!("Failed to read {}: {}", file_name, e))?; + serde_json::from_str(&content) + .map_err(|e| format!("Failed to parse {}: {}", file_name, e)) +} + +/// Write JSON file with pretty formatting +pub(crate) fn write_json_file( + file_path: &std::path::Path, + value: &Value, + file_name: &str, +) -> Result<(), String> { + let json_content = serde_json::to_string_pretty(value) + .map_err(|e| format!("Failed to serialize {}: {}", file_name, e))?; + std::fs::write(file_path, json_content) + .map_err(|e| format!("Failed to write {}: {}", file_name, e))?; + Ok(()) +} + +/// Write serializable value as JSON file +pub(crate) fn write_json_file_serialize( + file_path: &std::path::Path, + value: &T, + file_name: &str, +) -> Result<(), String> { + let json_value = serde_json::to_value(value) + .map_err(|e| format!("Failed to serialize {}: {}", file_name, e))?; + write_json_file(file_path, &json_value, file_name) +} + +/// Extract string array from JSON value +pub(crate) fn extract_string_array(value: &Value, key: &str) -> Vec { + value + .get(key) + .and_then(|v| v.as_array()) + .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()) + .unwrap_or_else(Vec::new) +} + +/// Get absolute project path from ~/.claude.json projects[cwd] +pub(crate) fn get_project_path_from_claude_json(cwd: &str) -> Result, String> { + let home_dir = home_dir()?; + let claude_json_path = home_dir.join(".claude.json"); + + if !claude_json_path.exists() { + return Ok(None); + } + + let json_value = read_json_file(&claude_json_path, ".claude.json")?; + + if let Some(projects) = json_value.get("projects").and_then(|p| p.as_object()) { + if projects.contains_key(cwd) { + return Ok(Some(PathBuf::from(cwd))); + } + } + + Ok(None) +} + +/// Read from project-local ./.mcp.json (LOCAL scope - highest priority) +pub(crate) fn read_local_mcp_servers( + project_path: &std::path::Path, +) -> Result, String> { + let local_mcp_path = project_path.join(".mcp.json"); + read_json_file_mcp_servers(&local_mcp_path, "local .mcp.json") +} + +/// Read from ~/.claude.json .projects[cwd].mcpServers (PROJECT scope) +pub(crate) fn read_project_mcp_servers( + cwd: &str, +) -> Result, String> { + let home_dir = home_dir()?; + let claude_json_path = home_dir.join(".claude.json"); + + let json_value = read_json_file(&claude_json_path, ".claude.json")?; + + Ok(json_value + .get("projects") + .and_then(|p| p.get(cwd)) + .and_then(|proj| proj.get("mcpServers")) + .and_then(|s| s.as_object()) + .cloned() + .unwrap_or_else(serde_json::Map::new)) +} + +/// Read MCPJSON servers from ~/.mcp.json +pub(crate) fn read_mcpjson_servers( + home_dir: &std::path::Path, +) -> Result, String> { + let mcp_json_path = home_dir.join(".mcp.json"); + read_json_file_mcp_servers(&mcp_json_path, ".mcp.json") +} + +/// Read Direct servers from ~/.claude.json +pub(crate) fn read_direct_servers( + home_dir: &std::path::Path, +) -> Result, String> { + let claude_json_path = home_dir.join(".claude.json"); + read_json_file_mcp_servers(&claude_json_path, ".claude.json") +} + +/// Read disabledMcpServers from ~/.claude.json (root or .projects[cwd]) +pub(crate) fn read_disabled_mcp_servers_from_claude_json( + cwd: Option<&str>, +) -> Result, String> { + let home_dir = home_dir()?; + let claude_json_path = home_dir.join(".claude.json"); + + if !claude_json_path.exists() { + return Ok(vec![]); + } + + let json_value = read_json_file(&claude_json_path, ".claude.json")?; + + // Check project-specific disabledMcpServers first (higher priority) + if let Some(cwd_str) = cwd { + if let Some(disabled) = json_value + .get("projects") + .and_then(|p| p.get(cwd_str)) + .and_then(|proj| proj.get("disabledMcpServers")) + .and_then(|v| v.as_array()) + { + return Ok(disabled + .iter() + .filter_map(|v| v.as_str().map(String::from)) + .collect()); + } + } + + // Fallback to root level disabledMcpServers + Ok(json_value + .get("disabledMcpServers") + .and_then(|v| v.as_array()) + .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()) + .unwrap_or_else(Vec::new)) +}