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 client_secret: bool,
256}
257
258impl McpAddJsonCommand {
259 #[must_use]
261 pub fn new(name: impl Into<String>, json: impl Into<String>) -> Self {
262 Self {
263 name: name.into(),
264 json: json.into(),
265 scope: None,
266 client_secret: false,
267 }
268 }
269
270 #[must_use]
272 pub fn scope(mut self, scope: Scope) -> Self {
273 self.scope = Some(scope);
274 self
275 }
276
277 #[must_use]
282 pub fn client_secret(mut self) -> Self {
283 self.client_secret = true;
284 self
285 }
286}
287
288impl ClaudeCommand for McpAddJsonCommand {
289 type Output = CommandOutput;
290
291 fn args(&self) -> Vec<String> {
292 let mut args = vec!["mcp".to_string(), "add-json".to_string()];
293
294 if let Some(ref scope) = self.scope {
295 args.push("--scope".to_string());
296 args.push(scope.as_arg().to_string());
297 }
298
299 if self.client_secret {
300 args.push("--client-secret".to_string());
301 }
302
303 args.push(self.name.clone());
304 args.push(self.json.clone());
305
306 args
307 }
308
309 #[cfg(feature = "async")]
310 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
311 exec::run_claude(claude, self.args()).await
312 }
313}
314
315#[derive(Debug, Clone)]
317pub struct McpRemoveCommand {
318 name: String,
319 scope: Option<Scope>,
320}
321
322impl McpRemoveCommand {
323 #[must_use]
325 pub fn new(name: impl Into<String>) -> Self {
326 Self {
327 name: name.into(),
328 scope: None,
329 }
330 }
331
332 #[must_use]
334 pub fn scope(mut self, scope: Scope) -> Self {
335 self.scope = Some(scope);
336 self
337 }
338}
339
340impl ClaudeCommand for McpRemoveCommand {
341 type Output = CommandOutput;
342
343 fn args(&self) -> Vec<String> {
344 let mut args = vec!["mcp".to_string(), "remove".to_string()];
345
346 if let Some(ref scope) = self.scope {
347 args.push("--scope".to_string());
348 args.push(scope.as_arg().to_string());
349 }
350
351 args.push(self.name.clone());
352
353 args
354 }
355
356 #[cfg(feature = "async")]
357 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
358 exec::run_claude(claude, self.args()).await
359 }
360}
361
362#[derive(Debug, Clone, Default)]
364pub struct McpAddFromDesktopCommand {
365 scope: Option<Scope>,
366}
367
368impl McpAddFromDesktopCommand {
369 #[must_use]
371 pub fn new() -> Self {
372 Self::default()
373 }
374
375 #[must_use]
377 pub fn scope(mut self, scope: Scope) -> Self {
378 self.scope = Some(scope);
379 self
380 }
381}
382
383impl ClaudeCommand for McpAddFromDesktopCommand {
384 type Output = CommandOutput;
385
386 fn args(&self) -> Vec<String> {
387 let mut args = vec!["mcp".to_string(), "add-from-claude-desktop".to_string()];
388 if let Some(ref scope) = self.scope {
389 args.push("--scope".to_string());
390 args.push(scope.as_arg().to_string());
391 }
392 args
393 }
394
395 #[cfg(feature = "async")]
396 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
397 exec::run_claude(claude, self.args()).await
398 }
399}
400
401#[derive(Debug, Clone, Default)]
418pub struct McpServeCommand {
419 debug: bool,
420 verbose: bool,
421}
422
423impl McpServeCommand {
424 #[must_use]
426 pub fn new() -> Self {
427 Self::default()
428 }
429
430 #[must_use]
432 pub fn debug(mut self) -> Self {
433 self.debug = true;
434 self
435 }
436
437 #[must_use]
439 pub fn verbose(mut self) -> Self {
440 self.verbose = true;
441 self
442 }
443}
444
445impl ClaudeCommand for McpServeCommand {
446 type Output = CommandOutput;
447
448 fn args(&self) -> Vec<String> {
449 let mut args = vec!["mcp".to_string(), "serve".to_string()];
450 if self.debug {
451 args.push("--debug".to_string());
452 }
453 if self.verbose {
454 args.push("--verbose".to_string());
455 }
456 args
457 }
458
459 #[cfg(feature = "async")]
460 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
461 exec::run_claude(claude, self.args()).await
462 }
463}
464
465#[derive(Debug, Clone, Default)]
467pub struct McpResetProjectChoicesCommand;
468
469impl McpResetProjectChoicesCommand {
470 #[must_use]
471 pub fn new() -> Self {
472 Self
473 }
474}
475
476impl ClaudeCommand for McpResetProjectChoicesCommand {
477 type Output = CommandOutput;
478
479 fn args(&self) -> Vec<String> {
480 vec!["mcp".to_string(), "reset-project-choices".to_string()]
481 }
482
483 #[cfg(feature = "async")]
484 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
485 exec::run_claude(claude, self.args()).await
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492
493 #[test]
494 fn test_mcp_list_args() {
495 let cmd = McpListCommand::new();
496 assert_eq!(cmd.args(), vec!["mcp", "list"]);
497 }
498
499 #[test]
500 fn test_mcp_get_args() {
501 let cmd = McpGetCommand::new("my-server");
502 assert_eq!(cmd.args(), vec!["mcp", "get", "my-server"]);
503 }
504
505 #[test]
506 fn test_mcp_add_http() {
507 let cmd = McpAddCommand::new("sentry", "https://mcp.sentry.dev/mcp")
508 .transport(Transport::Http)
509 .scope(Scope::User);
510
511 let args = cmd.args();
512 assert_eq!(
513 args,
514 vec![
515 "mcp",
516 "add",
517 "--transport",
518 "http",
519 "--scope",
520 "user",
521 "sentry",
522 "https://mcp.sentry.dev/mcp"
523 ]
524 );
525 }
526
527 #[test]
528 fn test_mcp_add_stdio_with_env() {
529 let cmd = McpAddCommand::new("my-server", "npx")
530 .env("API_KEY", "xxx")
531 .server_args(["my-mcp-server"]);
532
533 let args = cmd.args();
534 assert_eq!(
535 args,
536 vec![
537 "mcp",
538 "add",
539 "-e",
540 "API_KEY=xxx",
541 "my-server",
542 "npx",
543 "--",
544 "my-mcp-server"
545 ]
546 );
547 }
548
549 #[test]
550 fn test_mcp_add_oauth_flags() {
551 let cmd = McpAddCommand::new("my-server", "https://example.com/mcp")
552 .transport(Transport::Http)
553 .callback_port(8080)
554 .client_id("my-app-id")
555 .client_secret();
556
557 let args = cmd.args();
558 assert_eq!(
559 args,
560 vec![
561 "mcp",
562 "add",
563 "--transport",
564 "http",
565 "--callback-port",
566 "8080",
567 "--client-id",
568 "my-app-id",
569 "--client-secret",
570 "my-server",
571 "https://example.com/mcp"
572 ]
573 );
574 }
575
576 #[test]
577 fn test_mcp_add_json_basic() {
578 let cmd = McpAddJsonCommand::new("srv", r#"{"command":"npx"}"#).scope(Scope::User);
579 assert_eq!(
580 cmd.args(),
581 vec![
582 "mcp",
583 "add-json",
584 "--scope",
585 "user",
586 "srv",
587 r#"{"command":"npx"}"#
588 ]
589 );
590 }
591
592 #[test]
593 fn test_mcp_add_json_client_secret() {
594 let cmd = McpAddJsonCommand::new("srv", "{}").client_secret();
596 assert_eq!(
597 cmd.args(),
598 vec!["mcp", "add-json", "--client-secret", "srv", "{}"]
599 );
600 }
601
602 #[test]
603 fn test_mcp_add_json_no_client_secret_by_default() {
604 let cmd = McpAddJsonCommand::new("srv", "{}");
605 assert!(!cmd.args().contains(&"--client-secret".to_string()));
606 }
607
608 #[test]
609 fn test_mcp_remove_args() {
610 let cmd = McpRemoveCommand::new("old-server").scope(Scope::Project);
611 assert_eq!(
612 cmd.args(),
613 vec!["mcp", "remove", "--scope", "project", "old-server"]
614 );
615 }
616
617 #[test]
618 fn test_mcp_add_from_desktop() {
619 let cmd = McpAddFromDesktopCommand::new().scope(Scope::User);
620 assert_eq!(
621 cmd.args(),
622 vec!["mcp", "add-from-claude-desktop", "--scope", "user"]
623 );
624 }
625
626 #[test]
627 fn test_mcp_reset_project_choices() {
628 let cmd = McpResetProjectChoicesCommand::new();
629 assert_eq!(cmd.args(), vec!["mcp", "reset-project-choices"]);
630 }
631
632 #[test]
633 fn test_mcp_serve_default() {
634 let cmd = McpServeCommand::new();
635 assert_eq!(cmd.args(), vec!["mcp", "serve"]);
636 }
637
638 #[test]
639 fn test_mcp_serve_with_flags() {
640 let cmd = McpServeCommand::new().debug().verbose();
641 assert_eq!(cmd.args(), vec!["mcp", "serve", "--debug", "--verbose"]);
642 }
643}