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