xai-org/grok-build · error

mcp_servers is not a table

Error message

mcp_servers is not a table

What it means

save_mcp_server_config_at looks up (or creates) the `mcp_servers` entry in the root table before inserting the server config. If `mcp_servers` exists but holds a non-table TOML value (string, array, integer), as_table_mut() returns None and this error is thrown — the server cannot be stored under a key that is not a table.

Source

Thrown at crates/codegen/xai-grok-shell/src/util/config/mcp.rs:976

/// config file, e.g. a project-scoped `.grok/config.toml`.
pub async fn save_mcp_server_config_at(
    path: &std::path::Path,
    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");

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Edit the config so mcp_servers is a table: [mcp_servers.my-server] with per-server keys
  2. Convert a list-of-names mcp_servers into the table form with one sub-table per server
  3. Remove the offending mcp_servers key and re-run save_mcp_server_config to recreate it correctly
  4. Validate the config with a TOML/schema checker before saving

Example fix

// before (config.toml)
mcp_servers = ["a", "b"]
// after (config.toml)
[mcp_servers.a]
command = "a-cmd"
[mcp_servers.b]
command = "b-cmd"
Defensive patterns

Strategy: type-guard

Validate before calling

// before save_mcp_server_config
let root: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
let bad = root.get("mcp_servers").map(|v| !v.is_table()).unwrap_or(false);
if bad {
    eprintln!("mcp_servers must be a table; fixing config");
    // rewrite mcp_servers as [mcp_servers.<name>] sub-tables before saving
}

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

match save_mcp_server_config(...) {
    Err(e) if e.to_string().contains("mcp_servers is not a table") => {
        eprintln!("convert mcp_servers to a [mcp_servers.<name>] table in the config");
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling save_mcp_server_config on a config where `mcp_servers = "foo"` or `mcp_servers = [ ... ]` — i.e. the key exists with a scalar/array type instead of a table of server definitions.

Common situations: A previous tool or script wrote mcp_servers as a list of names instead of a table of server objects; manual edit set the wrong type; schema drift between tool versions.

Related errors


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