Hmbown/CodeWhale · error · anyhow::Error
Failed to parse MCP config; file contents were omitted
Error message
Failed to parse MCP config; file contents were omitted
What it means
This error comes from mutate_config in crates/tui/src/mcp.rs when it reads the existing MCP config file to apply a mutation. The raw file text failed serde_json::from_str, meaning the config file on disk is not valid JSON. The message intentionally omits the file contents (they may contain secrets like bearer tokens), so it never tells you what the bad text was.
Solutions
- Open the MCP config file and validate it with a JSON parser (jq . mcp.json or python -m json.tool) to find the syntax error and fix it
- Delete or rename the broken file and let init_config (or the app) regenerate a fresh template, then re-add servers via supported commands
- Restore the file from backup/version control if the edit was accidental
Example fix
// before (mcp.json, invalid: trailing comma)
{ "mcpServers": { "fs": { "command": "npx", } } }
// after
{ "mcpServers": { "fs": { "command": "npx" } } } Defensive patterns
Strategy: validation
Validate before calling
use std::fs;
fn mcp_config_is_valid_json(path: &std::path::Path) -> bool {
fs::read_to_string(path)
.ok()
.map(|s| serde_json::from_str::<serde_json::Value>(&s).is_ok())
.unwrap_or(false)
} Type guard
fn is_json(text: &str) -> bool {
serde_json::from_str::<serde_json::Value>(text).is_ok()
} Try / catch
match set_server_enabled(&path, name, true) {
Err(e) if e.to_string().contains("Failed to parse MCP config") => {
eprintln!("config file is not valid JSON; validate with jq and fix it");
}
Err(e) => return Err(e),
Ok(()) => {}
} Prevention
- Validate the config file with jq/python -m json.tool after every hand edit
- Never put comments or trailing commas in the MCP config — it is strict JSON
- Let the app's add/update commands edit the file instead of hand-editing
- Keep the config in version control so a corrupt edit is easy to revert
When it happens
Trigger: Any mutation entry point that routes through mutate_config (add/update/remove server, set_server_enabled, etc.) when the MCP config file exists and its text cannot be parsed as JSON — e.g. trailing commas, comments, BOM, truncated write, or a hand-edited file.
Common situations: Hand-editing mcp config and leaving a trailing comma or comment; a concurrent write or crash left a truncated file; copying a config5-style TOML content into the JSON file; editor saved with a BOM.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Failed to parse MCP config
- Failed to parse MCP config
- Failed to serialize MCP config
- invalid JSON payload at key
- invalid MCP server definition list in key
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/7918b148140c5745.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/mcp.rs:5834
}
/// Every managed MCP writer rereads under the same OS-process lock. This is
/// a delta operation, not a save of a previously loaded typed snapshot.
pub fn mutate_config<T>(
path: &Path,
expected_revision: Option<&str>,
mutate: impl FnOnce(&mut McpConfig) -> Result<T>,
) -> Result<(T, String)> {
validate_mcp_config_path(path)?;
codewhale_config::with_config_write_lock(path, |path| {
let original = read_mcp_config_file(path)?;
let revision = config_revision(original.as_deref());
if expected_revision.is_some_and(|expected| expected != revision) {
return Err(McpRevisionConflict.into());
}
let mut raw: serde_json::Value = match original.as_deref() {
Some(raw) => serde_json::from_str(raw).map_err(|_| {
anyhow::anyhow!("Failed to parse MCP config; file contents were omitted")
})?,
None => serde_json::json!({}),
};
anyhow::ensure!(raw.is_object(), "MCP config must be an object");
let mut config: McpConfig = serde_json::from_value(raw.clone())
.map_err(|_| anyhow::anyhow!("Invalid MCP config; file contents were omitted"))?;
let before = serde_json::to_value(&config)?;
let result = mutate(&mut config)?;
let after = serde_json::to_value(&config)?;
if before == after {
return Ok((result, revision));
}
// Preserve legacy spelling while applying the canonical typed delta.
let legacy = raw.get("mcpServers").is_some();
if legacy {
let object = raw
.as_object_mut()
.context("MCP config must be an object")?;View on GitHub (pinned to 73e0f67d83)