use std::path::PathBuf;
use crate::shim::cli::AgentTarget;
const MCP_URL: &str = "https://mcp.falsegreen.com/v1/mcp";
pub fn install(agent: &AgentTarget) -> std::io::Result<()> {
match agent {
AgentTarget::Codex => install_codex(),
AgentTarget::GithubCopilot => install_vscode_http(),
AgentTarget::ClaudeCode => install_claude_code(),
AgentTarget::OpenCode => install_opencode(),
AgentTarget::Cursor => install_cursor(),
}
}
fn install_codex() -> std::io::Result<()> {
let config_path = codex_config_path()?;
let self_path = std::env::current_exe()?;
let existing = std::fs::read_to_string(&config_path).unwrap_or_default();
let mut doc: toml::Value = if existing.trim().is_empty() {
toml::Value::Table(toml::value::Table::new())
} else {
toml::from_str(&existing).unwrap_or(toml::Value::Table(toml::value::Table::new()))
};
let root = doc.as_table_mut().unwrap();
if let Some(mcp) = root.get("mcp_servers").and_then(|v| v.as_table()) {
if mcp.contains_key("falsegreen") {
println!("FalseGreen is already configured in {}.", config_path.display());
println!("To reconfigure, remove the [mcp_servers.falsegreen] section first.");
return Ok(());
}
}
let server = stdio_server_config(&self_path);
let mcp_servers = root
.entry("mcp_servers")
.or_insert_with(|| toml::Value::Table(toml::value::Table::new()));
if let Some(table) = mcp_servers.as_table_mut() {
table.insert("falsegreen".into(), toml::Value::Table(server));
}
write_toml(&config_path, &doc)?;
println!("Installed FalseGreen into {}", config_path.display());
println!("\nCodex will now launch `falsegreen mcp` as a STDIO MCP server.");
println!("Run `codex mcp list` to verify.");
Ok(())
}
fn install_vscode_http() -> std::io::Result<()> {
let config_path = vscode_mcp_path()?;
let mut doc = read_json_or_default(&config_path);
if let Some(inputs) = doc.get_mut("inputs").and_then(|v| v.as_array_mut()) {
if !inputs.iter().any(|i| i.get("id") == Some(&serde_json::json!("falsegreen-key"))) {
inputs.push(serde_json::json!({
"type": "promptString",
"id": "falsegreen-key",
"description": "FalseGreen key",
"password": true,
}));
}
} else {
doc["inputs"] = serde_json::json!([
{"type": "promptString", "id": "falsegreen-key", "description": "FalseGreen key", "password": true}
]);
}
let server = serde_json::json!({
"type": "http",
"url": MCP_URL,
"headers": {"X-API-KEY": "${input:falsegreen-key}"}
});
insert_json_server(&mut doc, "falsegreen", server);
write_json(&config_path, &doc)?;
println!("Installed FalseGreen into {}", config_path.display());
println!("\nGitHub Copilot will connect to {} with your key.", MCP_URL);
println!("Open Copilot Chat in Agent mode and enable the FalseGreen tools.");
Ok(())
}
fn install_claude_code() -> std::io::Result<()> {
let config_path = project_mcp_path()?;
let key = crate::shim::config::load_token();
let mut doc = read_json_or_default(&config_path);
let header_value = match &key {
Some(k) => k.clone(),
None => "${FALSEGREEN_KEY}".to_string(),
};
let server = serde_json::json!({
"type": "http",
"url": MCP_URL,
"headers": {"X-API-KEY": header_value}
});
if let Some(servers) = doc.get_mut("mcpServers").and_then(|v| v.as_object_mut()) {
if servers.contains_key("falsegreen") {
println!("FalseGreen is already configured in {}.", config_path.display());
return Ok(());
}
servers.insert("falsegreen".to_string(), server);
} else {
let mut servers = serde_json::Map::new();
servers.insert("falsegreen".to_string(), server);
doc["mcpServers"] = serde_json::Value::Object(servers);
}
write_json(&config_path, &doc)?;
println!("Installed FalseGreen into {}", config_path.display());
if key.is_none() {
println!("\nSet FALSEGREEN_KEY in your environment before launching Claude Code.");
}
println!("Alternatively, run: claude mcp add --transport http falsegreen {} \\", MCP_URL);
println!(" --header \"X-API-KEY: YOUR_KEY\"");
Ok(())
}
fn install_opencode() -> std::io::Result<()> {
let config_path = opencode_config_path()?;
let mut doc = read_json_or_default(&config_path);
let header_value = "{env:FALSEGREEN_KEY}".to_string();
let server = serde_json::json!({
"type": "remote",
"url": MCP_URL,
"headers": {"X-API-KEY": header_value}
});
if let Some(mcp) = doc.get_mut("mcp").and_then(|v| v.as_object_mut()) {
if mcp.contains_key("falsegreen") {
println!("FalseGreen is already configured in {}.", config_path.display());
return Ok(());
}
mcp.insert("falsegreen".to_string(), server);
} else {
let mut mcp = serde_json::Map::new();
mcp.insert("falsegreen".to_string(), server);
doc["mcp"] = serde_json::Value::Object(mcp);
}
write_json(&config_path, &doc)?;
println!("Installed FalseGreen into {}", config_path.display());
println!("\nSet FALSEGREEN_KEY in your environment before launching OpenCode.");
Ok(())
}
fn install_cursor() -> std::io::Result<()> {
let config_path = cursor_mcp_path()?;
let key = crate::shim::config::load_token();
let mut doc = read_json_or_default(&config_path);
let header_value = match &key {
Some(k) => k.clone(),
None => "${env:FALSEGREEN_KEY}".to_string(),
};
let server = serde_json::json!({
"url": MCP_URL,
"headers": {"X-API-KEY": header_value}
});
if let Some(servers) = doc.get_mut("mcpServers").and_then(|v| v.as_object_mut()) {
if servers.contains_key("falsegreen") {
println!("FalseGreen is already configured in {}.", config_path.display());
return Ok(());
}
servers.insert("falsegreen".to_string(), server);
} else {
let mut servers = serde_json::Map::new();
servers.insert("falsegreen".to_string(), server);
doc["mcpServers"] = serde_json::Value::Object(servers);
}
write_json(&config_path, &doc)?;
println!("Installed FalseGreen into {}", config_path.display());
if key.is_none() {
println!("\nSet FALSEGREEN_KEY in your environment before launching Cursor.");
}
Ok(())
}
fn stdio_server_config(self_path: &std::path::Path) -> toml::value::Table {
let mut server = toml::value::Table::new();
server.insert(
"command".into(),
toml::Value::String(self_path.to_string_lossy().to_string()),
);
server.insert(
"args".into(),
toml::Value::Array(vec![toml::Value::String("mcp".into())]),
);
let mut env = toml::value::Table::new();
if let Some(token) = crate::shim::config::load_token() {
env.insert("FALSEGREEN_KEY".into(), toml::Value::String(token));
}
if let Ok(api_url) = std::env::var("FALSEGREEN_API_URL") {
env.insert("FALSEGREEN_API_URL".into(), toml::Value::String(api_url));
}
if !env.is_empty() {
server.insert("env".into(), toml::Value::Table(env));
}
server
}
fn insert_json_server(doc: &mut serde_json::Value, name: &str, server: serde_json::Value) {
if let Some(servers) = doc.get_mut("servers").and_then(|v| v.as_object_mut()) {
if servers.contains_key(name) {
println!("FalseGreen is already configured.");
return;
}
servers.insert(name.to_string(), server);
return;
}
let mut servers = serde_json::Map::new();
servers.insert(name.to_string(), server);
doc["servers"] = serde_json::Value::Object(servers);
}
fn read_json_or_default(path: &std::path::Path) -> serde_json::Value {
let content = std::fs::read_to_string(path).unwrap_or_default();
if content.trim().is_empty() {
serde_json::json!({})
} else {
serde_json::from_str(&content).unwrap_or(serde_json::json!({}))
}
}
fn write_json(path: &std::path::Path, doc: &serde_json::Value) -> std::io::Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let serialized = serde_json::to_string_pretty(doc).unwrap_or_default();
std::fs::write(path, serialized + "\n")
}
fn write_toml(path: &std::path::Path, doc: &toml::Value) -> std::io::Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let serialized = toml::to_string_pretty(doc).unwrap_or_default();
std::fs::write(path, serialized)
}
fn codex_config_path() -> std::io::Result<PathBuf> {
if let Ok(home) = std::env::var("CODEX_HOME") {
return Ok(PathBuf::from(home).join("config.toml"));
}
let home = dirs::home_dir().ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::NotFound, "cannot find home directory")
})?;
Ok(home.join(".codex").join("config.toml"))
}
fn vscode_mcp_path() -> std::io::Result<PathBuf> {
Ok(PathBuf::from(".vscode").join("mcp.json"))
}
fn project_mcp_path() -> std::io::Result<PathBuf> {
Ok(PathBuf::from(".mcp.json"))
}
fn opencode_config_path() -> std::io::Result<PathBuf> {
Ok(PathBuf::from("opencode.json"))
}
fn cursor_mcp_path() -> std::io::Result<PathBuf> {
Ok(PathBuf::from(".cursor").join("mcp.json"))
}