Kuberwastaken/claurst · error
mcpServers must be an object
Error message
mcpServers must be an object
What it means
parse_mcp_servers in import_config.rs requires the JSON value under the `mcpServers` key to be a JSON object mapping server names to server configs. If the value is any other JSON type (string, array, number, null), this error is thrown. It guards the MCP-server import path during settings/config import from another tool's config file.
Solutions
- Open the source settings file and make "mcpServers" a JSON object keyed by server name
- Remove the mcpServers key entirely if no MCP servers should be imported (it is then skipped instead of parsed)
- If the source tool stores servers as an array, convert entries into an object keyed by server name
Example fix
// before
{ "mcpServers": [ {"name": "fs", "command": "npx"} ] }
// after
{ "mcpServers": { "fs": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-fs"] } } } Defensive patterns
Strategy: validation
Validate before calling
const cfg = JSON.parse(fs.readFileSync(path, 'utf8'));
if (cfg.mcpServers !== undefined && (typeof cfg.mcpServers !== 'object' || Array.isArray(cfg.mcpServers) || cfg.mcpServers === null)) {
throw new Error('mcpServers must be a JSON object keyed by server name');
} Type guard
const isMcpServersMap = (v) => typeof v === 'object' && v !== null && !Array.isArray(v) && Object.values(v).every((e) => typeof e === 'object' && e !== null);
Try / catch
try { importConfig(path) } catch (e) { if (String(e).includes('mcpServers must be an object')) { fixMcpServersShape(path); } else { throw e; } } Prevention
- Never null out mcpServers to disable it; delete the key instead
- Validate settings JSON against the importer schema before running import
- Keep mcpServers entries as an object map, not an array
When it happens
Trigger: Calling the config importer (via map_mcp_servers_field or parse_mcp_servers_object) with a settings file where "mcpServers" is null, a string, or an array instead of an object like {"server": {...}}.
Common situations: Importing a settings.json where mcpServers was manually emptied (set to null), a typo like "mcpServer", copying config fragments between tools, or a foreign config format that stores servers as a list.
Related errors
- mcpServers. must be an object
- mcpServers. is missing command/url
- hooks must be an object
- hooks. must be an array
- hooks. [] must be an object
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/a9ab4d399a7d8f34.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/core/src/import_config.rs:646
PreviewAction::Import
};
preview_fields.push(PreviewField {
name: format!("hooks ({})", hooks.len()),
action,
reason: None,
});
target.config.hooks = hooks;
imported_fields.push("hooks".to_string());
if action == PreviewAction::Replace {
*replaced_count += 1;
} else {
*imported_count += 1;
}
}
fn parse_mcp_servers(value: &Value) -> Result<Vec<McpServerConfig>> {
let Some(obj) = value.as_object() else {
return Err(anyhow!("mcpServers must be an object"));
};
let mut servers = Vec::new();
for (name, entry) in obj {
let entry_obj = entry
.as_object()
.ok_or_else(|| anyhow!("mcpServers.{name} must be an object"))?;
let command = entry_obj
.get("command")
.and_then(Value::as_str)
.map(ToString::to_string);
let url = entry_obj
.get("url")
.and_then(Value::as_str)
.map(ToString::to_string);
if command.is_none() && url.is_none() {
return Err(anyhow!("mcpServers.{name} is missing command/url"));
}View on GitHub (pinned to b0637c97ec)