JuliusBrussee/caveman · error
${settingsPath} mcpServers must be a JSON object; refusing t
Error message
${settingsPath} mcpServers must be a JSON object; refusing to overwrite it What it means
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.
Source
Thrown at packages/cli/src/index.ts:6111
GEMINI_NATIVE_ENV_BEGIN,
`GEMINI_BASE_URL=${route}`,
`GOOGLE_GEMINI_BASE_URL=${route}`,
`GOOGLE_VERTEX_BASE_URL=${appendUrlPath(route, "/vertex")}`,
GEMINI_NATIVE_ENV_END,
].join("\n");
return { text: `${stripped}${stripped ? "\n\n" : ""}${block}\n`, block };
}
function geminiNativeMutations(gw: string, mcpBinary: string): NativeMutation[] {
if (wrapMode(gw) === "managed") {
throw new Error("managed Gemini CLI routing is unsupported because Gemini CLI cannot send separate Caveman and upstream credentials");
}
const settingsPath = geminiSettingsPath();
const settingsBefore = fileBytes(settingsPath);
const settings = parseJsonFileObject(settingsPath, settingsBefore);
assertNativeHooksShape(settingsPath, settings, "gemini");
if (settings.mcpServers !== undefined && (typeof settings.mcpServers !== "object" || settings.mcpServers === null || Array.isArray(settings.mcpServers))) {
throw new Error(`${settingsPath} mcpServers must be a JSON object; refusing to overwrite it`);
}
const servers = settings.mcpServers && typeof settings.mcpServers === "object" && !Array.isArray(settings.mcpServers)
? settings.mcpServers as Record<string, unknown>
: {};
const previousMcp = servers.caveman;
const installedMcp = { command: mcpBinary, args: [] };
servers.caveman = installedMcp;
settings.mcpServers = servers;
const withHooks = nativeHooksDocument("gemini", true, settings);
const envPath = join(homedir(), ".gemini", ".env");
const envBefore = fileBytes(envPath);
const route = appendUrlPath(gw, "/w/gemini");
const nativeEnv = geminiNativeEnv(envBefore?.toString("utf8") ?? "", route);
return [
{
file: settingsPath,
before: settingsBefore,View on GitHub (pinned to 27d5a3981a)
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
Example fix
// before — ~/.gemini/settings.json
{ "mcpServers": null }
// after
{ "mcpServers": { "caveman": { "command": "caveman-mcp", "args": [] } } } Defensive patterns
Strategy: validation
Validate before calling
import { readFileSync } from "node:fs";
function geminiMcpShapeOk(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; }
} Type guard
const isJsonObject = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null && !Array.isArray(v);
Try / catch
try { nativeInstallGemini(); } catch (e) {
if (e instanceof Error && /mcpServers must be a JSON object/.test(e.message)) {
normalizeGeminiMcpServers(); nativeInstallGemini();
} else throw e;
} Prevention
- 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
When it happens
Trigger: Native Gemini install (local wrap mode) while the Gemini settings file has "mcpServers": [...] / string / number / null.
Common situations: Hand-edited Gemini settings replacing mcpServers with an array or disabling it via a string; another MCP config tool wrote a non-object shape.
Related errors
- ${mcpPath} mcpServers must be a JSON object; refusing to ove
- ${configPath} mcp must be a JSON object; refusing to overwri
- ${settingsPath} env must be a JSON object; refusing to overw
- ${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/e413dbafc89d23f5.
Report an issue: GitHub.