Skip to content

Commit 73e5026

Browse files
committed
add force flag in add & import
1 parent 1a33a20 commit 73e5026

File tree

3 files changed

+58
-13
lines changed

3 files changed

+58
-13
lines changed

crates/q_chat/src/cli.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ pub struct McpAdd {
7171
/// Server launch timeout, in milliseconds
7272
#[arg(long)]
7373
pub timeout: Option<u64>,
74+
/// Overwrite an existing server with the same name
75+
#[arg(long, default_value_t = false)]
76+
pub force: bool,
7477
}
7578

7679
#[derive(Debug, Clone, PartialEq, Eq, Args)]
@@ -99,6 +102,9 @@ pub struct McpImport {
99102
pub scope: Option<Scope>,
100103
#[arg(long)]
101104
pub profile: Option<String>,
105+
/// Overwrite an existing server with the same name
106+
#[arg(long, default_value_t = false)]
107+
pub force: bool,
102108
}
103109

104110
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]

crates/q_chat/src/mcp.rs

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ use crate::tool_manager::{
2323
profile_mcp_config_path,
2424
workspace_mcp_config_path,
2525
};
26-
use crate::tools::custom_tool::CustomToolConfig;
26+
use crate::tools::custom_tool::{
27+
CustomToolConfig,
28+
default_timeout,
29+
};
2730

2831
pub async fn execute_mcp(args: Mcp) -> Result<ExitCode> {
2932
let ctx = Context::new();
@@ -44,14 +47,33 @@ pub async fn execute_mcp(args: Mcp) -> Result<ExitCode> {
4447
pub async fn add_mcp_server(ctx: &Context, args: McpAdd) -> Result<()> {
4548
let scope = args.scope.unwrap_or(Scope::Workspace);
4649
let config_path = resolve_scope_profile(ctx, args.scope, args.profile.as_ref())?;
50+
51+
if !ctx.fs().exists(&config_path) && scope != Scope::Profile {
52+
if let Some(parent) = config_path.parent() {
53+
ctx.fs().create_dir_all(parent).await?;
54+
}
55+
McpServerConfig::default().save_to_file(ctx, &config_path).await?;
56+
println!("📁 Created MCP config in'{}'", config_path.display());
57+
}
58+
4759
let mut config: McpServerConfig = serde_json::from_str(&ctx.fs().read_to_string(&config_path).await?)?;
48-
let merged_env = args.env.into_iter().flatten().collect::<HashMap<_, _>>();
4960

61+
if config.mcp_servers.contains_key(&args.name) && !args.force {
62+
bail!(
63+
"MCP server '{}' already exists in {} (scope {}). Use --force to overwrite.",
64+
args.name,
65+
config_path.display(),
66+
scope
67+
);
68+
}
69+
70+
let merged_env = args.env.into_iter().flatten().collect::<HashMap<_, _>>();
5071
let tool: CustomToolConfig = serde_json::from_value(serde_json::json!({
5172
"command": args.command,
5273
"env": merged_env,
53-
"timeout": args.timeout,
74+
"timeout": args.timeout.unwrap_or(default_timeout()),
5475
}))?;
76+
5577
config.mcp_servers.insert(args.name.clone(), tool);
5678
config.save_to_file(ctx, &config_path).await?;
5779

@@ -68,7 +90,7 @@ pub async fn remove_mcp_server(ctx: &Context, args: McpRemove) -> Result<()> {
6890
let config_path = resolve_scope_profile(ctx, args.scope, args.profile.as_ref())?;
6991

7092
if !ctx.fs().exists(&config_path) {
71-
println!("No MCP configuration at {}", config_path.display());
93+
println!("\n No MCP server configurations found.\n");
7294
return Ok(());
7395
}
7496

@@ -82,7 +104,11 @@ pub async fn remove_mcp_server(ctx: &Context, args: McpRemove) -> Result<()> {
82104
scope_display(&scope, &args.profile)
83105
);
84106
},
85-
None => println!("No MCP server named '{}' found in {}", args.name, scope_display(&scope, &args.profile)),
107+
None => println!(
108+
"No MCP server named '{}' found in {}",
109+
args.name,
110+
scope_display(&scope, &args.profile)
111+
),
86112
}
87113
Ok(())
88114
}
@@ -115,20 +141,33 @@ pub async fn list_mcp_server(ctx: &Context, args: McpList) -> Result<()> {
115141
pub async fn import_mcp_server(ctx: &Context, args: McpImport) -> Result<()> {
116142
let scope: Scope = args.scope.unwrap_or(Scope::Workspace);
117143
let config_path = resolve_scope_profile(ctx, args.scope, args.profile.as_ref())?;
118-
let mut dst_cfg: McpServerConfig = if ctx.fs().exists(&config_path) {
119-
McpServerConfig::load_from_file(ctx, &config_path).await?
120-
} else {
121-
McpServerConfig::default()
122-
};
144+
145+
if !ctx.fs().exists(&config_path) && scope != Scope::Profile {
146+
if let Some(parent) = config_path.parent() {
147+
ctx.fs().create_dir_all(parent).await?;
148+
}
149+
McpServerConfig::default().save_to_file(ctx, &config_path).await?;
150+
println!("📁 Created MCP config in'{}'", config_path.display());
151+
}
152+
123153
let src_path = expand_path(ctx, &args.file)?;
124154
let src_cfg: McpServerConfig = serde_json::from_str(&ctx.fs().read_to_string(&src_path).await?)?;
155+
let mut dst_cfg: McpServerConfig = McpServerConfig::load_from_file(ctx, &config_path).await?;
125156

126157
let before = dst_cfg.mcp_servers.len();
127158
for (name, cfg) in src_cfg.mcp_servers {
128-
if dst_cfg.mcp_servers.insert(name.clone(), cfg).is_some() {
129-
warn!(%name, "Overwriting existing MCP server configuration");
159+
let exists = dst_cfg.mcp_servers.contains_key(&name);
160+
if exists && !args.force {
161+
bail!(
162+
"MCP server '{}' already exists in {} (scope {}). Use --force to overwrite.",
163+
name,
164+
config_path.display(),
165+
scope
166+
);
130167
}
168+
dst_cfg.mcp_servers.insert(name.clone(), cfg);
131169
}
170+
132171
let added = dst_cfg.mcp_servers.len() - before;
133172
dst_cfg.save_to_file(ctx, &config_path).await?;
134173

crates/q_chat/src/tools/custom_tool.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub struct CustomToolConfig {
4646
pub timeout: u64,
4747
}
4848

49-
fn default_timeout() -> u64 {
49+
pub fn default_timeout() -> u64 {
5050
120 * 1000
5151
}
5252

0 commit comments

Comments
 (0)