{"record":{"id":"34ed943174fcb37f","repo":"warpdotdev/warp","slug":"mcp-server-name-config-must-be-a-json-object","errorCode":null,"errorMessage":"MCP server '{name}' config must be a JSON object","messagePattern":"MCP server '(.+?)' config must be a JSON object","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"app/src/ai/agent_sdk/config_file.rs","lineNumber":120,"sourceCode":"/// Convert an unwrapped `mcp_servers` map into runtime MCP specs for AgentDriver.\n///\n/// Behavior:\n/// - Entries with a UUID `warp_id` become `MCPSpec::Uuid`.\n/// - Entries with any other non-empty `warp_id` (e.g. `\"linear\"`) become `MCPSpec::WellKnown`\n///   when `FeatureFlag::WellKnownMcpIds` is enabled (and are rejected otherwise);\n///   the server owns the set of recognized ids and unknown ids are skipped at resolution.\n/// - Entries with `command`/`url` remain as inline JSON (`MCPSpec::Json`) containing the unwrapped server map.\npub fn mcp_specs_from_mcp_servers(\n    mcp_servers: &Map<String, Value>,\n) -> anyhow::Result<Vec<MCPSpec>> {\n    let mut uuids: Vec<uuid::Uuid> = Vec::new();\n    let mut well_known: Vec<String> = Vec::new();\n    let mut json_map: Map<String, Value> = Map::new();\n\n    for (name, config) in mcp_servers {\n        let obj = config\n            .as_object()\n            .ok_or_else(|| anyhow::anyhow!(\"MCP server '{name}' config must be a JSON object\"))?;\n\n        if let Some(warp_id) = obj.get(\"warp_id\").and_then(Value::as_str) {\n            if let Ok(uuid) = uuid::Uuid::parse_str(warp_id) {\n                uuids.push(uuid);\n            } else if !FeatureFlag::WellKnownMcpIds.is_enabled() {\n                return Err(anyhow::anyhow!(\n                    \"MCP server '{name}' field 'warp_id' must be a UUID\"\n                ));\n            } else if warp_id.trim().is_empty() {\n                return Err(anyhow::anyhow!(\n                    \"MCP server '{name}' field 'warp_id' must be non-empty\"\n                ));\n            } else {\n                well_known.push(warp_id.to_string());\n            }\n        } else {\n            json_map.insert(name.clone(), config.clone());\n        }","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/warpdotdev/warp/blob/e72fd7aacbbb2236d9b3be2aad7e7178fe94b4bc/app/src/ai/agent_sdk/config_file.rs#L102-L138","documentation":"Thrown by mcp_specs_from_mcp_servers when an entry in the agent config file's `mcp_servers` map has a value that is not a JSON object. Each server's value must be a map (containing optionally `warp_id`, or `command`/`url` fields); strings, arrays, numbers, or booleans are rejected at config parse time.","triggerScenarios":"Loading an agent config file whose mcp_servers looks like {\"github\": \"https://mcp.github.com\"} (string shorthand) or {\"github\": [\"npx\", \"server\"]} (array) — any Value::as_object() == None for a server entry triggers this error before any MCP connection is attempted.","commonSituations":"Hand-editing the config and using the shorthand accepted by other tools (Claude Desktop, Cursor) that allow string URLs; copy-pasting a JSON5/YAML-converted config where nesting got flattened; or a malformed merge leaving a scalar in place of the object.","solutions":["Make every mcp_servers value a JSON object, e.g. {\"github\": {\"url\": \"https://mcp.github.com\"}} instead of a bare string.","Validate the config with a JSON schema or jq before launching: jq -e '.mcp_servers | to_entries | all(.value | type == \"object\")' config.json.","Check for stray commas/brackets from hand edits that can collapse an object into a scalar."],"exampleFix":"// before (config file)\n{\n  \"mcp_servers\": {\n    \"github\": \"https://mcp.github.com\"\n  }\n}\n\n// after\n{\n  \"mcp_servers\": {\n    \"github\": { \"url\": \"https://mcp.github.com\" }\n  }\n}","handlingStrategy":"type-guard","validationCode":"for (name, config) in &mcp_servers {\n    if !config.is_object() {\n        anyhow::bail!(\"mcp_servers.{name} must be a JSON object, got {}\", json_type(config));\n    }\n}","typeGuard":"fn is_valid_mcp_entry(v: &serde_json::Value) -> bool {\n    v.as_object().is_some()\n}","tryCatchPattern":"match mcp_specs_from_mcp_servers(&mcp_servers) {\n    Err(err) if err.to_string().contains(\"config must be a JSON object\") => {\n        // point the user at the offending server name parsed from the message\n    }\n    rest => rest,\n}","preventionTips":["Never use string shorthand for MCP servers in Warp configs — always a nested object.","Validate configs with jq or a JSON schema in CI before the agent loads them.","Beware tool-specific config dialects when copy-pasting between Claude Desktop/Cursor/Warp."],"tags":["mcp","configuration","json","validation","rust"],"backgroundTag":null,"analyzedSha":"e72fd7aacbbb2236d9b3be2aad7e7178fe94b4bc","analyzedAt":"2026-08-16T08:27:25.381Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}