turbomcp_cli/
executor.rs

1//! Command execution using turbomcp-client
2
3use crate::cli::*;
4use crate::error::{CliError, CliResult};
5use crate::formatter::Formatter;
6use crate::transport::{ClientType, create_client};
7use std::collections::HashMap;
8
9/// Execute CLI commands
10pub struct CommandExecutor {
11    pub formatter: Formatter,
12    verbose: bool,
13}
14
15impl CommandExecutor {
16    pub fn new(format: OutputFormat, colored: bool, verbose: bool) -> Self {
17        Self {
18            formatter: Formatter::new(format, colored),
19            verbose,
20        }
21    }
22
23    /// Display an error with rich formatting
24    pub fn display_error(&self, error: &CliError) {
25        self.formatter.display_error(error);
26    }
27
28    /// Execute a command
29    pub async fn execute(&self, command: Commands) -> CliResult<()> {
30        match command {
31            Commands::Tools(cmd) => self.execute_tool_command(cmd).await,
32            Commands::Resources(cmd) => self.execute_resource_command(cmd).await,
33            Commands::Prompts(cmd) => self.execute_prompt_command(cmd).await,
34            Commands::Complete(cmd) => self.execute_completion_command(cmd).await,
35            Commands::Server(cmd) => self.execute_server_command(cmd).await,
36            Commands::Sample(cmd) => self.execute_sampling_command(cmd).await,
37            Commands::Connect(conn) => self.execute_connect(conn).await,
38            Commands::Status(conn) => self.execute_status(conn).await,
39        }
40    }
41
42    // Tool commands
43
44    async fn execute_tool_command(&self, command: ToolCommands) -> CliResult<()> {
45        match command {
46            ToolCommands::List { conn } => {
47                let client_type = create_client(&conn).await?;
48                let tools = match client_type {
49                    ClientType::Stdio(client) => {
50                        client.initialize().await?;
51                        client.list_tools().await?
52                    }
53                    ClientType::Tcp(client) => {
54                        client.initialize().await?;
55                        client.list_tools().await?
56                    }
57                    ClientType::Unix(client) => {
58                        client.initialize().await?;
59                        client.list_tools().await?
60                    }
61                };
62                self.formatter.display_tools(&tools)
63            }
64
65            ToolCommands::Call {
66                conn,
67                name,
68                arguments,
69            } => {
70                let args: HashMap<String, serde_json::Value> =
71                    if arguments.trim().is_empty() || arguments == "{}" {
72                        HashMap::new()
73                    } else {
74                        serde_json::from_str(&arguments).map_err(|e| {
75                            CliError::InvalidArguments(format!("Invalid JSON arguments: {}", e))
76                        })?
77                    };
78
79                let client_type = create_client(&conn).await?;
80                let result = match client_type {
81                    ClientType::Stdio(client) => {
82                        client.initialize().await?;
83                        client.call_tool(&name, Some(args)).await?
84                    }
85                    ClientType::Tcp(client) => {
86                        client.initialize().await?;
87                        client.call_tool(&name, Some(args)).await?
88                    }
89                    ClientType::Unix(client) => {
90                        client.initialize().await?;
91                        client.call_tool(&name, Some(args)).await?
92                    }
93                };
94                self.formatter.display(&result)
95            }
96
97            ToolCommands::Schema { conn, name } => {
98                let client_type = create_client(&conn).await?;
99                let tools = match client_type {
100                    ClientType::Stdio(client) => {
101                        client.initialize().await?;
102                        client.list_tools().await?
103                    }
104                    ClientType::Tcp(client) => {
105                        client.initialize().await?;
106                        client.list_tools().await?
107                    }
108                    ClientType::Unix(client) => {
109                        client.initialize().await?;
110                        client.list_tools().await?
111                    }
112                };
113
114                if let Some(tool_name) = name {
115                    let tool = tools.iter().find(|t| t.name == tool_name).ok_or_else(|| {
116                        CliError::Other(format!("Tool '{}' not found", tool_name))
117                    })?;
118
119                    self.formatter.display(&tool.input_schema)
120                } else {
121                    let schemas: Vec<_> = tools
122                        .iter()
123                        .map(|t| {
124                            serde_json::json!({
125                                "name": t.name,
126                                "schema": t.input_schema
127                            })
128                        })
129                        .collect();
130
131                    self.formatter.display(&schemas)
132                }
133            }
134
135            ToolCommands::Export { conn, output } => {
136                let client_type = create_client(&conn).await?;
137                let tools = match client_type {
138                    ClientType::Stdio(client) => {
139                        client.initialize().await?;
140                        client.list_tools().await?
141                    }
142                    ClientType::Tcp(client) => {
143                        client.initialize().await?;
144                        client.list_tools().await?
145                    }
146                    ClientType::Unix(client) => {
147                        client.initialize().await?;
148                        client.list_tools().await?
149                    }
150                };
151
152                std::fs::create_dir_all(&output)?;
153
154                for tool in tools {
155                    let filename = format!("{}.json", tool.name);
156                    let filepath = output.join(filename);
157                    let schema = serde_json::to_string_pretty(&tool.input_schema)?;
158                    std::fs::write(&filepath, schema)?;
159
160                    if self.verbose {
161                        println!("Exported: {}", filepath.display());
162                    }
163                }
164
165                println!("✓ Exported schemas to: {}", output.display());
166                Ok(())
167            }
168        }
169    }
170
171    // Resource commands
172
173    async fn execute_resource_command(&self, command: ResourceCommands) -> CliResult<()> {
174        match command {
175            ResourceCommands::List { conn } => {
176                let client_type = create_client(&conn).await?;
177                let resource_uris = match client_type {
178                    ClientType::Stdio(client) => {
179                        client.initialize().await?;
180                        client.list_resources().await?
181                    }
182                    ClientType::Tcp(client) => {
183                        client.initialize().await?;
184                        client.list_resources().await?
185                    }
186                    ClientType::Unix(client) => {
187                        client.initialize().await?;
188                        client.list_resources().await?
189                    }
190                };
191
192                // Note: Client returns URIs only. For full Resource objects with metadata,
193                // we would need to extend the client API.
194                self.formatter.display(&resource_uris)
195            }
196
197            ResourceCommands::Read { conn, uri } => {
198                let client_type = create_client(&conn).await?;
199                let result = match client_type {
200                    ClientType::Stdio(client) => {
201                        client.initialize().await?;
202                        client.read_resource(&uri).await?
203                    }
204                    ClientType::Tcp(client) => {
205                        client.initialize().await?;
206                        client.read_resource(&uri).await?
207                    }
208                    ClientType::Unix(client) => {
209                        client.initialize().await?;
210                        client.read_resource(&uri).await?
211                    }
212                };
213                self.formatter.display(&result)
214            }
215
216            ResourceCommands::Templates { conn } => {
217                let client_type = create_client(&conn).await?;
218                let templates = match client_type {
219                    ClientType::Stdio(client) => {
220                        client.initialize().await?;
221                        client.list_resource_templates().await?
222                    }
223                    ClientType::Tcp(client) => {
224                        client.initialize().await?;
225                        client.list_resource_templates().await?
226                    }
227                    ClientType::Unix(client) => {
228                        client.initialize().await?;
229                        client.list_resource_templates().await?
230                    }
231                };
232                self.formatter.display(&templates)
233            }
234
235            ResourceCommands::Subscribe { conn, uri } => {
236                let client_type = create_client(&conn).await?;
237                match client_type {
238                    ClientType::Stdio(client) => {
239                        client.initialize().await?;
240                        client.subscribe(&uri).await?;
241                    }
242                    ClientType::Tcp(client) => {
243                        client.initialize().await?;
244                        client.subscribe(&uri).await?;
245                    }
246                    ClientType::Unix(client) => {
247                        client.initialize().await?;
248                        client.subscribe(&uri).await?;
249                    }
250                }
251                println!("✓ Subscribed to: {uri}");
252                Ok(())
253            }
254
255            ResourceCommands::Unsubscribe { conn, uri } => {
256                let client_type = create_client(&conn).await?;
257                match client_type {
258                    ClientType::Stdio(client) => {
259                        client.initialize().await?;
260                        client.unsubscribe(&uri).await?;
261                    }
262                    ClientType::Tcp(client) => {
263                        client.initialize().await?;
264                        client.unsubscribe(&uri).await?;
265                    }
266                    ClientType::Unix(client) => {
267                        client.initialize().await?;
268                        client.unsubscribe(&uri).await?;
269                    }
270                }
271                println!("✓ Unsubscribed from: {uri}");
272                Ok(())
273            }
274        }
275    }
276
277    // Prompt commands
278
279    async fn execute_prompt_command(&self, command: PromptCommands) -> CliResult<()> {
280        match command {
281            PromptCommands::List { conn } => {
282                let client_type = create_client(&conn).await?;
283                let prompts = match client_type {
284                    ClientType::Stdio(client) => {
285                        client.initialize().await?;
286                        client.list_prompts().await?
287                    }
288                    ClientType::Tcp(client) => {
289                        client.initialize().await?;
290                        client.list_prompts().await?
291                    }
292                    ClientType::Unix(client) => {
293                        client.initialize().await?;
294                        client.list_prompts().await?
295                    }
296                };
297                self.formatter.display_prompts(&prompts)
298            }
299
300            PromptCommands::Get {
301                conn,
302                name,
303                arguments,
304            } => {
305                // Parse arguments as HashMap<String, Value>
306                let args: HashMap<String, serde_json::Value> =
307                    if arguments.trim().is_empty() || arguments == "{}" {
308                        HashMap::new()
309                    } else {
310                        serde_json::from_str(&arguments).map_err(|e| {
311                            CliError::InvalidArguments(format!("Invalid JSON arguments: {}", e))
312                        })?
313                    };
314
315                let args_option = if args.is_empty() { None } else { Some(args) };
316
317                let client_type = create_client(&conn).await?;
318                let result = match client_type {
319                    ClientType::Stdio(client) => {
320                        client.initialize().await?;
321                        client.get_prompt(&name, args_option).await?
322                    }
323                    ClientType::Tcp(client) => {
324                        client.initialize().await?;
325                        client.get_prompt(&name, args_option.clone()).await?
326                    }
327                    ClientType::Unix(client) => {
328                        client.initialize().await?;
329                        client.get_prompt(&name, args_option.clone()).await?
330                    }
331                };
332                self.formatter.display(&result)
333            }
334
335            PromptCommands::Schema { conn, name } => {
336                let client_type = create_client(&conn).await?;
337                let prompts = match client_type {
338                    ClientType::Stdio(client) => {
339                        client.initialize().await?;
340                        client.list_prompts().await?
341                    }
342                    ClientType::Tcp(client) => {
343                        client.initialize().await?;
344                        client.list_prompts().await?
345                    }
346                    ClientType::Unix(client) => {
347                        client.initialize().await?;
348                        client.list_prompts().await?
349                    }
350                };
351
352                let prompt = prompts
353                    .iter()
354                    .find(|p| p.name == name)
355                    .ok_or_else(|| CliError::Other(format!("Prompt '{}' not found", name)))?;
356
357                self.formatter.display(&prompt.arguments)
358            }
359        }
360    }
361
362    // Completion commands
363
364    async fn execute_completion_command(&self, command: CompletionCommands) -> CliResult<()> {
365        match command {
366            CompletionCommands::Get {
367                conn,
368                ref_type,
369                ref_value,
370                argument,
371            } => {
372                let client_type = create_client(&conn).await?;
373
374                // Use the appropriate completion method based on reference type
375                let result = match ref_type {
376                    RefType::Prompt => {
377                        let arg_name = argument.as_deref().unwrap_or("value");
378                        match client_type {
379                            ClientType::Stdio(client) => {
380                                client.initialize().await?;
381                                client
382                                    .complete_prompt(&ref_value, arg_name, "", None)
383                                    .await?
384                            }
385                            ClientType::Tcp(client) => {
386                                client.initialize().await?;
387                                client
388                                    .complete_prompt(&ref_value, arg_name, "", None)
389                                    .await?
390                            }
391                            ClientType::Unix(client) => {
392                                client.initialize().await?;
393                                client
394                                    .complete_prompt(&ref_value, arg_name, "", None)
395                                    .await?
396                            }
397                        }
398                    }
399                    RefType::Resource => {
400                        let arg_name = argument.as_deref().unwrap_or("uri");
401                        match client_type {
402                            ClientType::Stdio(client) => {
403                                client.initialize().await?;
404                                client
405                                    .complete_resource(&ref_value, arg_name, "", None)
406                                    .await?
407                            }
408                            ClientType::Tcp(client) => {
409                                client.initialize().await?;
410                                client
411                                    .complete_resource(&ref_value, arg_name, "", None)
412                                    .await?
413                            }
414                            ClientType::Unix(client) => {
415                                client.initialize().await?;
416                                client
417                                    .complete_resource(&ref_value, arg_name, "", None)
418                                    .await?
419                            }
420                        }
421                    }
422                };
423
424                self.formatter.display(&result)
425            }
426        }
427    }
428
429    // Server commands
430
431    async fn execute_server_command(&self, command: ServerCommands) -> CliResult<()> {
432        match command {
433            ServerCommands::Info { conn } => {
434                let client_type = create_client(&conn).await?;
435                let info = match client_type {
436                    ClientType::Stdio(client) => {
437                        let result = client.initialize().await?;
438                        result.server_info
439                    }
440                    ClientType::Tcp(client) => {
441                        let result = client.initialize().await?;
442                        result.server_info
443                    }
444                    ClientType::Unix(client) => {
445                        let result = client.initialize().await?;
446                        result.server_info
447                    }
448                };
449                self.formatter.display_server_info(&info)
450            }
451
452            ServerCommands::Ping { conn } => {
453                let client_type = create_client(&conn).await?;
454                let start = std::time::Instant::now();
455
456                match client_type {
457                    ClientType::Stdio(client) => {
458                        client.initialize().await?;
459                        client.ping().await?;
460                    }
461                    ClientType::Tcp(client) => {
462                        client.initialize().await?;
463                        client.ping().await?;
464                    }
465                    ClientType::Unix(client) => {
466                        client.initialize().await?;
467                        client.ping().await?;
468                    }
469                }
470
471                let elapsed = start.elapsed();
472                println!("✓ Pong! ({:.2}ms)", elapsed.as_secs_f64() * 1000.0);
473                Ok(())
474            }
475
476            ServerCommands::LogLevel { conn, level } => {
477                // Convert level once before using
478                let protocol_level: turbomcp_protocol::types::LogLevel = level.clone().into();
479
480                let client_type = create_client(&conn).await?;
481                match client_type {
482                    ClientType::Stdio(client) => {
483                        client.initialize().await?;
484                        client.set_log_level(protocol_level).await?;
485                    }
486                    ClientType::Tcp(client) => {
487                        client.initialize().await?;
488                        client.set_log_level(protocol_level).await?;
489                    }
490                    ClientType::Unix(client) => {
491                        client.initialize().await?;
492                        client.set_log_level(protocol_level).await?;
493                    }
494                }
495                println!("✓ Log level set to: {:?}", level);
496                Ok(())
497            }
498
499            ServerCommands::Roots { conn } => {
500                // Roots are part of server capabilities returned during initialization
501                let client_type = create_client(&conn).await?;
502                let result = match client_type {
503                    ClientType::Stdio(client) => client.initialize().await?,
504                    ClientType::Tcp(client) => client.initialize().await?,
505                    ClientType::Unix(client) => client.initialize().await?,
506                };
507
508                // Display server capabilities which includes roots info
509                self.formatter.display(&result.server_capabilities)
510            }
511        }
512    }
513
514    // Sampling commands
515
516    async fn execute_sampling_command(&self, _command: SamplingCommands) -> CliResult<()> {
517        Err(CliError::NotSupported(
518            "Sampling commands require LLM handler implementation".to_string(),
519        ))
520    }
521
522    // Connection commands
523
524    async fn execute_connect(&self, conn: Connection) -> CliResult<()> {
525        println!("Connecting to server...");
526        let client_type = create_client(&conn).await?;
527
528        let info = match client_type {
529            ClientType::Stdio(client) => {
530                let result = client.initialize().await?;
531                result.server_info
532            }
533            ClientType::Tcp(client) => {
534                let result = client.initialize().await?;
535                result.server_info
536            }
537            ClientType::Unix(client) => {
538                let result = client.initialize().await?;
539                result.server_info
540            }
541        };
542
543        println!("✓ Connected successfully!");
544        self.formatter.display_server_info(&info)
545    }
546
547    async fn execute_status(&self, conn: Connection) -> CliResult<()> {
548        let client_type = create_client(&conn).await?;
549
550        let info = match client_type {
551            ClientType::Stdio(client) => {
552                let result = client.initialize().await?;
553                result.server_info
554            }
555            ClientType::Tcp(client) => {
556                let result = client.initialize().await?;
557                result.server_info
558            }
559            ClientType::Unix(client) => {
560                let result = client.initialize().await?;
561                result.server_info
562            }
563        };
564
565        println!("Status: Connected");
566        self.formatter.display_server_info(&info)
567    }
568}