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
- Inspect the wrapped {e} TOML error to identify the offending field/value in the config struct
- Convert non-string map keys to strings before saving (e.g. BTreeMap<String, T>)
- Replace NaN/infinity or heterogeneous-typed values with TOML-compatible equivalents
- 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
- Keep MCP config map keys as Strings (TOML requires string keys)
- Avoid NaN/infinity and heterogeneous arrays in config values
- Add a round-trip test: deserialize -> serialize config with toml in CI
- Sanitize JSON-derived configs before converting to TOML
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
- failed to serialize MCP server {name}: {e}
- [mcp_servers] is not a table
- Failed to create agent config: {e}
- Failed to load config: {e}
- Failed to parse config.toml: {e}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/b42a4e25764364a4.
Report an issue: GitHub.