Hmbown/CodeWhale · error · anyhow::Error
Invalid MCP config; file contents were omitted
Error message
Invalid MCP config; file contents were omitted
What it means
After confirming the file is a JSON object, mutate_config deserializes it into the typed McpConfig struct. If the object's fields do not match McpConfig (unknown shape, wrong types, missing required fields), this error is thrown. As elsewhere, the file contents are deliberately omitted from the message.
Solutions
- Compare the file against McpConfig's expected shape and fix field names/types, especially the servers map and each server entry's command/args/env
- Validate incrementally: parse with jq and inspect the mcpServers/servers object entries for missing or mistyped fields
- Regenerate with init_config --force and re-add servers through the supported add/update commands
Example fix
// before (servers as array — wrong type for McpConfig)
{ "servers": [{ "name": "fs" }] }
// after
{ "servers": { "fs": { "command": "npx", "args": ["-y", "fs"] } } } Defensive patterns
Strategy: validation
Validate before calling
fn matches_mcp_config_shape(path: &std::path::Path) -> bool {
std::fs::read_to_string(path).ok()
.and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok())
.map(|v| v.get("mcpServers").or_else(|| v.get("servers"))
.map_or(false, |m| m.is_object()))
.unwrap_or(false)
} Type guard
fn is_server_entry(v: &serde_json::Value) -> bool {
v.as_object().map_or(false, |o| o.contains_key("command") || o.contains_key("url"))
} Try / catch
match result {
Err(e) if e.to_string().starts_with("Invalid MCP config") => {
eprintln!("config object does not deserialize into McpConfig; check field names/types");
}
_ => {}
} Prevention
- Compare your file against a freshly generated template from init_config before editing
- Keep server entries under the servers map with command/args/env (or url) fields only
- Make schema-shaped edits through the app's commands rather than raw text edits
- Snapshot the file in git so a schema-breaking edit is diffable
When it happens
Trigger: Any mutate_config-backed operation when the config file is a JSON object but its contents do not deserialize into McpConfig — wrong field types (e.g. servers is an array), unrecognized top-level shape, or a map whose entries are not valid server objects.
Common situations: Mixing another tool's config schema into the file; a server entry missing "command"; renaming fields by hand; pasting a partial snippet that has mcpServers nested twice.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- {error}
- Failed to parse configuration; file contents were omitted
- Failed to parse MCP config
- Failed to parse MCP config
- Failed to parse MCP config; file contents were omitted
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/7566be8ee5605b32.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/mcp.rs:5840
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")?;
let servers = object.remove("mcpServers").expect("checked above");
object.insert("servers".into(), servers);
}
apply_json_delta(&mut raw, &before, &after);
if legacy {
let object = rawView on GitHub (pinned to 73e0f67d83)