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

  1. Edit ~/.claude.json so mcpServers is an object mapping server names to configs, or remove the key entirely
  2. Back up ~/.claude.json first (it also holds Claude Code state), then re-run the caveman native install
  3. 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

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


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/a2dc47a2c8ca588f. Report an issue: GitHub.