xai-org/grok-build · error
disabled_mcp_tools is not a table
Error message
disabled_mcp_tools is not a table
What it means
Within save_mcp_disabled_tools, the code fetches/creates the `disabled_mcp_tools` section and requires it to be a table. If the key already exists in the config but holds a non-table TOML value (string, integer, array), as_table_mut fails with this error.
Source
Thrown at crates/codegen/xai-grok-shell/src/util/config/mcp.rs:650
/// incomplete server entries that fail to deserialize for managed servers.
pub(crate) async fn save_mcp_disabled_tools(
server_name: &str,
disabled_tools: &[String],
) -> Result<()> {
let path = config_path();
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 section = table
.entry("disabled_mcp_tools")
.or_insert_with(|| TomlValue::Table(TomlMap::new()))
.as_table_mut()
.ok_or_else(|| anyhow::anyhow!("disabled_mcp_tools is not a table"))?;
if disabled_tools.is_empty() {
section.remove(server_name);
if section.is_empty() {
table.remove("disabled_mcp_tools");
}
} else {
let arr = disabled_tools
.iter()
.map(|s| TomlValue::String(s.clone()))
.collect();
section.insert(server_name.to_string(), TomlValue::Array(arr));
}
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
- Edit the config file so `disabled_mcp_tools` is a proper table: `[disabled_mcp_tools]\nserver = { }` or `disabled_mcp_tools = { server = [...] }` per the expected schema.
- Remove the bogus `disabled_mcp_tools` line and rerun the save to regenerate it.
- Validate the config with a TOML parser/schema check before saving.
- Harden the code: if the existing value is not a table, replace it with a fresh table instead of erroring.
Example fix
// before # config.toml disabled_mcp_tools = true // after # config.toml [disabled_mcp_tools] [disabled_mcp_tools.my_server] enabled = false
Defensive patterns
Strategy: validation
Validate before calling
fn disabled_tools_section_ok(path: &Path) -> bool {
std::fs::read_to_string(path).ok()
.and_then(|s| s.parse::<toml::Value>().ok())
.and_then(|v| v.get("disabled_mcp_tools").cloned())
.map(|v| v.is_table())
.unwrap_or(true) // absent key is fine
} Type guard
fn is_section_table(v: Option<&toml::Value>) -> bool {
v.map(|v| v.is_table()).unwrap_or(true)
} Try / catch
match save_mcp_disabled_tools(&cfg, &server, &tools).await {
Err(e) if e.to_string().contains("disabled_mcp_tools is not a table") => {
repair_section_to_table(&cfg_path, "disabled_mcp_tools")?;
save_mcp_disabled_tools(&cfg, &server, &tools).await
}
other => other,
} Prevention
- Never hand-write disabled_mcp_tools as a scalar or array
- Coerce non-table sections to fresh tables instead of erroring
- Run a TOML schema check after manual config edits
- Keep one writer for the config to avoid schema drift
When it happens
Trigger: Calling save_mcp_disabled_tools when the config file contains `disabled_mcp_tools = <non-table value>` (e.g. `disabled_mcp_tools = true` or a list) instead of a `[disabled_mcp_tools]` section.
Common situations: Hand-edited or tool-mangled config where disabled_mcp_tools was written as a scalar/array; merge of configs from different schema versions.
Related errors
- Failed to load config: {e}
- config root is not a table
- refusing to overwrite unparseable {}: {}; fix the syntax bef
- Failed to create agent config: {e}
- Failed to create agent config: {e}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/502589bbe42628e1.
Report an issue: GitHub.