use crate::Claude;
use crate::command::ClaudeCommand;
use crate::error::Result;
use crate::exec::{self, CommandOutput};
use crate::types::{Scope, Transport};
#[derive(Debug, Clone, Default)]
pub struct McpListCommand;
impl McpListCommand {
#[must_use]
pub fn new() -> Self {
Self
}
}
impl ClaudeCommand for McpListCommand {
type Output = CommandOutput;
fn args(&self) -> Vec<String> {
vec!["mcp".to_string(), "list".to_string()]
}
async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
exec::run_claude(claude, self.args()).await
}
}
#[derive(Debug, Clone)]
pub struct McpGetCommand {
name: String,
}
impl McpGetCommand {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self { name: name.into() }
}
}
impl ClaudeCommand for McpGetCommand {
type Output = CommandOutput;
fn args(&self) -> Vec<String> {
vec!["mcp".to_string(), "get".to_string(), self.name.clone()]
}
async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
exec::run_claude(claude, self.args()).await
}
}
#[derive(Debug, Clone)]
pub struct McpAddCommand {
name: String,
command_or_url: String,
server_args: Vec<String>,
transport: Option<Transport>,
scope: Option<Scope>,
env: Vec<(String, String)>,
headers: Vec<String>,
callback_port: Option<u16>,
client_id: Option<String>,
client_secret: bool,
}
impl McpAddCommand {
#[must_use]
pub fn new(name: impl Into<String>, command_or_url: impl Into<String>) -> Self {
Self {
name: name.into(),
command_or_url: command_or_url.into(),
server_args: Vec::new(),
transport: None,
scope: None,
env: Vec::new(),
headers: Vec::new(),
callback_port: None,
client_id: None,
client_secret: false,
}
}
#[must_use]
pub fn transport(mut self, transport: Transport) -> Self {
self.transport = Some(transport);
self
}
#[must_use]
pub fn scope(mut self, scope: Scope) -> Self {
self.scope = Some(scope);
self
}
#[must_use]
pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env.push((key.into(), value.into()));
self
}
#[must_use]
pub fn header(mut self, header: impl Into<String>) -> Self {
self.headers.push(header.into());
self
}
#[must_use]
pub fn server_args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.server_args.extend(args.into_iter().map(Into::into));
self
}
#[must_use]
pub fn callback_port(mut self, port: u16) -> Self {
self.callback_port = Some(port);
self
}
#[must_use]
pub fn client_id(mut self, id: impl Into<String>) -> Self {
self.client_id = Some(id.into());
self
}
#[must_use]
pub fn client_secret(mut self) -> Self {
self.client_secret = true;
self
}
}
impl ClaudeCommand for McpAddCommand {
type Output = CommandOutput;
fn args(&self) -> Vec<String> {
let mut args = vec!["mcp".to_string(), "add".to_string()];
if let Some(transport) = self.transport {
args.push("--transport".to_string());
args.push(transport.to_string());
}
if let Some(ref scope) = self.scope {
args.push("--scope".to_string());
args.push(scope.as_arg().to_string());
}
for (key, value) in &self.env {
args.push("-e".to_string());
args.push(format!("{key}={value}"));
}
for header in &self.headers {
args.push("-H".to_string());
args.push(header.clone());
}
if let Some(port) = self.callback_port {
args.push("--callback-port".to_string());
args.push(port.to_string());
}
if let Some(ref id) = self.client_id {
args.push("--client-id".to_string());
args.push(id.clone());
}
if self.client_secret {
args.push("--client-secret".to_string());
}
args.push(self.name.clone());
args.push(self.command_or_url.clone());
if !self.server_args.is_empty() {
args.push("--".to_string());
args.extend(self.server_args.clone());
}
args
}
async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
exec::run_claude(claude, self.args()).await
}
}
#[derive(Debug, Clone)]
pub struct McpAddJsonCommand {
name: String,
json: String,
scope: Option<Scope>,
}
impl McpAddJsonCommand {
#[must_use]
pub fn new(name: impl Into<String>, json: impl Into<String>) -> Self {
Self {
name: name.into(),
json: json.into(),
scope: None,
}
}
#[must_use]
pub fn scope(mut self, scope: Scope) -> Self {
self.scope = Some(scope);
self
}
}
impl ClaudeCommand for McpAddJsonCommand {
type Output = CommandOutput;
fn args(&self) -> Vec<String> {
let mut args = vec!["mcp".to_string(), "add-json".to_string()];
if let Some(ref scope) = self.scope {
args.push("--scope".to_string());
args.push(scope.as_arg().to_string());
}
args.push(self.name.clone());
args.push(self.json.clone());
args
}
async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
exec::run_claude(claude, self.args()).await
}
}
#[derive(Debug, Clone)]
pub struct McpRemoveCommand {
name: String,
scope: Option<Scope>,
}
impl McpRemoveCommand {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
scope: None,
}
}
#[must_use]
pub fn scope(mut self, scope: Scope) -> Self {
self.scope = Some(scope);
self
}
}
impl ClaudeCommand for McpRemoveCommand {
type Output = CommandOutput;
fn args(&self) -> Vec<String> {
let mut args = vec!["mcp".to_string(), "remove".to_string()];
if let Some(ref scope) = self.scope {
args.push("--scope".to_string());
args.push(scope.as_arg().to_string());
}
args.push(self.name.clone());
args
}
async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
exec::run_claude(claude, self.args()).await
}
}
#[derive(Debug, Clone, Default)]
pub struct McpAddFromDesktopCommand {
scope: Option<Scope>,
}
impl McpAddFromDesktopCommand {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn scope(mut self, scope: Scope) -> Self {
self.scope = Some(scope);
self
}
}
impl ClaudeCommand for McpAddFromDesktopCommand {
type Output = CommandOutput;
fn args(&self) -> Vec<String> {
let mut args = vec!["mcp".to_string(), "add-from-claude-desktop".to_string()];
if let Some(ref scope) = self.scope {
args.push("--scope".to_string());
args.push(scope.as_arg().to_string());
}
args
}
async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
exec::run_claude(claude, self.args()).await
}
}
#[derive(Debug, Clone, Default)]
pub struct McpServeCommand {
debug: bool,
verbose: bool,
}
impl McpServeCommand {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn debug(mut self) -> Self {
self.debug = true;
self
}
#[must_use]
pub fn verbose(mut self) -> Self {
self.verbose = true;
self
}
}
impl ClaudeCommand for McpServeCommand {
type Output = CommandOutput;
fn args(&self) -> Vec<String> {
let mut args = vec!["mcp".to_string(), "serve".to_string()];
if self.debug {
args.push("--debug".to_string());
}
if self.verbose {
args.push("--verbose".to_string());
}
args
}
async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
exec::run_claude(claude, self.args()).await
}
}
#[derive(Debug, Clone, Default)]
pub struct McpResetProjectChoicesCommand;
impl McpResetProjectChoicesCommand {
#[must_use]
pub fn new() -> Self {
Self
}
}
impl ClaudeCommand for McpResetProjectChoicesCommand {
type Output = CommandOutput;
fn args(&self) -> Vec<String> {
vec!["mcp".to_string(), "reset-project-choices".to_string()]
}
async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
exec::run_claude(claude, self.args()).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mcp_list_args() {
let cmd = McpListCommand::new();
assert_eq!(cmd.args(), vec!["mcp", "list"]);
}
#[test]
fn test_mcp_get_args() {
let cmd = McpGetCommand::new("my-server");
assert_eq!(cmd.args(), vec!["mcp", "get", "my-server"]);
}
#[test]
fn test_mcp_add_http() {
let cmd = McpAddCommand::new("sentry", "https://mcp.sentry.dev/mcp")
.transport(Transport::Http)
.scope(Scope::User);
let args = cmd.args();
assert_eq!(
args,
vec![
"mcp",
"add",
"--transport",
"http",
"--scope",
"user",
"sentry",
"https://mcp.sentry.dev/mcp"
]
);
}
#[test]
fn test_mcp_add_stdio_with_env() {
let cmd = McpAddCommand::new("my-server", "npx")
.env("API_KEY", "xxx")
.server_args(["my-mcp-server"]);
let args = cmd.args();
assert_eq!(
args,
vec![
"mcp",
"add",
"-e",
"API_KEY=xxx",
"my-server",
"npx",
"--",
"my-mcp-server"
]
);
}
#[test]
fn test_mcp_add_oauth_flags() {
let cmd = McpAddCommand::new("my-server", "https://example.com/mcp")
.transport(Transport::Http)
.callback_port(8080)
.client_id("my-app-id")
.client_secret();
let args = cmd.args();
assert_eq!(
args,
vec![
"mcp",
"add",
"--transport",
"http",
"--callback-port",
"8080",
"--client-id",
"my-app-id",
"--client-secret",
"my-server",
"https://example.com/mcp"
]
);
}
#[test]
fn test_mcp_remove_args() {
let cmd = McpRemoveCommand::new("old-server").scope(Scope::Project);
assert_eq!(
cmd.args(),
vec!["mcp", "remove", "--scope", "project", "old-server"]
);
}
#[test]
fn test_mcp_add_from_desktop() {
let cmd = McpAddFromDesktopCommand::new().scope(Scope::User);
assert_eq!(
cmd.args(),
vec!["mcp", "add-from-claude-desktop", "--scope", "user"]
);
}
#[test]
fn test_mcp_reset_project_choices() {
let cmd = McpResetProjectChoicesCommand::new();
assert_eq!(cmd.args(), vec!["mcp", "reset-project-choices"]);
}
#[test]
fn test_mcp_serve_default() {
let cmd = McpServeCommand::new();
assert_eq!(cmd.args(), vec!["mcp", "serve"]);
}
#[test]
fn test_mcp_serve_with_flags() {
let cmd = McpServeCommand::new().debug().verbose();
assert_eq!(cmd.args(), vec!["mcp", "serve", "--debug", "--verbose"]);
}
}