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
- Fix the MCP server to return serverInfo with string `name` and `version` in its initialize result, per the MCP spec.
- If you control a wrapper/proxy in front of the server, ensure it forwards serverInfo verbatim.
- Pin to a known-good server version; check the server's changelog for initialize-result regressions.
- 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
- Test your server's initialize response against the MCP schema in CI.
- Log the raw initialize response when integrating a new server.
- Prefer maintained, spec-conformant server implementations.
- Wrapper/proxy code must forward serverInfo untouched.
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
- MCP server ' ': initialize capabilities must be an object
- .inputSchema is required for an advertised MCP tool
- .inputSchema must be a valid object-shaped MCP input schema
- MCP server ' ': initialize capabilities must be an object
- MCP server ' ' initialize result omitted protocolVersion
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)