From d9f7764f8a42f96e568ae9aa067ae223bc45c58d Mon Sep 17 00:00:00 2001 From: OnlyYu1996 <1158673577@qq.com> Date: Sun, 17 May 2026 21:49:00 +0800 Subject: [PATCH] fix(cli): include path in mcp config parse errors --- src/cortex-cli/src/agent_cmd/tests.rs | 5 ++--- src/cortex-cli/src/mcp_cmd/config.rs | 22 +++++++++++++++++++++- src/cortex-cli/src/mcp_cmd/handlers.rs | 17 ++++++----------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/cortex-cli/src/agent_cmd/tests.rs b/src/cortex-cli/src/agent_cmd/tests.rs index e2ff07f9f..18f7ba753 100644 --- a/src/cortex-cli/src/agent_cmd/tests.rs +++ b/src/cortex-cli/src/agent_cmd/tests.rs @@ -3,10 +3,9 @@ #[cfg(test)] mod tests { use crate::agent_cmd::cli::{CopyArgs, ExportArgs}; - use crate::agent_cmd::loader::{ - load_builtin_agents, parse_frontmatter, read_file_with_encoding, - }; + use crate::agent_cmd::loader::{load_builtin_agents, parse_frontmatter}; use crate::agent_cmd::types::AgentMode; + use crate::utils::file::read_file_with_encoding; #[test] fn test_read_file_with_utf8() { diff --git a/src/cortex-cli/src/mcp_cmd/config.rs b/src/cortex-cli/src/mcp_cmd/config.rs index 472ce2edc..531c1d881 100644 --- a/src/cortex-cli/src/mcp_cmd/config.rs +++ b/src/cortex-cli/src/mcp_cmd/config.rs @@ -5,6 +5,13 @@ use anyhow::{Context, Result}; use cortex_engine::config::find_cortex_home; +use std::path::Path; + +/// Parse MCP config TOML with the file path included in parse errors. +pub(crate) fn parse_config_content(content: &str, config_path: &Path) -> Result { + toml::from_str(content) + .with_context(|| format!("failed to parse config: {}", config_path.display())) +} /// Load the config file as a toml::Value. pub(crate) fn load_config() -> Result> { @@ -19,7 +26,7 @@ pub(crate) fn load_config() -> Result> { let content = std::fs::read_to_string(&config_path) .with_context(|| format!("failed to read config: {}", config_path.display()))?; - let config: toml::Value = toml::from_str(&content).with_context(|| "failed to parse config")?; + let config = parse_config_content(&content, &config_path)?; Ok(Some(config)) } @@ -87,4 +94,17 @@ mod tests { assert!(result.is_ok()); assert!(result.unwrap().is_none()); } + + #[test] + fn test_parse_config_content_error_includes_path() { + let config_path = std::path::PathBuf::from("custom-cortex-home/config.toml"); + let err = parse_config_content("invalid = [unclosed", &config_path) + .expect_err("invalid TOML should fail"); + let message = err.to_string(); + + assert!( + message.contains("failed to parse config: custom-cortex-home/config.toml"), + "error should include config path, got: {message}" + ); + } } diff --git a/src/cortex-cli/src/mcp_cmd/handlers.rs b/src/cortex-cli/src/mcp_cmd/handlers.rs index a2d9189cf..fb41a7073 100644 --- a/src/cortex-cli/src/mcp_cmd/handlers.rs +++ b/src/cortex-cli/src/mcp_cmd/handlers.rs @@ -8,7 +8,7 @@ use cortex_engine::config::find_cortex_home; use std::io::{self, BufRead, Write}; use super::auth::{get_auth_status_for_display, remove_auth_silent}; -use super::config::{get_mcp_server, get_mcp_servers}; +use super::config::{get_mcp_server, get_mcp_servers, parse_config_content}; use super::types::{ AddArgs, AddMcpSseArgs, AddMcpStreamableHttpArgs, AddMcpTransportArgs, DisableArgs, EnableArgs, GetArgs, ListArgs, RemoveArgs, RenameArgs, @@ -301,8 +301,7 @@ pub(crate) async fn run_add(args: AddArgs) -> Result<()> { if config_path.exists() { let content = std::fs::read_to_string(&config_path) .with_context(|| format!("failed to read config: {}", config_path.display()))?; - let mut config: toml::Value = - toml::from_str(&content).with_context(|| "failed to parse config")?; + let mut config = parse_config_content(&content, &config_path)?; if let Some(mcp_servers) = config.get_mut("mcp_servers").and_then(|v| v.as_table_mut()) { @@ -576,8 +575,7 @@ pub(crate) async fn run_remove(args: RemoveArgs) -> Result<()> { .with_context(|| format!("failed to read config: {}", config_path.display()))?; // Parse the TOML and remove the server section - let mut config: toml::Value = - toml::from_str(&content).with_context(|| "failed to parse config")?; + let mut config = parse_config_content(&content, &config_path)?; if let Some(mcp_servers) = config.get_mut("mcp_servers").and_then(|v| v.as_table_mut()) { mcp_servers.remove(&name); @@ -622,8 +620,7 @@ pub(crate) async fn run_enable(args: EnableArgs) -> Result<()> { let content = std::fs::read_to_string(&config_path) .with_context(|| format!("failed to read config: {}", config_path.display()))?; - let mut config: toml::Value = - toml::from_str(&content).with_context(|| "failed to parse config")?; + let mut config = parse_config_content(&content, &config_path)?; if let Some(mcp_servers) = config.get_mut("mcp_servers").and_then(|v| v.as_table_mut()) && let Some(server) = mcp_servers.get_mut(&args.name) @@ -674,8 +671,7 @@ pub(crate) async fn run_disable(args: DisableArgs) -> Result<()> { let content = std::fs::read_to_string(&config_path) .with_context(|| format!("failed to read config: {}", config_path.display()))?; - let mut config: toml::Value = - toml::from_str(&content).with_context(|| "failed to parse config")?; + let mut config = parse_config_content(&content, &config_path)?; if let Some(mcp_servers) = config.get_mut("mcp_servers").and_then(|v| v.as_table_mut()) && let Some(server) = mcp_servers.get_mut(&args.name) @@ -740,8 +736,7 @@ pub(crate) async fn run_rename(args: RenameArgs) -> Result<()> { let content = std::fs::read_to_string(&config_path) .with_context(|| format!("failed to read config: {}", config_path.display()))?; - let mut config: toml::Value = - toml::from_str(&content).with_context(|| "failed to parse config")?; + let mut config = parse_config_content(&content, &config_path)?; if let Some(mcp_servers) = config.get_mut("mcp_servers").and_then(|v| v.as_table_mut()) && let Some(server_config) = mcp_servers.remove(&args.old_name)