use anyhow::{Context, Result};
use mcp_execution_core::cli::ExitCode;
use std::path::PathBuf;
use std::process::Stdio;
use tokio::process::Command;
pub async fn run() -> Result<ExitCode> {
println!("Checking runtime environment...\n");
check_node_version().await?;
check_mcp_config()?;
#[cfg(unix)]
make_files_executable().await?;
println!("\n✓ Runtime setup complete");
println!(" Claude Code can now execute MCP tools via:");
println!(" node ~/.claude/servers/<server>/<tool>.ts '{{\"param\":\"value\"}}'");
println!("\nNext steps:");
println!(" 1. Generate tools: mcp-execution-cli generate <server>");
println!(" 2. Configure servers in ~/.claude/mcp.json");
println!(" 3. Execute tools autonomously via Node.js");
Ok(ExitCode::SUCCESS)
}
async fn check_node_version() -> Result<()> {
let output = Command::new("node")
.arg("--version")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.await
.context(
"Node.js not found in PATH.\n\
\n\
Node.js 18+ is required for MCP tool execution.\n\
Install from: https://nodejs.org\n\
\n\
Or use a version manager:\n\
- nvm: https://github.com/nvm-sh/nvm\n\
- fnm: https://github.com/Schniz/fnm",
)?;
if !output.status.success() {
anyhow::bail!("Node.js is installed but not working correctly");
}
let version_str = String::from_utf8_lossy(&output.stdout);
let version_str = version_str.trim().trim_start_matches('v');
let major_version = version_str
.split('.')
.next()
.and_then(|s| s.parse::<u32>().ok())
.context("Failed to parse Node.js version")?;
if major_version < 18 {
anyhow::bail!(
"Node.js version {version_str} is too old.\n\
\n\
Required: Node.js 18.0.0 or higher\n\
Current: Node.js {version_str}\n\
\n\
Please upgrade Node.js:\n\
- Download: https://nodejs.org\n\
- Or use nvm: nvm install 18"
);
}
println!("✓ Node.js v{version_str} detected");
Ok(())
}
fn check_mcp_config() -> Result<()> {
let config_path = get_mcp_config_path()?;
if config_path.exists() {
println!("✓ MCP configuration found: {}", config_path.display());
} else {
println!("⚠ MCP configuration not found");
println!(" Expected location: {}", config_path.display());
println!(" Create it with your server configurations:");
println!();
println!(" {{");
println!(" \"mcpServers\": {{");
println!(" \"github\": {{");
println!(" \"command\": \"docker\",");
println!(" \"args\": [\"run\", \"-i\", \"--rm\", \"...\"]");
println!(" }}");
println!(" }}");
println!(" }}");
println!();
println!(" See examples/mcp.json.example for more details.");
}
Ok(())
}
#[cfg(unix)]
async fn make_files_executable() -> Result<()> {
use std::os::unix::fs::PermissionsExt;
use tokio::fs;
let servers_dir = get_servers_dir()?;
if !servers_dir.exists() {
println!("⚠ No servers directory found");
println!(" Run 'mcp-execution-cli generate <server>' to create tools");
return Ok(());
}
let mut count = 0;
let mut entries = fs::read_dir(&servers_dir).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if path.is_dir() {
if let Ok(mut server_entries) = fs::read_dir(&path).await {
while let Some(server_entry) = server_entries.next_entry().await? {
let file_path = server_entry.path();
if file_path.extension().and_then(|s| s.to_str()) == Some("ts") {
let metadata = fs::metadata(&file_path).await?;
let mut perms = metadata.permissions();
perms.set_mode(0o755); fs::set_permissions(&file_path, perms).await?;
count += 1;
}
}
}
}
}
if count > 0 {
println!("✓ Made {count} TypeScript files executable");
}
Ok(())
}
fn get_mcp_config_path() -> Result<PathBuf> {
let home = dirs::home_dir().context("Failed to get home directory")?;
Ok(home.join(".claude").join("mcp.json"))
}
fn get_servers_dir() -> Result<PathBuf> {
let home = dirs::home_dir().context("Failed to get home directory")?;
Ok(home.join(".claude").join("servers"))
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_check_node_version() {
let result = check_node_version().await;
if let Err(e) = result {
let error_msg = e.to_string();
assert!(
error_msg.contains("Node.js") || error_msg.contains("version"),
"Error message should be helpful: {error_msg}"
);
}
}
#[test]
fn test_get_mcp_config_path() {
let path = get_mcp_config_path();
assert!(path.is_ok());
let path = path.unwrap();
assert!(path.to_string_lossy().contains(".claude"));
assert!(path.to_string_lossy().contains("mcp.json"));
}
#[test]
fn test_get_servers_dir() {
let path = get_servers_dir();
assert!(path.is_ok());
let path = path.unwrap();
assert!(path.to_string_lossy().contains(".claude"));
assert!(path.to_string_lossy().contains("servers"));
}
#[test]
fn test_check_mcp_config_no_panic() {
let result = check_mcp_config();
assert!(result.is_ok());
}
}