1use crate::cli::*;
4use crate::error::{CliError, CliResult};
5use crate::formatter::Formatter;
6use crate::path_security;
7use crate::transport::create_client;
8use std::collections::HashMap;
9
10pub struct CommandExecutor {
12 pub formatter: Formatter,
13 verbose: bool,
14}
15
16impl CommandExecutor {
17 #[must_use]
18 pub fn new(format: OutputFormat, colored: bool, verbose: bool) -> Self {
19 Self {
20 formatter: Formatter::new(format, colored),
21 verbose,
22 }
23 }
24
25 pub fn display_error(&self, error: &CliError) {
27 self.formatter.display_error(error);
28 }
29
30 pub async fn execute(&self, command: Commands) -> CliResult<()> {
32 match command {
33 Commands::Tools(cmd) => self.execute_tool_command(cmd).await,
34 Commands::Resources(cmd) => self.execute_resource_command(cmd).await,
35 Commands::Prompts(cmd) => self.execute_prompt_command(cmd).await,
36 Commands::Complete(cmd) => self.execute_completion_command(cmd).await,
37 Commands::Server(cmd) => self.execute_server_command(cmd).await,
38 Commands::Sample(cmd) => self.execute_sampling_command(cmd).await,
39 Commands::Connect(conn) => self.execute_connect(conn).await,
40 Commands::Status(conn) => self.execute_status(conn).await,
41 Commands::Dev(args) => self.execute_dev(args),
42 Commands::Install(args) => self.execute_install(args),
43 Commands::Build(args) => self.execute_build(args),
44 Commands::Deploy(args) => self.execute_deploy(args),
45 Commands::New(args) => self.execute_new(args),
46 }
47 }
48
49 async fn execute_tool_command(&self, command: ToolCommands) -> CliResult<()> {
52 match command {
53 ToolCommands::List { conn } => {
54 let client = create_client(&conn).await?;
55 client.initialize().await?;
56 let tools = client.list_tools().await?;
57 self.formatter.display_tools(&tools)
58 }
59
60 ToolCommands::Call {
61 conn,
62 name,
63 arguments,
64 } => {
65 let args: HashMap<String, serde_json::Value> =
66 if arguments.trim().is_empty() || arguments == "{}" {
67 HashMap::new()
68 } else {
69 serde_json::from_str(&arguments).map_err(|e| {
70 let location = format!("line {}, column {}", e.line(), e.column());
71 CliError::InvalidArguments(format!(
72 "Invalid JSON arguments at {}: {}",
73 location, e
74 ))
75 })?
76 };
77
78 let client = create_client(&conn).await?;
79 client.initialize().await?;
80 let result = client.call_tool(&name, Some(args)).await?;
81 self.formatter.display(&result)
82 }
83
84 ToolCommands::Schema { conn, name } => {
85 let client = create_client(&conn).await?;
86 client.initialize().await?;
87 let tools = client.list_tools().await?;
88
89 if let Some(tool_name) = name {
90 let tool = tools.iter().find(|t| t.name == tool_name).ok_or_else(|| {
91 CliError::Other(format!("Tool '{}' not found", tool_name))
92 })?;
93
94 self.formatter.display(&tool.input_schema)
95 } else {
96 let schemas: Vec<_> = tools
97 .iter()
98 .map(|t| {
99 serde_json::json!({
100 "name": t.name,
101 "schema": t.input_schema
102 })
103 })
104 .collect();
105
106 self.formatter.display(&schemas)
107 }
108 }
109
110 ToolCommands::Export { conn, output } => {
111 let client = create_client(&conn).await?;
112 client.initialize().await?;
113 let tools = client.list_tools().await?;
114
115 if !output.is_absolute() {
117 return Err(CliError::InvalidArguments(
118 "Output directory must be an absolute path".to_string(),
119 ));
120 }
121
122 std::fs::create_dir_all(&output)?;
124
125 let mut exported_count = 0;
126 let mut skipped_count = 0;
127
128 for tool in tools {
129 match path_security::safe_output_path(&output, &tool.name, "json") {
132 Ok(filepath) => {
133 let schema = serde_json::to_string_pretty(&tool.input_schema)?;
134 std::fs::write(&filepath, schema)?;
135
136 if self.verbose {
137 println!("Exported: {}", filepath.display());
138 }
139 exported_count += 1;
140 }
141 Err(e) => {
142 eprintln!("Warning: Skipped tool '{}': {}", tool.name, e);
144 skipped_count += 1;
145 }
146 }
147 }
148
149 if exported_count > 0 {
150 println!(
151 "✓ Exported {} schema{} to: {}",
152 exported_count,
153 if exported_count == 1 { "" } else { "s" },
154 output.display()
155 );
156 }
157
158 if skipped_count > 0 {
159 println!(
160 "⚠ Skipped {} tool{} due to invalid names",
161 skipped_count,
162 if skipped_count == 1 { "" } else { "s" }
163 );
164 }
165
166 Ok(())
167 }
168 }
169 }
170
171 async fn execute_resource_command(&self, command: ResourceCommands) -> CliResult<()> {
174 match command {
175 ResourceCommands::List { conn } => {
176 let client = create_client(&conn).await?;
177 client.initialize().await?;
178 let resources = client.list_resources().await?;
179 self.formatter.display(&resources)
180 }
181
182 ResourceCommands::Read { conn, uri } => {
183 let client = create_client(&conn).await?;
184 client.initialize().await?;
185 let result = client.read_resource(&uri).await?;
186 self.formatter.display(&result)
187 }
188
189 ResourceCommands::Templates { conn } => {
190 let client = create_client(&conn).await?;
191 client.initialize().await?;
192 let templates = client.list_resource_templates().await?;
193 self.formatter.display(&templates)
194 }
195
196 ResourceCommands::Subscribe { conn, uri } => {
197 let client = create_client(&conn).await?;
198 client.initialize().await?;
199 client.subscribe(&uri).await?;
200 println!("✓ Subscribed to: {uri}");
201 Ok(())
202 }
203
204 ResourceCommands::Unsubscribe { conn, uri } => {
205 let client = create_client(&conn).await?;
206 client.initialize().await?;
207 client.unsubscribe(&uri).await?;
208 println!("✓ Unsubscribed from: {uri}");
209 Ok(())
210 }
211 }
212 }
213
214 async fn execute_prompt_command(&self, command: PromptCommands) -> CliResult<()> {
217 match command {
218 PromptCommands::List { conn } => {
219 let client = create_client(&conn).await?;
220 client.initialize().await?;
221 let prompts = client.list_prompts().await?;
222 self.formatter.display_prompts(&prompts)
223 }
224
225 PromptCommands::Get {
226 conn,
227 name,
228 arguments,
229 } => {
230 let args: HashMap<String, serde_json::Value> =
232 if arguments.trim().is_empty() || arguments == "{}" {
233 HashMap::new()
234 } else {
235 serde_json::from_str(&arguments).map_err(|e| {
236 let location = format!("line {}, column {}", e.line(), e.column());
237 CliError::InvalidArguments(format!(
238 "Invalid JSON arguments at {}: {}",
239 location, e
240 ))
241 })?
242 };
243
244 let args_option = if args.is_empty() { None } else { Some(args) };
245
246 let client = create_client(&conn).await?;
247 client.initialize().await?;
248 let result = client.get_prompt(&name, args_option).await?;
249 self.formatter.display(&result)
250 }
251
252 PromptCommands::Schema { conn, name } => {
253 let client = create_client(&conn).await?;
254 client.initialize().await?;
255 let prompts = client.list_prompts().await?;
256
257 let prompt = prompts
258 .iter()
259 .find(|p| p.name == name)
260 .ok_or_else(|| CliError::Other(format!("Prompt '{}' not found", name)))?;
261
262 self.formatter.display(&prompt.arguments)
263 }
264 }
265 }
266
267 async fn execute_completion_command(&self, command: CompletionCommands) -> CliResult<()> {
270 match command {
271 CompletionCommands::Get {
272 conn,
273 ref_type,
274 ref_value,
275 argument,
276 } => {
277 let client = create_client(&conn).await?;
278 client.initialize().await?;
279
280 let result = match ref_type {
282 RefType::Prompt => {
283 let arg_name = argument.as_deref().unwrap_or("value");
284 client
285 .complete_prompt(&ref_value, arg_name, "", None)
286 .await?
287 }
288 RefType::Resource => {
289 let arg_name = argument.as_deref().unwrap_or("uri");
290 client
291 .complete_resource(&ref_value, arg_name, "", None)
292 .await?
293 }
294 };
295
296 self.formatter.display(&result)
297 }
298 }
299 }
300
301 async fn execute_server_command(&self, command: ServerCommands) -> CliResult<()> {
304 match command {
305 ServerCommands::Info { conn } => {
306 let client = create_client(&conn).await?;
307 let result = client.initialize().await?;
308 self.formatter.display_server_info(&result.server_info)
309 }
310
311 ServerCommands::Ping { conn } => {
312 let client = create_client(&conn).await?;
313 let start = std::time::Instant::now();
314
315 client.initialize().await?;
316 client.ping().await?;
317
318 let elapsed = start.elapsed();
319 println!("✓ Pong! ({:.2}ms)", elapsed.as_secs_f64() * 1000.0);
320 Ok(())
321 }
322
323 ServerCommands::LogLevel { conn, level } => {
324 let protocol_level: turbomcp_protocol::types::LogLevel = level.clone().into();
326
327 let client = create_client(&conn).await?;
328 client.initialize().await?;
329 client.set_log_level(protocol_level).await?;
330 println!("✓ Log level set to: {:?}", level);
331 Ok(())
332 }
333
334 ServerCommands::Roots { conn } => {
335 let client = create_client(&conn).await?;
337 let result = client.initialize().await?;
338
339 self.formatter.display(&result.server_capabilities)
341 }
342 }
343 }
344
345 async fn execute_sampling_command(&self, _command: SamplingCommands) -> CliResult<()> {
348 Err(CliError::NotSupported(
349 "Sampling commands require LLM handler implementation".to_string(),
350 ))
351 }
352
353 async fn execute_connect(&self, conn: Connection) -> CliResult<()> {
356 println!("Connecting to server...");
357 let client = create_client(&conn).await?;
358
359 let result = client.initialize().await?;
360
361 println!("✓ Connected successfully!");
362 self.formatter.display_server_info(&result.server_info)
363 }
364
365 async fn execute_status(&self, conn: Connection) -> CliResult<()> {
366 let client = create_client(&conn).await?;
367
368 let result = client.initialize().await?;
369
370 println!("Status: Connected");
371 self.formatter.display_server_info(&result.server_info)
372 }
373
374 fn execute_dev(&self, args: DevArgs) -> CliResult<()> {
377 crate::dev::execute(&args).map_err(|e| CliError::Other(e.to_string()))
378 }
379
380 fn execute_install(&self, args: InstallArgs) -> CliResult<()> {
381 crate::install::execute(&args).map_err(|e| CliError::Other(e.to_string()))
382 }
383
384 fn execute_build(&self, args: crate::cli::BuildArgs) -> CliResult<()> {
387 crate::build::execute(&args)
388 }
389
390 fn execute_deploy(&self, args: crate::cli::DeployArgs) -> CliResult<()> {
391 crate::deploy::execute(&args)
392 }
393
394 fn execute_new(&self, args: crate::cli::NewArgs) -> CliResult<()> {
395 crate::new::execute(&args)
396 }
397}