xai-org/grok-build · error

failed to serialize MCP server config: {e}

Error message

failed to serialize MCP server config: {e}

What it means

save_mcp_server_config_at converts the in-memory MCP server config struct into a TOML value via toml::Value::try_from before merging it into config.toml. If the config struct contains types TOML cannot represent (e.g. non-string map keys, nested Options serialized as unit, or unsupported value types), serialization fails and this error is thrown. It wraps the underlying toml serializer error so the TOML detail is embedded in the message.

Source

Thrown at crates/codegen/xai-grok-shell/src/util/config/mcp.rs:979

    server_name: &str,
    config: &McpServerConfig,
) -> Result<()> {
    let mut root: TomlValue = match tokio::fs::read_to_string(&path).await {
        Ok(s) => toml::from_str(&s).unwrap_or(TomlValue::Table(TomlMap::new())),
        Err(_) => TomlValue::Table(TomlMap::new()),
    };
    let table = root
        .as_table_mut()
        .ok_or_else(|| anyhow::anyhow!("config root is not a table"))?;

    let servers = table
        .entry("mcp_servers")
        .or_insert_with(|| TomlValue::Table(TomlMap::new()))
        .as_table_mut()
        .ok_or_else(|| anyhow::anyhow!("mcp_servers is not a table"))?;

    let serialized = toml::Value::try_from(config)
        .map_err(|e| anyhow::anyhow!("failed to serialize MCP server config: {e}"))?;
    servers.insert(server_name.to_string(), serialized);

    // Ensure the server isn't in the disabled list.
    if let Some(arr) = table
        .get_mut("disabled_mcp_servers")
        .and_then(|v| v.as_array_mut())
    {
        arr.retain(|v| v.as_str() != Some(server_name));
        if arr.is_empty() {
            table.remove("disabled_mcp_servers");
        }
    }

    let toml_str = toml::to_string_pretty(&root)?;
    let tmp = path.with_extension("toml.tmp");
    if let Some(parent) = path.parent() {
        let _ = tokio::fs::create_dir_all(parent).await;
    }

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Inspect the wrapped {e} TOML error to identify the offending field/value in the config struct
  2. Convert non-string map keys to strings before saving (e.g. BTreeMap<String, T>)
  3. Replace NaN/infinity or heterogeneous-typed values with TOML-compatible equivalents
  4. Update the config struct's serde attributes to emit TOML-representable shapes

Example fix

// before
let serialized = toml::Value::try_from(config)
    .map_err(|e| anyhow::anyhow!("failed to serialize MCP server config: {e}"))?;
// after
let config = sanitize_config_for_toml(config); // stringify map keys, replace NaN
let serialized = toml::Value::try_from(&config)
    .map_err(|e| anyhow::anyhow!("failed to serialize MCP server config: {e}"))?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_toml_serializable(config: &McpServerConfig) -> bool {
    toml::Value::try_from(config).is_ok()
}
if !is_toml_serializable(&config) { eprintln!("config not TOML-serializable"); }

Type guard

fn as_toml_value<T: serde::Serialize>(v: &T) -> Option<toml::Value> {
    toml::Value::try_from(v).ok()
}

Prevention

When it happens

Trigger: Calling save_mcp_server_config / save_mcp_server_config_at with an McpServerConfig whose fields produce TOML-incompatible values — e.g. a map with non-string keys (toml only supports string keys), a nested None option, or a float that is NaN/infinity.

Common situations: Programmatically constructing server config from deserialized JSON (JSON keys can be integers, TOML keys cannot); hand-built configs with heterogeneous arrays (TOML requires homogeneous arrays); NaN/inf values coming from a numeric parser.

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/b42a4e25764364a4. Report an issue: GitHub.