Hmbown/CodeWhale · error

MCP server ' ': initialize serverInfo. must be a string

Error message

MCP server '{server_name}': initialize serverInfo.{field} must be a string

What it means

During the MCP initialize handshake, stdio_client validates that the child server's initialize result contains a serverInfo object with string-valued `name` and `version` fields. This bail fires when either field is missing or is not a JSON string. The library enforces this because protocol and server identity are recorded for capability negotiation and diagnostics.

Solutions

  1. Fix the MCP server to return serverInfo with string `name` and `version` in its initialize result, per the MCP spec.
  2. If you control a wrapper/proxy in front of the server, ensure it forwards serverInfo verbatim.
  3. Pin to a known-good server version; check the server's changelog for initialize-result regressions.
  4. Capture raw stdout of the child to inspect the actual initialize JSON it sent.

Example fix

// server before (non-conformant)
{"jsonrpc":"2.0","id":1,"result":{"serverInfo":{"name":42}}}
// after
{"jsonrpc":"2.0","id":1,"result":{"serverInfo":{"name":"my-server","version":"1.0.0"}}}
Defensive patterns

Strategy: validation

Validate before calling

fn server_info_is_valid(init: &serde_json::Value) -> bool {
    init.get("result").and_then(|r| r.get("serverInfo")).and_then(serde_json::Value::as_object)
        .map(|si| ["name", "version"].iter().all(|f| si.get(*f).is_some_and(serde_json::Value::is_string)))
        .unwrap_or(false)
}

Type guard

fn as_server_info(v: &Value) -> Option<(&str, &str)> {
    let si = v.get("result")?.get("serverInfo")?.as_object()?;
    Some((si.get("name")?.as_str()?, si.get("version")?.as_str()?))
}

Try / catch

match spawn_with_timeouts(&config, handshake, request_timeout) {
    Ok(c) => c,
    Err(e) if e.to_string().contains("serverInfo") => {
        eprintln!("server sent malformed serverInfo; check server version");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling spawn_with_timeouts (directly or via connect) against an MCP server whose initialize response has serverInfo missing, null, a non-object, or whose `name`/`version` is absent or a non-string (number, object, etc.).

Common situations: Homegrown or non-conformant MCP servers returning malformed serverInfo; servers skipping the spec's serverInfo; proxies or wrappers that rewrite initialize responses and drop or retype fields.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at crates/mcp/src/stdio_client.rs:454

        })?;
    // Negotiation per spec: we advertise the newest revision and accept any
    // dated revision we still implement; anything else ends the handshake.
    if !MCP_SUPPORTED_PROTOCOL_VERSIONS.contains(&protocol_version) {
        bail!(
            "MCP server '{server_name}': unsupported protocol version '{protocol_version}' (supported: {})",
            MCP_SUPPORTED_PROTOCOL_VERSIONS.join(", ")
        );
    }

    let server_info = response
        .get("serverInfo")
        .and_then(Value::as_object)
        .with_context(|| {
            format!("MCP server '{server_name}': initialize result omitted serverInfo")
        })?;
    for field in ["name", "version"] {
        if !server_info.get(field).is_some_and(Value::is_string) {
            bail!("MCP server '{server_name}': initialize serverInfo.{field} must be a string");
        }
    }

    match response.get("capabilities") {
        None => Ok(None),
        Some(Value::Object(capabilities)) => Ok(Some(ServerCapabilities {
            tools: capabilities.contains_key("tools"),
            resources: capabilities.contains_key("resources"),
        })),
        Some(_) => bail!("MCP server '{server_name}': initialize capabilities must be an object"),
    }
}

/// A live connection to one MCP server subprocess.
pub struct ChildProcessMcpClient {
    server_name: String,
    capabilities: Option<ServerCapabilities>,
    connection: Mutex<Connection>,

View on GitHub (pinned to 73e0f67d83)