xai-org/grok-build · error
[mcp_servers] is not a table
Error message
[mcp_servers] is not a table
What it means
merge_mcp_servers found an mcp_servers key whose value is not a table, so per-server config tables cannot be inserted. A missing key gets an empty table; a present non-table value triggers this error instead of overwriting the user's data.
Source
Thrown at crates/codegen/xai-grok-shell/src/claude_import.rs:980
if !env_table.contains_key(*key) {
env_table.insert(key.to_string(), TomlValue::String(value.to_string()));
count += 1;
}
}
count
}
/// Merge MCP server configs into `[mcp_servers]`. Existing servers are NOT overwritten.
fn merge_mcp_servers(
table: &mut TomlMap<String, TomlValue>,
servers: &[(&str, &McpServerConfig)],
) -> anyhow::Result<usize> {
let mcp = table
.entry("mcp_servers")
.or_insert_with(|| TomlValue::Table(TomlMap::new()));
let mcp_table = mcp
.as_table_mut()
.ok_or_else(|| anyhow::anyhow!("[mcp_servers] is not a table"))?;
let mut count = 0;
for (name, config) in servers {
// Don't overwrite existing server entries.
if !mcp_table.contains_key(*name) {
let serialized = toml::Value::try_from(*config)
.map_err(|e| anyhow::anyhow!("failed to serialize MCP server {name}: {e}"))?;
mcp_table.insert(name.to_string(), serialized);
count += 1;
}
}
Ok(count)
}
/// Merge a list of path strings into `[paths] <key>` (an array of strings).
///
/// Existing entries are preserved; new entries that aren't already present
/// are appended. Returns the number of newly added entries.View on GitHub (pinned to bc7f02eddd)
Solutions
- Restore mcp_servers to a table: [mcp_servers] or mcp_servers = {} in the config.
- Delete the malformed key and re-run the import so the table is created.
- Move existing server definitions under [mcp_servers.<name>] sections.
Example fix
// before (config.toml) mcp_servers = "disabled" // after [mcp_servers] # server entries go here
Defensive patterns
Strategy: validation
Validate before calling
let root: toml::Value = toml::from_str(&std::fs::read_to_string(&path)?)?;
if let Some(v) = root.get("mcp_servers") {
if !v.is_table() {
anyhow::bail!("mcp_servers must be a [mcp_servers] table");
}
} Type guard
fn mcp_servers_is_table(root: &toml::Value) -> bool {
root.get("mcp_servers").map(|v| v.is_table()).unwrap_or(true)
} Try / catch
if let Err(e) = apply_import(items, &path) {
if e.to_string().contains("[mcp_servers] is not a table") {
eprintln!("Restore mcp_servers to a table section, then retry the import");
} else {
return Err(e.into());
}
} Prevention
- Keep all server definitions nested under [mcp_servers.<name>].
- Never assign a scalar or string to mcp_servers.
- Validate the section type after manual edits.
When it happens
Trigger: apply_items_to_config processes MCP servers while the config has e.g. mcp_servers = true or a string/array value under mcp_servers.
Common situations: User set mcp_servers = "disabled" by mistake; another tool wrote the key as a non-table; hand-edit replaced the section with a scalar.
Related errors
- config root is not a table
- [claude_compat] is not a table
- [permission] is not a table
- permission.{key} is not an array
- failed to serialize MCP server {name}: {e}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/054b5e4e298c86ef.
Report an issue: GitHub.