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

  1. 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
  2. Validate incrementally: parse with jq and inspect the mcpServers/servers object entries for missing or mistyped fields
  3. 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

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


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 = raw

View on GitHub (pinned to 73e0f67d83)