1use std::path::Path;
21use std::process::{Command, Stdio};
22
23use anyhow::{Context, Result, bail};
24
25use crate::cli::DevArgs;
26
27pub fn execute(args: &DevArgs) -> Result<()> {
29 let path = &args.path;
30
31 let is_cargo_project = path.is_dir() && path.join("Cargo.toml").exists();
33 let is_binary = path.is_file() && is_executable(path);
34
35 if !is_cargo_project && !is_binary {
36 bail!(
37 "Path '{}' is neither a Cargo project nor an executable binary.\n\
38 For a Cargo project, provide the directory containing Cargo.toml.\n\
39 For a binary, provide the path to the executable.",
40 path.display()
41 );
42 }
43
44 if args.watch {
45 run_with_watch(args, is_cargo_project)
46 } else if is_cargo_project {
47 run_cargo_project(args)
48 } else {
49 run_binary(args)
50 }
51}
52
53fn run_with_watch(args: &DevArgs, is_cargo_project: bool) -> Result<()> {
55 if !is_command_available("cargo-watch") {
57 eprintln!("cargo-watch is not installed. Install it with:");
58 eprintln!(" cargo install cargo-watch");
59 eprintln!();
60 eprintln!("Then run this command again.");
61 bail!("cargo-watch not found");
62 }
63
64 if !is_cargo_project {
65 bail!("--watch requires a Cargo project directory, not a binary");
66 }
67
68 println!("Starting development server with hot reload...");
69 println!(" Project: {}", args.path.display());
70 println!(" Mode: {}", if args.release { "release" } else { "debug" });
71 println!();
72 println!("Press Ctrl+C to stop.");
73 println!();
74
75 let mut cmd = Command::new("cargo");
76 cmd.arg("watch")
77 .arg("-x")
78 .arg(build_cargo_run_args(args))
79 .current_dir(&args.path)
80 .stdin(Stdio::inherit())
81 .stdout(Stdio::inherit())
82 .stderr(Stdio::inherit());
83
84 let status = cmd.status().context("Failed to run cargo-watch")?;
85
86 if !status.success() {
87 bail!("cargo-watch exited with non-zero status");
88 }
89
90 Ok(())
91}
92
93fn run_cargo_project(args: &DevArgs) -> Result<()> {
95 println!("Starting development server...");
96 println!(" Project: {}", args.path.display());
97 println!(" Mode: {}", if args.release { "release" } else { "debug" });
98 println!();
99
100 let mut cmd = Command::new("cargo");
101 cmd.arg("run");
102
103 if args.release {
104 cmd.arg("--release");
105 }
106
107 if !args.server_args.is_empty() {
108 cmd.arg("--");
109 cmd.args(&args.server_args);
110 }
111
112 cmd.current_dir(&args.path)
113 .stdin(Stdio::inherit())
114 .stdout(Stdio::inherit())
115 .stderr(Stdio::inherit());
116
117 let status = cmd.status().context("Failed to run cargo")?;
118
119 if !status.success() {
120 bail!("Server exited with non-zero status");
121 }
122
123 Ok(())
124}
125
126fn run_binary(args: &DevArgs) -> Result<()> {
128 println!("Starting MCP server...");
129 println!(" Binary: {}", args.path.display());
130 println!();
131
132 let mut cmd = Command::new(&args.path);
133 cmd.args(&args.server_args)
134 .stdin(Stdio::inherit())
135 .stdout(Stdio::inherit())
136 .stderr(Stdio::inherit());
137
138 let status = cmd.status().context("Failed to run server binary")?;
139
140 if !status.success() {
141 bail!("Server exited with non-zero status");
142 }
143
144 Ok(())
145}
146
147fn build_cargo_run_args(args: &DevArgs) -> String {
149 let mut cmd_args = vec!["run".to_string()];
150
151 if args.release {
152 cmd_args.push("--release".to_string());
153 }
154
155 if !args.server_args.is_empty() {
156 cmd_args.push("--".to_string());
157 cmd_args.extend(args.server_args.clone());
158 }
159
160 cmd_args.join(" ")
161}
162
163fn is_command_available(cmd: &str) -> bool {
165 Command::new("which")
166 .arg(cmd)
167 .stdout(Stdio::null())
168 .stderr(Stdio::null())
169 .status()
170 .map(|s| s.success())
171 .unwrap_or(false)
172}
173
174fn is_executable(path: &Path) -> bool {
176 #[cfg(unix)]
177 {
178 use std::os::unix::fs::PermissionsExt;
179 path.metadata()
180 .map(|m| m.is_file() && (m.permissions().mode() & 0o111) != 0)
181 .unwrap_or(false)
182 }
183
184 #[cfg(not(unix))]
185 {
186 path.is_file()
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use std::path::PathBuf;
194
195 #[test]
196 fn test_build_cargo_run_args_basic() {
197 let args = DevArgs {
198 path: PathBuf::from("."),
199 watch: false,
200 server_args: vec![],
201 release: false,
202 inspector: false,
203 inspector_port: 5173,
204 };
205
206 assert_eq!(build_cargo_run_args(&args), "run");
207 }
208
209 #[test]
210 fn test_build_cargo_run_args_release() {
211 let args = DevArgs {
212 path: PathBuf::from("."),
213 watch: false,
214 server_args: vec![],
215 release: true,
216 inspector: false,
217 inspector_port: 5173,
218 };
219
220 assert_eq!(build_cargo_run_args(&args), "run --release");
221 }
222
223 #[test]
224 fn test_build_cargo_run_args_with_server_args() {
225 let args = DevArgs {
226 path: PathBuf::from("."),
227 watch: false,
228 server_args: vec!["--port".to_string(), "8080".to_string()],
229 release: false,
230 inspector: false,
231 inspector_port: 5173,
232 };
233
234 assert_eq!(build_cargo_run_args(&args), "run -- --port 8080");
235 }
236}