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 resource_uris = 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                // Note: Client returns URIs only. For full Resource objects with metadata,
194                // we would need to extend the client API.
195                self.formatter.display(&resource_uris)
196            }
197
198            ResourceCommands::Read { conn, uri } => {
199                let client_type = create_client(&conn).await?;
200                let result = match client_type {
201                    ClientType::Stdio(client) => {
202                        client.initialize().await?;
203                        client.read_resource(&uri).await?
204                    }
205                    ClientType::Tcp(client) => {
206                        client.initialize().await?;
207                        client.read_resource(&uri).await?
208                    }
209                    ClientType::Unix(client) => {
210                        client.initialize().await?;
211                        client.read_resource(&uri).await?
212                    }
213                };
214                self.formatter.display(&result)
215            }
216
217            ResourceCommands::Templates { conn } => {
218                let client_type = create_client(&conn).await?;
219                let templates = match client_type {
220                    ClientType::Stdio(client) => {
221                        client.initialize().await?;
222                        client.list_resource_templates().await?
223                    }
224                    ClientType::Tcp(client) => {
225                        client.initialize().await?;
226                        client.list_resource_templates().await?
227                    }
228                    ClientType::Unix(client) => {
229                        client.initialize().await?;
230                        client.list_resource_templates().await?
231                    }
232                };
233                self.formatter.display(&templates)
234            }
235
236            ResourceCommands::Subscribe { conn, uri } => {
237                let client_type = create_client(&conn).await?;
238                match client_type {
239                    ClientType::Stdio(client) => {
240                        client.initialize().await?;
241                        client.subscribe(&uri).await?;
242                    }
243                    ClientType::Tcp(client) => {
244                        client.initialize().await?;
245                        client.subscribe(&uri).await?;
246                    }
247                    ClientType::Unix(client) => {
248                        client.initialize().await?;
249                        client.subscribe(&uri).await?;
250                    }
251                }
252                println!("✓ Subscribed to: {uri}");
253                Ok(())
254            }
255
256            ResourceCommands::Unsubscribe { conn, uri } => {
257                let client_type = create_client(&conn).await?;
258                match client_type {
259                    ClientType::Stdio(client) => {
260                        client.initialize().await?;
261                        client.unsubscribe(&uri).await?;
262                    }
263                    ClientType::Tcp(client) => {
264                        client.initialize().await?;
265                        client.unsubscribe(&uri).await?;
266                    }
267                    ClientType::Unix(client) => {
268                        client.initialize().await?;
269                        client.unsubscribe(&uri).await?;
270                    }
271                }
272                println!("✓ Unsubscribed from: {uri}");
273                Ok(())
274            }
275        }
276    }
277
278    // Prompt commands
279
280    async fn execute_prompt_command(&self, command: PromptCommands) -> CliResult<()> {
281        match command {
282            PromptCommands::List { conn } => {
283                let client_type = create_client(&conn).await?;
284                let prompts = match client_type {
285                    ClientType::Stdio(client) => {
286                        client.initialize().await?;
287                        client.list_prompts().await?
288                    }
289                    ClientType::Tcp(client) => {
290                        client.initialize().await?;
291                        client.list_prompts().await?
292                    }
293                    ClientType::Unix(client) => {
294                        client.initialize().await?;
295                        client.list_prompts().await?
296                    }
297                };
298                self.formatter.display_prompts(&prompts)
299            }
300
301            PromptCommands::Get {
302                conn,
303                name,
304                arguments,
305            } => {
306                // Parse arguments as HashMap<String, Value>
307                let args: HashMap<String, serde_json::Value> =
308                    if arguments.trim().is_empty() || arguments == "{}" {
309                        HashMap::new()
310                    } else {
311                        serde_json::from_str(&arguments).map_err(|e| {
312                            CliError::InvalidArguments(format!("Invalid JSON arguments: {}", e))
313                        })?
314                    };
315
316                let args_option = if args.is_empty() { None } else { Some(args) };
317
318                let client_type = create_client(&conn).await?;
319                let result = match client_type {
320                    ClientType::Stdio(client) => {
321                        client.initialize().await?;
322                        client.get_prompt(&name, args_option).await?
323                    }
324                    ClientType::Tcp(client) => {
325                        client.initialize().await?;
326                        client.get_prompt(&name, args_option.clone()).await?
327                    }
328                    ClientType::Unix(client) => {
329                        client.initialize().await?;
330                        client.get_prompt(&name, args_option.clone()).await?
331                    }
332                };
333                self.formatter.display(&result)
334            }
335
336            PromptCommands::Schema { conn, name } => {
337                let client_type = create_client(&conn).await?;
338                let prompts = match client_type {
339                    ClientType::Stdio(client) => {
340                        client.initialize().await?;
341                        client.list_prompts().await?
342                    }
343                    ClientType::Tcp(client) => {
344                        client.initialize().await?;
345                        client.list_prompts().await?
346                    }
347                    ClientType::Unix(client) => {
348                        client.initialize().await?;
349                        client.list_prompts().await?
350                    }
351                };
352
353                let prompt = prompts
354                    .iter()
355                    .find(|p| p.name == name)
356                    .ok_or_else(|| CliError::Other(format!("Prompt '{}' not found", name)))?;
357
358                self.formatter.display(&prompt.arguments)
359            }
360        }
361    }
362
363    // Completion commands
364
365    async fn execute_completion_command(&self, command: CompletionCommands) -> CliResult<()> {
366        match command {
367            CompletionCommands::Get {
368                conn,
369                ref_type,
370                ref_value,
371                argument,
372            } => {
373                let client_type = create_client(&conn).await?;
374
375                // Use the appropriate completion method based on reference type
376                let result = match ref_type {
377                    RefType::Prompt => {
378                        let arg_name = argument.as_deref().unwrap_or("value");
379                        match client_type {
380                            ClientType::Stdio(client) => {
381                                client.initialize().await?;
382                                client
383                                    .complete_prompt(&ref_value, arg_name, "", None)
384                                    .await?
385                            }
386                            ClientType::Tcp(client) => {
387                                client.initialize().await?;
388                                client
389                                    .complete_prompt(&ref_value, arg_name, "", None)
390                                    .await?
391                            }
392                            ClientType::Unix(client) => {
393                                client.initialize().await?;
394                                client
395                                    .complete_prompt(&ref_value, arg_name, "", None)
396                                    .await?
397                            }
398                        }
399                    }
400                    RefType::Resource => {
401                        let arg_name = argument.as_deref().unwrap_or("uri");
402                        match client_type {
403                            ClientType::Stdio(client) => {
404                                client.initialize().await?;
405                                client
406                                    .complete_resource(&ref_value, arg_name, "", None)
407                                    .await?
408                            }
409                            ClientType::Tcp(client) => {
410                                client.initialize().await?;
411                                client
412                                    .complete_resource(&ref_value, arg_name, "", None)
413                                    .await?
414                            }
415                            ClientType::Unix(client) => {
416                                client.initialize().await?;
417                                client
418                                    .complete_resource(&ref_value, arg_name, "", None)
419                                    .await?
420                            }
421                        }
422                    }
423                };
424
425                self.formatter.display(&result)
426            }
427        }
428    }
429
430    // Server commands
431
432    async fn execute_server_command(&self, command: ServerCommands) -> CliResult<()> {
433        match command {
434            ServerCommands::Info { conn } => {
435                let client_type = create_client(&conn).await?;
436                let info = match client_type {
437                    ClientType::Stdio(client) => {
438                        let result = client.initialize().await?;
439                        result.server_info
440                    }
441                    ClientType::Tcp(client) => {
442                        let result = client.initialize().await?;
443                        result.server_info
444                    }
445                    ClientType::Unix(client) => {
446                        let result = client.initialize().await?;
447                        result.server_info
448                    }
449                };
450                self.formatter.display_server_info(&info)
451            }
452
453            ServerCommands::Ping { conn } => {
454                let client_type = create_client(&conn).await?;
455                let start = std::time::Instant::now();
456
457                match client_type {
458                    ClientType::Stdio(client) => {
459                        client.initialize().await?;
460                        client.ping().await?;
461                    }
462                    ClientType::Tcp(client) => {
463                        client.initialize().await?;
464                        client.ping().await?;
465                    }
466                    ClientType::Unix(client) => {
467                        client.initialize().await?;
468                        client.ping().await?;
469                    }
470                }
471
472                let elapsed = start.elapsed();
473                println!("✓ Pong! ({:.2}ms)", elapsed.as_secs_f64() * 1000.0);
474                Ok(())
475            }
476
477            ServerCommands::LogLevel { conn, level } => {
478                // Convert level once before using
479                let protocol_level: turbomcp_protocol::types::LogLevel = level.clone().into();
480
481                let client_type = create_client(&conn).await?;
482                match client_type {
483                    ClientType::Stdio(client) => {
484                        client.initialize().await?;
485                        client.set_log_level(protocol_level).await?;
486                    }
487                    ClientType::Tcp(client) => {
488                        client.initialize().await?;
489                        client.set_log_level(protocol_level).await?;
490                    }
491                    ClientType::Unix(client) => {
492                        client.initialize().await?;
493                        client.set_log_level(protocol_level).await?;
494                    }
495                }
496                println!("✓ Log level set to: {:?}", level);
497                Ok(())
498            }
499
500            ServerCommands::Roots { conn } => {
501                // Roots are part of server capabilities returned during initialization
502                let client_type = create_client(&conn).await?;
503                let result = match client_type {
504                    ClientType::Stdio(client) => client.initialize().await?,
505                    ClientType::Tcp(client) => client.initialize().await?,
506                    ClientType::Unix(client) => client.initialize().await?,
507                };
508
509                // Display server capabilities which includes roots info
510                self.formatter.display(&result.server_capabilities)
511            }
512        }
513    }
514
515    // Sampling commands
516
517    async fn execute_sampling_command(&self, _command: SamplingCommands) -> CliResult<()> {
518        Err(CliError::NotSupported(
519            "Sampling commands require LLM handler implementation".to_string(),
520        ))
521    }
522
523    // Connection commands
524
525    async fn execute_connect(&self, conn: Connection) -> CliResult<()> {
526        println!("Connecting to server...");
527        let client_type = create_client(&conn).await?;
528
529        let info = match client_type {
530            ClientType::Stdio(client) => {
531                let result = client.initialize().await?;
532                result.server_info
533            }
534            ClientType::Tcp(client) => {
535                let result = client.initialize().await?;
536                result.server_info
537            }
538            ClientType::Unix(client) => {
539                let result = client.initialize().await?;
540                result.server_info
541            }
542        };
543
544        println!("✓ Connected successfully!");
545        self.formatter.display_server_info(&info)
546    }
547
548    async fn execute_status(&self, conn: Connection) -> CliResult<()> {
549        let client_type = create_client(&conn).await?;
550
551        let info = match client_type {
552            ClientType::Stdio(client) => {
553                let result = client.initialize().await?;
554                result.server_info
555            }
556            ClientType::Tcp(client) => {
557                let result = client.initialize().await?;
558                result.server_info
559            }
560            ClientType::Unix(client) => {
561                let result = client.initialize().await?;
562                result.server_info
563            }
564        };
565
566        println!("Status: Connected");
567        self.formatter.display_server_info(&info)
568    }
569}