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::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 = create_client(&conn).await?;
49                client.initialize().await?;
50                let tools = client.list_tools().await?;
51                self.formatter.display_tools(&tools)
52            }
53
54            ToolCommands::Call {
55                conn,
56                name,
57                arguments,
58            } => {
59                let args: HashMap<String, serde_json::Value> =
60                    if arguments.trim().is_empty() || arguments == "{}" {
61                        HashMap::new()
62                    } else {
63                        serde_json::from_str(&arguments).map_err(|e| {
64                            CliError::InvalidArguments(format!("Invalid JSON arguments: {}", e))
65                        })?
66                    };
67
68                let client = create_client(&conn).await?;
69                client.initialize().await?;
70                let result = client.call_tool(&name, Some(args)).await?;
71                self.formatter.display(&result)
72            }
73
74            ToolCommands::Schema { conn, name } => {
75                let client = create_client(&conn).await?;
76                client.initialize().await?;
77                let tools = client.list_tools().await?;
78
79                if let Some(tool_name) = name {
80                    let tool = tools.iter().find(|t| t.name == tool_name).ok_or_else(|| {
81                        CliError::Other(format!("Tool '{}' not found", tool_name))
82                    })?;
83
84                    self.formatter.display(&tool.input_schema)
85                } else {
86                    let schemas: Vec<_> = tools
87                        .iter()
88                        .map(|t| {
89                            serde_json::json!({
90                                "name": t.name,
91                                "schema": t.input_schema
92                            })
93                        })
94                        .collect();
95
96                    self.formatter.display(&schemas)
97                }
98            }
99
100            ToolCommands::Export { conn, output } => {
101                let client = create_client(&conn).await?;
102                client.initialize().await?;
103                let tools = client.list_tools().await?;
104
105                std::fs::create_dir_all(&output)?;
106
107                for tool in tools {
108                    let filename = format!("{}.json", tool.name);
109                    let filepath = output.join(filename);
110                    let schema = serde_json::to_string_pretty(&tool.input_schema)?;
111                    std::fs::write(&filepath, schema)?;
112
113                    if self.verbose {
114                        println!("Exported: {}", filepath.display());
115                    }
116                }
117
118                println!("✓ Exported schemas to: {}", output.display());
119                Ok(())
120            }
121        }
122    }
123
124    // Resource commands
125
126    async fn execute_resource_command(&self, command: ResourceCommands) -> CliResult<()> {
127        match command {
128            ResourceCommands::List { conn } => {
129                let client = create_client(&conn).await?;
130                client.initialize().await?;
131                let resources = client.list_resources().await?;
132                self.formatter.display(&resources)
133            }
134
135            ResourceCommands::Read { conn, uri } => {
136                let client = create_client(&conn).await?;
137                client.initialize().await?;
138                let result = client.read_resource(&uri).await?;
139                self.formatter.display(&result)
140            }
141
142            ResourceCommands::Templates { conn } => {
143                let client = create_client(&conn).await?;
144                client.initialize().await?;
145                let templates = client.list_resource_templates().await?;
146                self.formatter.display(&templates)
147            }
148
149            ResourceCommands::Subscribe { conn, uri } => {
150                let client = create_client(&conn).await?;
151                client.initialize().await?;
152                client.subscribe(&uri).await?;
153                println!("✓ Subscribed to: {uri}");
154                Ok(())
155            }
156
157            ResourceCommands::Unsubscribe { conn, uri } => {
158                let client = create_client(&conn).await?;
159                client.initialize().await?;
160                client.unsubscribe(&uri).await?;
161                println!("✓ Unsubscribed from: {uri}");
162                Ok(())
163            }
164        }
165    }
166
167    // Prompt commands
168
169    async fn execute_prompt_command(&self, command: PromptCommands) -> CliResult<()> {
170        match command {
171            PromptCommands::List { conn } => {
172                let client = create_client(&conn).await?;
173                client.initialize().await?;
174                let prompts = client.list_prompts().await?;
175                self.formatter.display_prompts(&prompts)
176            }
177
178            PromptCommands::Get {
179                conn,
180                name,
181                arguments,
182            } => {
183                // Parse arguments as HashMap<String, Value>
184                let args: HashMap<String, serde_json::Value> =
185                    if arguments.trim().is_empty() || arguments == "{}" {
186                        HashMap::new()
187                    } else {
188                        serde_json::from_str(&arguments).map_err(|e| {
189                            CliError::InvalidArguments(format!("Invalid JSON arguments: {}", e))
190                        })?
191                    };
192
193                let args_option = if args.is_empty() { None } else { Some(args) };
194
195                let client = create_client(&conn).await?;
196                client.initialize().await?;
197                let result = client.get_prompt(&name, args_option).await?;
198                self.formatter.display(&result)
199            }
200
201            PromptCommands::Schema { conn, name } => {
202                let client = create_client(&conn).await?;
203                client.initialize().await?;
204                let prompts = client.list_prompts().await?;
205
206                let prompt = prompts
207                    .iter()
208                    .find(|p| p.name == name)
209                    .ok_or_else(|| CliError::Other(format!("Prompt '{}' not found", name)))?;
210
211                self.formatter.display(&prompt.arguments)
212            }
213        }
214    }
215
216    // Completion commands
217
218    async fn execute_completion_command(&self, command: CompletionCommands) -> CliResult<()> {
219        match command {
220            CompletionCommands::Get {
221                conn,
222                ref_type,
223                ref_value,
224                argument,
225            } => {
226                let client = create_client(&conn).await?;
227                client.initialize().await?;
228
229                // Use the appropriate completion method based on reference type
230                let result = match ref_type {
231                    RefType::Prompt => {
232                        let arg_name = argument.as_deref().unwrap_or("value");
233                        client
234                            .complete_prompt(&ref_value, arg_name, "", None)
235                            .await?
236                    }
237                    RefType::Resource => {
238                        let arg_name = argument.as_deref().unwrap_or("uri");
239                        client
240                            .complete_resource(&ref_value, arg_name, "", None)
241                            .await?
242                    }
243                };
244
245                self.formatter.display(&result)
246            }
247        }
248    }
249
250    // Server commands
251
252    async fn execute_server_command(&self, command: ServerCommands) -> CliResult<()> {
253        match command {
254            ServerCommands::Info { conn } => {
255                let client = create_client(&conn).await?;
256                let result = client.initialize().await?;
257                self.formatter.display_server_info(&result.server_info)
258            }
259
260            ServerCommands::Ping { conn } => {
261                let client = create_client(&conn).await?;
262                let start = std::time::Instant::now();
263
264                client.initialize().await?;
265                client.ping().await?;
266
267                let elapsed = start.elapsed();
268                println!("✓ Pong! ({:.2}ms)", elapsed.as_secs_f64() * 1000.0);
269                Ok(())
270            }
271
272            ServerCommands::LogLevel { conn, level } => {
273                // Convert level once before using
274                let protocol_level: turbomcp_protocol::types::LogLevel = level.clone().into();
275
276                let client = create_client(&conn).await?;
277                client.initialize().await?;
278                client.set_log_level(protocol_level).await?;
279                println!("✓ Log level set to: {:?}", level);
280                Ok(())
281            }
282
283            ServerCommands::Roots { conn } => {
284                // Roots are part of server capabilities returned during initialization
285                let client = create_client(&conn).await?;
286                let result = client.initialize().await?;
287
288                // Display server capabilities which includes roots info
289                self.formatter.display(&result.server_capabilities)
290            }
291        }
292    }
293
294    // Sampling commands
295
296    async fn execute_sampling_command(&self, _command: SamplingCommands) -> CliResult<()> {
297        Err(CliError::NotSupported(
298            "Sampling commands require LLM handler implementation".to_string(),
299        ))
300    }
301
302    // Connection commands
303
304    async fn execute_connect(&self, conn: Connection) -> CliResult<()> {
305        println!("Connecting to server...");
306        let client = create_client(&conn).await?;
307
308        let result = client.initialize().await?;
309
310        println!("✓ Connected successfully!");
311        self.formatter.display_server_info(&result.server_info)
312    }
313
314    async fn execute_status(&self, conn: Connection) -> CliResult<()> {
315        let client = create_client(&conn).await?;
316
317        let result = client.initialize().await?;
318
319        println!("Status: Connected");
320        self.formatter.display_server_info(&result.server_info)
321    }
322}