{"record":{"id":"a2bf95e16d2d0bb6","repo":"Hmbown/CodeWhale","slug":"mcp-server-initialize-serverinfo-must-be-a-string","errorCode":null,"errorMessage":"MCP server '{}': initialize serverInfo.{} must be a string","messagePattern":"MCP server '(.+?)': initialize serverInfo\\.(.+?) must be a string","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/mcp/src/stdio_client.rs","lineNumber":453,"sourceCode":"        .and_then(Value::as_str)\n        .with_context(|| {\n            format!(\"MCP server '{server_name}': initialize result omitted protocolVersion\")\n        })?;\n    if protocol_version != PROTOCOL_VERSION {\n        bail!(\n            \"MCP server '{server_name}': unsupported protocol version '{protocol_version}' (expected {PROTOCOL_VERSION})\"\n        );\n    }\n\n    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>,","sourceCodeStart":435,"sourceCodeEnd":471,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/mcp/src/stdio_client.rs#L435-L471","documentation":"`validate_initialize_result` requires the `initialize` response's `serverInfo` object to contain string-valued `name` and `version` fields; it bails when either is absent or of another JSON type. The client records server identity from these fields, and a malformed identity would make diagnostics and dedup unreliable. This is strict schema validation of the MCP handshake response.","triggerScenarios":"`spawn_with_timeouts` receives an `initialize` JSON result whose `serverInfo` exists (otherwise a different context error fires) but where `serverInfo.name` or `serverInfo.version` is missing, null, a number, or some other non-string JSON value.","commonSituations":"A hand-rolled or homebrew MCP server returning `\"version\": 1` (number) instead of `\"1\"`; a server omitting `version` entirely; a buggy proxy that mangles the serverInfo object; a server emitting a JSON array instead of an object for serverInfo.","solutions":["Fix the MCP server so `initialize` returns `serverInfo: {\"name\": \"<string>\", \"version\": \"<string>\"} with both fields as strings.","If you control the server, validate its initialize response against the MCP schema before deployment.","Check for a proxy or middleware rewriting the response and remove/fix it.","File an issue with the server vendor if it is a third-party binary returning non-conformant serverInfo."],"exampleFix":"// before: non-string version in initialize result\n{\"protocolVersion\":\"...\",\"serverInfo\":{\"name\":\"my-server\",\"version\":2}}\n// after: both fields are strings\n{\"protocolVersion\":\"...\",\"serverInfo\":{\"name\":\"my-server\",\"version\":\"2.0.1\"}}","handlingStrategy":"validation","validationCode":"fn initialize_result_has_valid_server_info(v: &serde_json::Value) -> bool {\n    let si = v.get(\"serverInfo\");\n    si.is_some_and(|s| s.is_object())\n        && [\"name\", \"version\"].iter().all(|f| {\n            si.unwrap().get(f).is_some_and(serde_json::Value::is_string)\n        })\n}","typeGuard":"fn as_string_field<'a>(v: &'a serde_json::Value, k: &str) -> Option<&'a str> {\n    v.get(k).and_then(serde_json::Value::as_str)\n}","tryCatchPattern":"match spawn_with_timeouts(&config, hs, req).await {\n    Err(e) if e.to_string().contains(\"serverInfo\") => {\n        eprintln!(\"server returned malformed serverInfo; fix server initialize response\");\n    }\n    other => other,\n}","preventionTips":["Validate your server's initialize response against the MCP JSON schema before release.","Always emit name and version as strings, never numbers or null.","Include a handshake conformance test in the server's own test suite."],"tags":["mcp","schema-validation","handshake","serverinfo"],"backgroundTag":"schema-validation-failed","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"}