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
- Edit the config so mcp_servers is a table: [mcp_servers.my-server] with per-server keys
- Convert a list-of-names mcp_servers into the table form with one sub-table per server
- Remove the offending mcp_servers key and re-run save_mcp_server_config to recreate it correctly
- 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
- Always define MCP servers with [mcp_servers.name] sub-tables, never as arrays or strings
- Share a schema/example config so contributors use the correct shape
- Validate mcp_servers type at startup before any save operation
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
- config root is not a table
- [claude_compat] is not a table
- [permission] is not a table
- permission.{key} is not an array
- [mcp_servers] is not a table
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/3583394c13549886.
Report an issue: GitHub.