1#[cfg(feature = "async")]
2use crate::Claude;
3use crate::command::ClaudeCommand;
4#[cfg(feature = "async")]
5use crate::error::Result;
6#[cfg(feature = "async")]
7use crate::exec;
8use crate::exec::CommandOutput;
9use crate::types::{Scope, Transport};
10
11#[derive(Debug, Clone, Default)]
26pub struct McpListCommand;
27
28impl McpListCommand {
29 #[must_use]
31 pub fn new() -> Self {
32 Self
33 }
34}
35
36impl ClaudeCommand for McpListCommand {
37 type Output = CommandOutput;
38
39 fn args(&self) -> Vec<String> {
40 vec!["mcp".to_string(), "list".to_string()]
41 }
42
43 #[cfg(feature = "async")]
44 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
45 exec::run_claude(claude, self.args()).await
46 }
47}
48
49#[derive(Debug, Clone)]
51pub struct McpGetCommand {
52 name: String,
53}
54
55impl McpGetCommand {
56 #[must_use]
58 pub fn new(name: impl Into<String>) -> Self {
59 Self { name: name.into() }
60 }
61}
62
63impl ClaudeCommand for McpGetCommand {
64 type Output = CommandOutput;
65
66 fn args(&self) -> Vec<String> {
67 vec!["mcp".to_string(), "get".to_string(), self.name.clone()]
68 }
69
70 #[cfg(feature = "async")]
71 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
72 exec::run_claude(claude, self.args()).await
73 }
74}
75
76#[derive(Debug, Clone)]
104pub struct McpAddCommand {
105 name: String,
106 command_or_url: String,
107 server_args: Vec<String>,
108 transport: Option<Transport>,
109 scope: Option<Scope>,
110 env: Vec<(String, String)>,
111 headers: Vec<String>,
112 callback_port: Option<u16>,
113 client_id: Option<String>,
114 client_secret: bool,
115}
116
117impl McpAddCommand {
118 #[must_use]
120 pub fn new(name: impl Into<String>, command_or_url: impl Into<String>) -> Self {
121 Self {
122 name: name.into(),
123 command_or_url: command_or_url.into(),
124 server_args: Vec::new(),
125 transport: None,
126 scope: None,
127 env: Vec::new(),
128 headers: Vec::new(),
129 callback_port: None,
130 client_id: None,
131 client_secret: false,
132 }
133 }
134
135 #[must_use]
137 pub fn transport(mut self, transport: Transport) -> Self {
138 self.transport = Some(transport);
139 self
140 }
141
142 #[must_use]
144 pub fn scope(mut self, scope: Scope) -> Self {
145 self.scope = Some(scope);
146 self
147 }
148
149 #[must_use]
151 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
152 self.env.push((key.into(), value.into()));
153 self
154 }
155
156 #[must_use]
158 pub fn header(mut self, header: impl Into<String>) -> Self {
159 self.headers.push(header.into());
160 self
161 }
162
163 #[must_use]
165 pub fn server_args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
166 self.server_args.extend(args.into_iter().map(Into::into));
167 self
168 }
169
170 #[must_use]
172 pub fn callback_port(mut self, port: u16) -> Self {
173 self.callback_port = Some(port);
174 self
175 }
176
177 #[must_use]
179 pub fn client_id(mut self, id: impl Into<String>) -> Self {
180 self.client_id = Some(id.into());
181 self
182 }
183
184 #[must_use]
186 pub fn client_secret(mut self) -> Self {
187 self.client_secret = true;
188 self
189 }
190}
191
192impl ClaudeCommand for McpAddCommand {
193 type Output = CommandOutput;
194
195 fn args(&self) -> Vec<String> {
196 let mut args = vec!["mcp".to_string(), "add".to_string()];
197
198 if let Some(transport) = self.transport {
199 args.push("--transport".to_string());
200 args.push(transport.to_string());
201 }
202
203 if let Some(ref scope) = self.scope {
204 args.push("--scope".to_string());
205 args.push(scope.as_arg().to_string());
206 }
207
208 for (key, value) in &self.env {
209 args.push("-e".to_string());
210 args.push(format!("{key}={value}"));
211 }
212
213 for header in &self.headers {
214 args.push("-H".to_string());
215 args.push(header.clone());
216 }
217
218 if let Some(port) = self.callback_port {
219 args.push("--callback-port".to_string());
220 args.push(port.to_string());
221 }
222
223 if let Some(ref id) = self.client_id {
224 args.push("--client-id".to_string());
225 args.push(id.clone());
226 }
227
228 if self.client_secret {
229 args.push("--client-secret".to_string());
230 }
231
232 args.push(self.name.clone());
233 args.push(self.command_or_url.clone());
234
235 if !self.server_args.is_empty() {
236 args.push("--".to_string());
237 args.extend(self.server_args.clone());
238 }
239
240 args
241 }
242
243 #[cfg(feature = "async")]
244 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
245 exec::run_claude(claude, self.args()).await
246 }
247}
248
249#[derive(Debug, Clone)]
251pub struct McpAddJsonCommand {
252 name: String,
253 json: String,
254 scope: Option<Scope>,
255}
256
257impl McpAddJsonCommand {
258 #[must_use]
260 pub fn new(name: impl Into<String>, json: impl Into<String>) -> Self {
261 Self {
262 name: name.into(),
263 json: json.into(),
264 scope: None,
265 }
266 }
267
268 #[must_use]
270 pub fn scope(mut self, scope: Scope) -> Self {
271 self.scope = Some(scope);
272 self
273 }
274}
275
276impl ClaudeCommand for McpAddJsonCommand {
277 type Output = CommandOutput;
278
279 fn args(&self) -> Vec<String> {
280 let mut args = vec!["mcp".to_string(), "add-json".to_string()];
281
282 if let Some(ref scope) = self.scope {
283 args.push("--scope".to_string());
284 args.push(scope.as_arg().to_string());
285 }
286
287 args.push(self.name.clone());
288 args.push(self.json.clone());
289
290 args
291 }
292
293 #[cfg(feature = "async")]
294 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
295 exec::run_claude(claude, self.args()).await
296 }
297}
298
299#[derive(Debug, Clone)]
301pub struct McpRemoveCommand {
302 name: String,
303 scope: Option<Scope>,
304}
305
306impl McpRemoveCommand {
307 #[must_use]
309 pub fn new(name: impl Into<String>) -> Self {
310 Self {
311 name: name.into(),
312 scope: None,
313 }
314 }
315
316 #[must_use]
318 pub fn scope(mut self, scope: Scope) -> Self {
319 self.scope = Some(scope);
320 self
321 }
322}
323
324impl ClaudeCommand for McpRemoveCommand {
325 type Output = CommandOutput;
326
327 fn args(&self) -> Vec<String> {
328 let mut args = vec!["mcp".to_string(), "remove".to_string()];
329
330 if let Some(ref scope) = self.scope {
331 args.push("--scope".to_string());
332 args.push(scope.as_arg().to_string());
333 }
334
335 args.push(self.name.clone());
336
337 args
338 }
339
340 #[cfg(feature = "async")]
341 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
342 exec::run_claude(claude, self.args()).await
343 }
344}
345
346#[derive(Debug, Clone, Default)]
348pub struct McpAddFromDesktopCommand {
349 scope: Option<Scope>,
350}
351
352impl McpAddFromDesktopCommand {
353 #[must_use]
355 pub fn new() -> Self {
356 Self::default()
357 }
358
359 #[must_use]
361 pub fn scope(mut self, scope: Scope) -> Self {
362 self.scope = Some(scope);
363 self
364 }
365}
366
367impl ClaudeCommand for McpAddFromDesktopCommand {
368 type Output = CommandOutput;
369
370 fn args(&self) -> Vec<String> {
371 let mut args = vec!["mcp".to_string(), "add-from-claude-desktop".to_string()];
372 if let Some(ref scope) = self.scope {
373 args.push("--scope".to_string());
374 args.push(scope.as_arg().to_string());
375 }
376 args
377 }
378
379 #[cfg(feature = "async")]
380 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
381 exec::run_claude(claude, self.args()).await
382 }
383}
384
385#[derive(Debug, Clone, Default)]
402pub struct McpServeCommand {
403 debug: bool,
404 verbose: bool,
405}
406
407impl McpServeCommand {
408 #[must_use]
410 pub fn new() -> Self {
411 Self::default()
412 }
413
414 #[must_use]
416 pub fn debug(mut self) -> Self {
417 self.debug = true;
418 self
419 }
420
421 #[must_use]
423 pub fn verbose(mut self) -> Self {
424 self.verbose = true;
425 self
426 }
427}
428
429impl ClaudeCommand for McpServeCommand {
430 type Output = CommandOutput;
431
432 fn args(&self) -> Vec<String> {
433 let mut args = vec!["mcp".to_string(), "serve".to_string()];
434 if self.debug {
435 args.push("--debug".to_string());
436 }
437 if self.verbose {
438 args.push("--verbose".to_string());
439 }
440 args
441 }
442
443 #[cfg(feature = "async")]
444 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
445 exec::run_claude(claude, self.args()).await
446 }
447}
448
449#[derive(Debug, Clone, Default)]
451pub struct McpResetProjectChoicesCommand;
452
453impl McpResetProjectChoicesCommand {
454 #[must_use]
455 pub fn new() -> Self {
456 Self
457 }
458}
459
460impl ClaudeCommand for McpResetProjectChoicesCommand {
461 type Output = CommandOutput;
462
463 fn args(&self) -> Vec<String> {
464 vec!["mcp".to_string(), "reset-project-choices".to_string()]
465 }
466
467 #[cfg(feature = "async")]
468 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
469 exec::run_claude(claude, self.args()).await
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476
477 #[test]
478 fn test_mcp_list_args() {
479 let cmd = McpListCommand::new();
480 assert_eq!(cmd.args(), vec!["mcp", "list"]);
481 }
482
483 #[test]
484 fn test_mcp_get_args() {
485 let cmd = McpGetCommand::new("my-server");
486 assert_eq!(cmd.args(), vec!["mcp", "get", "my-server"]);
487 }
488
489 #[test]
490 fn test_mcp_add_http() {
491 let cmd = McpAddCommand::new("sentry", "https://mcp.sentry.dev/mcp")
492 .transport(Transport::Http)
493 .scope(Scope::User);
494
495 let args = cmd.args();
496 assert_eq!(
497 args,
498 vec![
499 "mcp",
500 "add",
501 "--transport",
502 "http",
503 "--scope",
504 "user",
505 "sentry",
506 "https://mcp.sentry.dev/mcp"
507 ]
508 );
509 }
510
511 #[test]
512 fn test_mcp_add_stdio_with_env() {
513 let cmd = McpAddCommand::new("my-server", "npx")
514 .env("API_KEY", "xxx")
515 .server_args(["my-mcp-server"]);
516
517 let args = cmd.args();
518 assert_eq!(
519 args,
520 vec![
521 "mcp",
522 "add",
523 "-e",
524 "API_KEY=xxx",
525 "my-server",
526 "npx",
527 "--",
528 "my-mcp-server"
529 ]
530 );
531 }
532
533 #[test]
534 fn test_mcp_add_oauth_flags() {
535 let cmd = McpAddCommand::new("my-server", "https://example.com/mcp")
536 .transport(Transport::Http)
537 .callback_port(8080)
538 .client_id("my-app-id")
539 .client_secret();
540
541 let args = cmd.args();
542 assert_eq!(
543 args,
544 vec![
545 "mcp",
546 "add",
547 "--transport",
548 "http",
549 "--callback-port",
550 "8080",
551 "--client-id",
552 "my-app-id",
553 "--client-secret",
554 "my-server",
555 "https://example.com/mcp"
556 ]
557 );
558 }
559
560 #[test]
561 fn test_mcp_remove_args() {
562 let cmd = McpRemoveCommand::new("old-server").scope(Scope::Project);
563 assert_eq!(
564 cmd.args(),
565 vec!["mcp", "remove", "--scope", "project", "old-server"]
566 );
567 }
568
569 #[test]
570 fn test_mcp_add_from_desktop() {
571 let cmd = McpAddFromDesktopCommand::new().scope(Scope::User);
572 assert_eq!(
573 cmd.args(),
574 vec!["mcp", "add-from-claude-desktop", "--scope", "user"]
575 );
576 }
577
578 #[test]
579 fn test_mcp_reset_project_choices() {
580 let cmd = McpResetProjectChoicesCommand::new();
581 assert_eq!(cmd.args(), vec!["mcp", "reset-project-choices"]);
582 }
583
584 #[test]
585 fn test_mcp_serve_default() {
586 let cmd = McpServeCommand::new();
587 assert_eq!(cmd.args(), vec!["mcp", "serve"]);
588 }
589
590 #[test]
591 fn test_mcp_serve_with_flags() {
592 let cmd = McpServeCommand::new().debug().verbose();
593 assert_eq!(cmd.args(), vec!["mcp", "serve", "--debug", "--verbose"]);
594 }
595}