Hmbown/CodeWhale · error · anyhow::Error

Failed to parse MCP config

Error message

Failed to parse MCP config {}; file contents were omitted

What it means

load_config validates the path, reads mcp config JSON, and parses it. On a JSON syntax error the message reports the path but deliberately omits file contents (to avoid leaking secrets like API keys in MCP configs into logs/UI). The underlying serde error detail is also discarded.

Solutions

  1. Validate the file with a JSON parser/linter and fix the syntax error (note: comments are not allowed)
  2. Restore the file from backup or delete it to fall back to McpConfig::default()
  3. If you need the actual serde error detail, temporarily parse the file yourself with serde_json to see the line/column

Example fix

// before (broken)
{ "mcpServers": { "fs": { "command": "npx", } } }
// after
{ "mcpServers": { "fs": { "command": "npx" } } }
Defensive patterns

Strategy: validation

Validate before calling

// validate before calling load_config
let text = std::fs::read_to_string(path)?;
serde_json::from_str::<serde_json::Value>(&text)
    .map_err(|e| anyhow!("{}: invalid JSON at {}:{}", path.display(), e.line(), e.column()))?;

Try / catch

match load_config(path) {
    Err(e) if e.to_string().contains("Failed to parse MCP config") => {
        prompt_user_to_fix_json(path); // e.g. run `jq . <file>` to locate the error
    }
    other => other,
}

Prevention

When it happens

Trigger: Loading an MCP config file whose contents are not valid JSON (trailing comma, comments, truncated file, wrong encoding).

Common situations: Hand-editing mcp.json and leaving a syntax error; a tool writing JSONC-style comments into a .json file; interrupted write leaving a truncated file.

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.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/ecaedb6f02a8adb9. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/mcp.rs:5326

        // A server that is enabled, inspected, connected and erroring on
        // nothing needs no recovery. It used to be labelled `diagnose`, so
        // every healthy row advertised a repair it did not need — founder
        // live-test: "even the ones that are connected say diagnose lol".
        return None;
    }
    if oauth_capable {
        return Some(McpRecoveryKind::Reauth);
    }
    Some(McpRecoveryKind::Reconnect)
}

pub fn load_config(path: &Path) -> Result<McpConfig> {
    validate_mcp_config_path(path)?;
    let Some(contents) = read_mcp_config_file(path)? else {
        return Ok(McpConfig::default());
    };
    serde_json::from_str(&contents).map_err(|_| {
        anyhow::anyhow!(
            "Failed to parse MCP config {}; file contents were omitted",
            codewhale_config::quote_os_path(path)
        )
    })
}

/// Maximum bytes read from an MCP config file. Configs are kilobytes.
const MAX_MCP_CONFIG_BYTES: u64 = 1024 * 1024;

fn read_mcp_config_file(path: &Path) -> Result<Option<String>> {
    let metadata = match fs::symlink_metadata(path) {
        Ok(metadata) => metadata,
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
        Err(err) => {
            return Err(err)
                .with_context(|| format!("Failed to inspect MCP config {}", path.display()));
        }
    };

View on GitHub (pinned to 73e0f67d83)