JuliusBrussee/caveman · error
${mcpPath} mcpServers must be a JSON object; refusing to ove
Error message
${mcpPath} mcpServers must be a JSON object; refusing to overwrite it What it means
claudeNativeMutations() merges a `caveman` entry into the mcpServers map inside ~/.claude.json (Claude Code's MCP registry). If an `mcpServers` key exists but is not a JSON object (array, string, number, or null), it throws instead of overwriting, because that key holds user-defined server configs.
Source
Thrown at packages/cli/src/index.ts:6056
const settings = parseJsonFileObject(settingsPath, settingsBefore);
if (settings.env !== undefined && (typeof settings.env !== "object" || settings.env === null || Array.isArray(settings.env))) {
throw new Error(`${settingsPath} env must be a JSON object; refusing to overwrite it`);
}
assertNativeHooksShape(settingsPath, settings, "claude");
const env = settings.env && typeof settings.env === "object" && !Array.isArray(settings.env)
? settings.env as Record<string, unknown>
: {};
const route = appendUrlPath(gw, "/w/claude");
const previousRoute = env.ANTHROPIC_BASE_URL;
env.ANTHROPIC_BASE_URL = route;
settings.env = env;
const withHooks = nativeHooksDocument("claude", true, settings);
const mcpPath = join(homedir(), ".claude.json");
const mcpBefore = fileBytes(mcpPath);
const mcpRoot = parseJsonFileObject(mcpPath, mcpBefore);
if (mcpRoot.mcpServers !== undefined && (typeof mcpRoot.mcpServers !== "object" || mcpRoot.mcpServers === null || Array.isArray(mcpRoot.mcpServers))) {
throw new Error(`${mcpPath} mcpServers must be a JSON object; refusing to overwrite it`);
}
const servers = mcpRoot.mcpServers && typeof mcpRoot.mcpServers === "object" && !Array.isArray(mcpRoot.mcpServers)
? mcpRoot.mcpServers as Record<string, unknown>
: {};
const previousMcp = servers.caveman;
const installedMcp = { type: "stdio", command: mcpBinary, args: [], env: {} };
servers.caveman = installedMcp;
mcpRoot.mcpServers = servers;
return [
{
file: settingsPath,
before: settingsBefore,
after: Buffer.from(JSON.stringify(withHooks, null, 2) + "\n"),
kind: "claude-settings",
owned: { route, previous_route: previousRoute ?? null },
},
{View on GitHub (pinned to 27d5a3981a)
Solutions
- Edit ~/.claude.json so mcpServers is an object mapping server names to configs, or remove the key entirely
- Back up ~/.claude.json first (it also holds Claude Code state), then re-run the caveman native install
- Verify with `jq '.mcpServers | type' ~/.claude.json` that it reports "object" or is absent
Example fix
// before — ~/.claude.json
{ "mcpServers": [ { "name": "caveman" } ] }
// after
{ "mcpServers": { "caveman": { "type": "stdio", "command": "caveman-mcp", "args": [] } } } Defensive patterns
Strategy: validation
Validate before calling
import { readFileSync } from "node:fs";
function claudeMcpShapeOk(path: string): boolean {
try {
const mcp = JSON.parse(readFileSync(path, "utf8")).mcpServers;
return mcp === undefined || (typeof mcp === "object" && mcp !== null && !Array.isArray(mcp));
} catch { return true; }
}
if (!claudeMcpShapeOk(`${homedir}/.claude.json`)) normalizeMcpServers(); Type guard
const isJsonObject = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null && !Array.isArray(v);
Try / catch
try { nativeInstallClaude(); } catch (e) {
if (e instanceof Error && /mcpServers must be a JSON object/.test(e.message)) {
rewriteMcpServersAsObject(); nativeInstallClaude();
} else throw e;
} Prevention
- Keep ~/.claude.json mcpServers as a name-keyed object; never an array or scalar
- Prefer tools (claude mcp add) over hand-editing ~/.claude.json
- Check shape before install: jq '.mcpServers | type' ~/.claude.json
When it happens
Trigger: Native Claude install when ~/.claude.json contains "mcpServers": [...] or "mcpServers": "none" or "mcpServers": null.
Common situations: User manually disabled MCP servers by replacing the object with a string/null; a different MCP manager tool wrote an array; hand-editing ~/.claude.json introduced a shape change.
Related errors
- ${settingsPath} env must be a JSON object; refusing to overw
- ${settingsPath} mcpServers must be a JSON object; refusing t
- ${configPath} mcp must be a JSON object; refusing to overwri
- ${configPath} provider must be a JSON object; refusing to ov
- caveman build: invalid .caveman/provider.json
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/a2dc47a2c8ca588f.
Report an issue: GitHub.