{"record":{"id":"bb90444dbe2e331f","repo":"Hmbown/CodeWhale","slug":"mcp-server-initialize-capabilities-must-be-an-object","errorCode":null,"errorMessage":"MCP server '{}': initialize capabilities must be an object","messagePattern":"MCP server '(.+?)': initialize capabilities must be an object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/mcp/src/stdio_client.rs","lineNumber":463,"sourceCode":"    let server_info = response\n        .get(\"serverInfo\")\n        .and_then(Value::as_object)\n        .with_context(|| {\n            format!(\"MCP server '{server_name}': initialize result omitted serverInfo\")\n        })?;\n    for field in [\"name\", \"version\"] {\n        if !server_info.get(field).is_some_and(Value::is_string) {\n            bail!(\"MCP server '{server_name}': initialize serverInfo.{field} must be a string\");\n        }\n    }\n\n    match response.get(\"capabilities\") {\n        None => Ok(None),\n        Some(Value::Object(capabilities)) => Ok(Some(ServerCapabilities {\n            tools: capabilities.contains_key(\"tools\"),\n            resources: capabilities.contains_key(\"resources\"),\n        })),\n        Some(_) => bail!(\"MCP server '{server_name}': initialize capabilities must be an object\"),\n    }\n}\n\n/// A live connection to one MCP server subprocess.\npub struct ChildProcessMcpClient {\n    server_name: String,\n    capabilities: Option<ServerCapabilities>,\n    connection: Mutex<Connection>,\n    request_timeout: Duration,\n}\n\nimpl std::fmt::Debug for ChildProcessMcpClient {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"ChildProcessMcpClient\")\n            .field(\"server_name\", &self.server_name)\n            .finish_non_exhaustive()\n    }\n}","sourceCodeStart":445,"sourceCodeEnd":481,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/mcp/src/stdio_client.rs#L445-L481","documentation":"After validating protocolVersion and serverInfo, `validate_initialize_result` reads the optional `capabilities` field of the `initialize` response. If it is present but not a JSON object, the client bails rather than guessing capability support. Capabilities are consulted later to decide whether tools/resources calls are legal, so a malformed value must fail the handshake.","triggerScenarios":"`spawn_with_timeouts` receives an `initialize` JSON result where `capabilities` is present but is a string, number, array, bool, or null-typed JSON value instead of an object, e.g. `\"capabilities\": []`.","commonSituations":"A custom MCP server serializing capabilities as an array of names (e.g. `[\"tools\",\"resources\"]`) instead of an object; a server emitting `\"capabilities\": null` from a template; a proxy that flattens the object.","solutions":["Fix the server to emit `capabilities` as a JSON object, e.g. {\"tools\":{},\"resources\":{}}.","If the server has no capabilities to advertise, omit the `capabilities` field entirely instead of sending a non-object value (the client treats absence as Ok(None)).","Validate the server's initialize response against the MCP JSON schema.","Check whether an intermediary (proxy, gateway) is transforming the response shape."],"exampleFix":"// before: capabilities as an array\n{\"capabilities\":[\"tools\",\"resources\"]}\n// after: capabilities as an object\n{\"capabilities\":{\"tools\":{},\"resources\":{}}}","handlingStrategy":"validation","validationCode":"fn capabilities_ok(v: &serde_json::Value) -> bool {\n    match v.get(\"capabilities\") {\n        None => true,\n        Some(c) => c.is_object(),\n    }\n}","typeGuard":"fn is_capabilities_object(v: &serde_json::Value) -> bool {\n    v.get(\"capabilities\").map_or(true, serde_json::Value::is_object)\n}","tryCatchPattern":"match spawn_with_timeouts(&config, hs, req).await {\n    Err(e) if e.to_string().contains(\"capabilities must be an object\") => {\n        eprintln!(\"server encodes capabilities wrongly; omit or use an object\");\n    }\n    other => other,\n}","preventionTips":["Emit capabilities as a JSON object (or omit the field entirely if none).","Never serialize capabilities as an array or string, even informally.","Cover the initialize response shape in the server's integration tests."],"tags":["mcp","schema-validation","capabilities","handshake"],"backgroundTag":"unexpected-response-shape","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}