Skip to content

Commit a14e985

Browse files
committed
add import&status&remove
1 parent 91dbeec commit a14e985

File tree

2 files changed

+82
-8
lines changed

2 files changed

+82
-8
lines changed

crates/q_chat/src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ pub struct McpAdd {
6767
pub profile: Option<String>,
6868
/// Environment variables to use when launching the server
6969
#[arg(long, value_parser = parse_env_vars)]
70-
pub env: Option<HashMap<String, String>>,
70+
pub env: Vec<HashMap<String, String>>,
7171
/// Server launch timeout, in milliseconds
7272
#[arg(long)]
7373
pub timeout: Option<u64>,

crates/q_chat/src/mcp.rs

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use eyre::{
77
bail,
88
};
99
use fig_os_shim::Context;
10+
use futures::future::ok;
1011
use tracing::warn;
1112

1213
use crate::cli::{
@@ -44,9 +45,18 @@ pub async fn execute_mcp(args: Mcp) -> Result<ExitCode> {
4445
pub async fn add_mcp_server(ctx: &Context, args: McpAdd) -> Result<()> {
4546
let config_path = resolve_scope_profile(ctx, args.scope, args.profile.as_ref())?;
4647
let mut config: McpServerConfig = serde_json::from_str(&ctx.fs().read_to_string(&config_path).await?)?;
48+
let merged_env = if args.env.is_empty() {
49+
None
50+
} else {
51+
let mut map = HashMap::new();
52+
for m in args.env {
53+
map.extend(m);
54+
}
55+
Some(map)
56+
};
4757
let val: CustomToolConfig = serde_json::from_value(serde_json::json!({
4858
"command": args.command,
49-
"env": args.env,
59+
"env": merged_env,
5060
"timeout": args.timeout,
5161
}))?;
5262
config.mcp_servers.insert(args.name, val);
@@ -58,8 +68,17 @@ pub async fn add_mcp_server(ctx: &Context, args: McpAdd) -> Result<()> {
5868
pub async fn remove_mcp_server(ctx: &Context, args: McpRemove) -> Result<()> {
5969
let config_path = resolve_scope_profile(ctx, args.scope, args.profile.as_ref())?;
6070
let mut config = McpServerConfig::load_from_file(ctx, &config_path).await?;
71+
72+
let scope = args.scope.unwrap_or(Scope::Workspace);
6173
match config.mcp_servers.remove(&args.name) {
62-
Some(_) => (),
74+
Some(_) => {
75+
config.save_to_file(ctx, &config_path).await?;
76+
println!(
77+
"✓ Removed MCP server '{}' from {}",
78+
args.name,
79+
scope_display(&scope, &args.profile)
80+
);
81+
},
6382
None => {
6483
warn!(?args, "No MCP server found");
6584
},
@@ -135,19 +154,74 @@ async fn get_mcp_server_configs(
135154

136155
fn scope_display(scope: &Scope, profile: &Option<String>) -> String {
137156
match scope {
138-
Scope::Workspace => "\n📄 workspace".into(),
139-
Scope::Global => "\n🌍 global".into(),
140-
Scope::Profile => format!("\n👤 profile({})", profile.as_deref().unwrap_or("default")),
157+
Scope::Workspace => "📄 workspace".into(),
158+
Scope::Global => "🌍 global".into(),
159+
Scope::Profile => format!("👤 profile({})", profile.as_deref().unwrap_or("default")),
141160
}
142161
}
143162

144163
pub async fn import_mcp_server(ctx: &Context, args: McpImport) -> Result<()> {
145164
let config_path = resolve_scope_profile(ctx, args.scope, args.profile.as_ref())?;
146-
todo!()
165+
let mut dst_cfg = if ctx.fs().exists(&config_path) {
166+
McpServerConfig::load_from_file(ctx, &config_path).await?
167+
} else {
168+
McpServerConfig::default()
169+
};
170+
let expanded = shellexpand::tilde(&args.file);
171+
let mut src_path = std::path::PathBuf::from(expanded.as_ref());
172+
if src_path.is_relative() {
173+
src_path = ctx.env().current_dir()?.join(src_path);
174+
}
175+
176+
let src_content = ctx
177+
.fs()
178+
.read_to_string(&src_path)
179+
.await
180+
.map_err(|e| eyre::eyre!("Failed to read source file '{}': {e}", src_path.display()))?;
181+
let src_cfg: McpServerConfig = serde_json::from_str(&src_content)
182+
.map_err(|e| eyre::eyre!("Invalid MCP JSON in '{}': {e}", src_path.display()))?;
183+
184+
let before = dst_cfg.mcp_servers.len();
185+
for (name, cfg) in src_cfg.mcp_servers {
186+
if dst_cfg.mcp_servers.insert(name.clone(), cfg).is_some() {
187+
warn!(server = %name, "Overwriting existing MCP server configuration");
188+
}
189+
}
190+
let added = dst_cfg.mcp_servers.len() - before;
191+
192+
dst_cfg.save_to_file(ctx, &config_path).await?;
193+
194+
let scope = args.scope.unwrap_or(Scope::Workspace);
195+
println!(
196+
"✓ Imported {added} MCP server(s) into {:?} scope",
197+
scope_display(&scope, &args.profile)
198+
);
199+
Ok(())
147200
}
148201

149202
pub async fn get_mcp_server_status(ctx: &Context, name: String) -> Result<()> {
150-
todo!()
203+
let configs = get_mcp_server_configs(ctx, None, None).await?;
204+
for (_, _, _, cfg_opt) in configs {
205+
if let Some(cfg) = cfg_opt {
206+
if let Some(tool_cfg) = cfg.mcp_servers.get(&name) {
207+
println!("MCP Server: {name}");
208+
println!("Command : {}", tool_cfg.command);
209+
println!("Timeout : {} ms", tool_cfg.timeout);
210+
println!(
211+
"Env Vars : {}",
212+
tool_cfg
213+
.env
214+
.as_ref()
215+
.map(|e| e.keys().cloned().collect::<Vec<_>>().join(", "))
216+
.unwrap_or_else(|| "(none)".into())
217+
);
218+
// todo yifan how can I know the server status
219+
println!("Status : ");
220+
return Ok(());
221+
}
222+
}
223+
}
224+
bail!("No MCP server named '{name}' found\n")
151225
}
152226

153227
fn resolve_scope_profile(ctx: &Context, scope: Option<Scope>, profile: Option<&impl AsRef<str>>) -> Result<PathBuf> {

0 commit comments

Comments
 (0)