{"record":{"id":"e413dbafc89d23f5","repo":"JuliusBrussee/caveman","slug":"settingspath-mcpservers-must-be-a-json-object","errorCode":null,"errorMessage":"${settingsPath} mcpServers must be a JSON object; refusing to overwrite it","messagePattern":"(.+?) mcpServers must be a JSON object; refusing to overwrite it","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":6111,"sourceCode":"    GEMINI_NATIVE_ENV_BEGIN,\n    `GEMINI_BASE_URL=${route}`,\n    `GOOGLE_GEMINI_BASE_URL=${route}`,\n    `GOOGLE_VERTEX_BASE_URL=${appendUrlPath(route, \"/vertex\")}`,\n    GEMINI_NATIVE_ENV_END,\n  ].join(\"\\n\");\n  return { text: `${stripped}${stripped ? \"\\n\\n\" : \"\"}${block}\\n`, block };\n}\n\nfunction geminiNativeMutations(gw: string, mcpBinary: string): NativeMutation[] {\n  if (wrapMode(gw) === \"managed\") {\n    throw new Error(\"managed Gemini CLI routing is unsupported because Gemini CLI cannot send separate Caveman and upstream credentials\");\n  }\n  const settingsPath = geminiSettingsPath();\n  const settingsBefore = fileBytes(settingsPath);\n  const settings = parseJsonFileObject(settingsPath, settingsBefore);\n  assertNativeHooksShape(settingsPath, settings, \"gemini\");\n  if (settings.mcpServers !== undefined && (typeof settings.mcpServers !== \"object\" || settings.mcpServers === null || Array.isArray(settings.mcpServers))) {\n    throw new Error(`${settingsPath} mcpServers must be a JSON object; refusing to overwrite it`);\n  }\n  const servers = settings.mcpServers && typeof settings.mcpServers === \"object\" && !Array.isArray(settings.mcpServers)\n    ? settings.mcpServers as Record<string, unknown>\n    : {};\n  const previousMcp = servers.caveman;\n  const installedMcp = { command: mcpBinary, args: [] };\n  servers.caveman = installedMcp;\n  settings.mcpServers = servers;\n  const withHooks = nativeHooksDocument(\"gemini\", true, settings);\n\n  const envPath = join(homedir(), \".gemini\", \".env\");\n  const envBefore = fileBytes(envPath);\n  const route = appendUrlPath(gw, \"/w/gemini\");\n  const nativeEnv = geminiNativeEnv(envBefore?.toString(\"utf8\") ?? \"\", route);\n  return [\n    {\n      file: settingsPath,\n      before: settingsBefore,","sourceCodeStart":6093,"sourceCodeEnd":6129,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/cli/src/index.ts#L6093-L6129","documentation":"geminiNativeMutations() merges a `caveman` entry into the mcpServers map of Gemini's settings file (geminiSettingsPath(), typically ~/.gemini/settings.json). If `mcpServers` exists but is not a JSON object, it throws rather than overwrite user server definitions.","triggerScenarios":"Native Gemini install (local wrap mode) while the Gemini settings file has \"mcpServers\": [...] / string / number / null.","commonSituations":"Hand-edited Gemini settings replacing mcpServers with an array or disabling it via a string; another MCP config tool wrote a non-object shape.","solutions":["Edit the Gemini settings file so mcpServers is an object keyed by server name (or remove the key)","Confirm with `jq '.mcpServers | type' ~/.gemini/settings.json` that it is \"object\" or absent","Re-run the Gemini native install"],"exampleFix":"// before — ~/.gemini/settings.json\n{ \"mcpServers\": null }\n\n// after\n{ \"mcpServers\": { \"caveman\": { \"command\": \"caveman-mcp\", \"args\": [] } } }","handlingStrategy":"validation","validationCode":"import { readFileSync } from \"node:fs\";\nfunction geminiMcpShapeOk(path: string): boolean {\n  try {\n    const mcp = JSON.parse(readFileSync(path, \"utf8\")).mcpServers;\n    return mcp === undefined || (typeof mcp === \"object\" && mcp !== null && !Array.isArray(mcp));\n  } catch { return true; }\n}","typeGuard":"const isJsonObject = (v: unknown): v is Record<string, unknown> =>\n  typeof v === \"object\" && v !== null && !Array.isArray(v);","tryCatchPattern":"try { nativeInstallGemini(); } catch (e) {\n  if (e instanceof Error && /mcpServers must be a JSON object/.test(e.message)) {\n    normalizeGeminiMcpServers(); nativeInstallGemini();\n  } else throw e;\n}","preventionTips":["Model mcpServers as an object in Gemini settings, mirroring the Claude convention","Validate JSON shape with jq before running installers","Avoid disabling MCP by nulling the key; remove it instead"],"tags":["config","json","gemini","mcp","shape-validation","refuse-overwrite"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}