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

  1. Restore mcp_servers to a table: [mcp_servers] or mcp_servers = {} in the config.
  2. Delete the malformed key and re-run the import so the table is created.
  3. 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

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


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