1use super::*;
6
7pub async fn cmd_mcp_list(url: &str, json: bool) -> Result<(), Box<dyn std::error::Error>> {
10 let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
11 let (OK, ACTION, WARN, DETAIL, ERR) = icons();
12 let c = RoboticusClient::new(url)?;
13 let data = c.get("/api/mcp/servers").await.map_err(|e| {
14 RoboticusClient::check_connectivity_hint(&*e);
15 e
16 })?;
17 if json {
18 println!("{}", serde_json::to_string_pretty(&data)?);
19 return Ok(());
20 }
21 heading("MCP Servers");
22 let servers = data.as_array().cloned().unwrap_or_default();
23 if servers.is_empty() {
24 eprintln!(" {DIM}No MCP servers configured.{RESET}");
25 } else {
26 kv("Configured servers", &servers.len().to_string());
27 for server in &servers {
28 let name = server["name"].as_str().unwrap_or("?");
29 let enabled = server["enabled"].as_bool().unwrap_or(false);
30 let connected = server["connected"].as_bool().unwrap_or(false);
31 let tool_count = server["tool_count"].as_u64().unwrap_or(0);
32 let status = if !enabled {
33 format!("{YELLOW}disabled{RESET}")
34 } else if connected {
35 format!("{GREEN}connected{RESET}")
36 } else {
37 format!("{RED}disconnected{RESET}")
38 };
39 kv(name, &format!("{status} tools={tool_count}"));
40 }
41 }
42 eprintln!();
43 Ok(())
44}
45
46pub fn cmd_mcp_add(
49 name: &str,
50 stdio: Option<&str>,
51 sse: Option<&str>,
52 args: &[String],
53) -> Result<(), Box<dyn std::error::Error>> {
54 let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
55 let (OK, ACTION, WARN, DETAIL, ERR) = icons();
56 match (stdio, sse) {
57 (Some(command), None) => {
58 eprintln!();
59 eprintln!(" {ACCENT}Adding STDIO MCP server '{name}'{RESET}");
60 eprintln!(" {DIM}command:{RESET} {MONO}{command}{RESET}");
61 if !args.is_empty() {
62 let arg_str = args
63 .iter()
64 .map(|a| format!("\"{}\"", a))
65 .collect::<Vec<_>>()
66 .join(", ");
67 eprintln!(" {DIM}args:{RESET} {MONO}[{arg_str}]{RESET}");
68 }
69 eprintln!();
70 eprintln!(" {DIM}Add the following to your {MONO}roboticus.toml{RESET}{DIM}:{RESET}");
71 eprintln!();
72 println!("[[mcp.servers]]");
73 println!("name = \"{name}\"");
74 println!("enabled = true");
75 println!("[mcp.servers.spec]");
76 println!("type = \"stdio\"");
77 println!("command = \"{command}\"");
78 if !args.is_empty() {
79 let arg_str = args
80 .iter()
81 .map(|a| format!("\"{}\"", a))
82 .collect::<Vec<_>>()
83 .join(", ");
84 println!("args = [{arg_str}]");
85 }
86 eprintln!();
87 }
88 (None, Some(url)) => {
89 eprintln!();
90 eprintln!(" {ACCENT}Adding SSE MCP server '{name}'{RESET}");
91 eprintln!(" {DIM}url:{RESET} {MONO}{url}{RESET}");
92 eprintln!();
93 eprintln!(" {DIM}Add the following to your {MONO}roboticus.toml{RESET}{DIM}:{RESET}");
94 eprintln!();
95 println!("[[mcp.servers]]");
96 println!("name = \"{name}\"");
97 println!("enabled = true");
98 println!("[mcp.servers.spec]");
99 println!("type = \"sse\"");
100 println!("url = \"{url}\"");
101 eprintln!();
102 }
103 _ => {
104 return Err("Specify either --stdio <command> or --sse <url>".into());
105 }
106 }
107 Ok(())
108}
109
110pub fn cmd_mcp_remove(name: &str) -> Result<(), Box<dyn std::error::Error>> {
113 let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
114 let (OK, ACTION, WARN, DETAIL, ERR) = icons();
115 eprintln!();
116 eprintln!(" {ACCENT}Remove MCP server '{name}'{RESET}");
117 eprintln!();
118 eprintln!(
119 " Delete the {MONO}[[mcp.servers]]{RESET} block with {MONO}name = \"{name}\"{RESET}"
120 );
121 eprintln!(" from your {MONO}roboticus.toml{RESET}, then restart the daemon:");
122 eprintln!();
123 println!(" roboticus daemon restart");
124 eprintln!();
125 Ok(())
126}
127
128pub async fn cmd_mcp_test(url: &str, name: &str) -> Result<(), Box<dyn std::error::Error>> {
131 let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
132 let (OK, ACTION, WARN, DETAIL, ERR) = icons();
133 let c = RoboticusClient::new(url)?;
134 eprintln!();
135 eprintln!(" {DIM}Testing MCP server '{MONO}{name}{RESET}{DIM}'...{RESET}");
136 let endpoint = format!("/api/mcp/servers/{name}/test");
137 match c.post(&endpoint, serde_json::json!({})).await {
138 Ok(data) => {
139 let ok = data["success"].as_bool().unwrap_or(false);
140 if ok {
141 eprintln!(" {GREEN}{OK} Connection test: PASSED{RESET}");
142 let tool_count = data["tool_count"].as_u64().unwrap_or(0);
143 eprintln!(" {DIM}Tools discovered:{RESET} {tool_count}");
144 } else {
145 let err = data["detail"].as_str().unwrap_or("unknown error");
146 eprintln!(" {RED}{ERR} Connection test: FAILED{RESET}");
147 eprintln!(" {DIM}Error:{RESET} {err}");
148 }
149 }
150 Err(e) => {
151 RoboticusClient::check_connectivity_hint(&*e);
152 eprintln!(" {RED}{ERR} Connection test: FAILED{RESET}");
153 eprintln!(" {DIM}Error:{RESET} {e}");
154 }
155 }
156 eprintln!();
157 Ok(())
158}