{"record":{"id":"7566be8ee5605b32","repo":"Hmbown/CodeWhale","slug":"invalid-mcp-config-file-contents-were-omitted","errorCode":null,"errorMessage":"Invalid MCP config; file contents were omitted","messagePattern":"Invalid MCP config; file contents were omitted","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp.rs","lineNumber":5840,"sourceCode":"    expected_revision: Option<&str>,\n    mutate: impl FnOnce(&mut McpConfig) -> Result<T>,\n) -> Result<(T, String)> {\n    validate_mcp_config_path(path)?;\n    codewhale_config::with_config_write_lock(path, |path| {\n        let original = read_mcp_config_file(path)?;\n        let revision = config_revision(original.as_deref());\n        if expected_revision.is_some_and(|expected| expected != revision) {\n            return Err(McpRevisionConflict.into());\n        }\n        let mut raw: serde_json::Value = match original.as_deref() {\n            Some(raw) => serde_json::from_str(raw).map_err(|_| {\n                anyhow::anyhow!(\"Failed to parse MCP config; file contents were omitted\")\n            })?,\n            None => serde_json::json!({}),\n        };\n        anyhow::ensure!(raw.is_object(), \"MCP config must be an object\");\n        let mut config: McpConfig = serde_json::from_value(raw.clone())\n            .map_err(|_| anyhow::anyhow!(\"Invalid MCP config; file contents were omitted\"))?;\n        let before = serde_json::to_value(&config)?;\n        let result = mutate(&mut config)?;\n        let after = serde_json::to_value(&config)?;\n        if before == after {\n            return Ok((result, revision));\n        }\n        // Preserve legacy spelling while applying the canonical typed delta.\n        let legacy = raw.get(\"mcpServers\").is_some();\n        if legacy {\n            let object = raw\n                .as_object_mut()\n                .context(\"MCP config must be an object\")?;\n            let servers = object.remove(\"mcpServers\").expect(\"checked above\");\n            object.insert(\"servers\".into(), servers);\n        }\n        apply_json_delta(&mut raw, &before, &after);\n        if legacy {\n            let object = raw","sourceCodeStart":5822,"sourceCodeEnd":5858,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp.rs#L5822-L5858","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (servers as array — wrong type for McpConfig)\n{ \"servers\": [{ \"name\": \"fs\" }] }\n// after\n{ \"servers\": { \"fs\": { \"command\": \"npx\", \"args\": [\"-y\", \"fs\"] } } }","handlingStrategy":"validation","validationCode":"fn matches_mcp_config_shape(path: &std::path::Path) -> bool {\n    std::fs::read_to_string(path).ok()\n        .and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok())\n        .map(|v| v.get(\"mcpServers\").or_else(|| v.get(\"servers\"))\n            .map_or(false, |m| m.is_object()))\n        .unwrap_or(false)\n}","typeGuard":"fn is_server_entry(v: &serde_json::Value) -> bool {\n    v.as_object().map_or(false, |o| o.contains_key(\"command\") || o.contains_key(\"url\"))\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().starts_with(\"Invalid MCP config\") => {\n        eprintln!(\"config object does not deserialize into McpConfig; check field names/types\");\n    }\n    _ => {}\n}","preventionTips":["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"],"tags":["mcp","config","deserialization"],"backgroundTag":"schema-validation-failed","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}