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

  1. 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.
  2. Remove the bogus `disabled_mcp_tools` line and rerun the save to regenerate it.
  3. Validate the config with a TOML parser/schema check before saving.
  4. 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

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


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