JuliusBrussee/caveman · error
${configPath} mcp must be a JSON object; refusing to overwri
Error message
${configPath} mcp must be a JSON object; refusing to overwrite it What it means
opencodeNativeMutations() also merges a `caveman` local MCP server into the `mcp` key of ~/.config/opencode/opencode.json. If `mcp` exists but is not a JSON object (opencode expects a map of server name to {type, command, enabled}), it refuses to overwrite.
Source
Thrown at packages/cli/src/index.ts:6328
},
dispose: async () => {
for (const sessionID of contexts.keys()) call("SessionEnd", { session_id: sessionID });
contexts.clear();
pending.clear();
},
});
`;
}
function opencodeNativeMutations(gw: string, mcpBinary: string): NativeMutation[] {
const configPath = join(homedir(), ".config", "opencode", "opencode.json");
const before = fileBytes(configPath);
const root = parseJsonFileObject(configPath, before);
if (root.provider !== undefined && (typeof root.provider !== "object" || root.provider === null || Array.isArray(root.provider))) {
throw new Error(`${configPath} provider must be a JSON object; refusing to overwrite it`);
}
if (root.mcp !== undefined && (typeof root.mcp !== "object" || root.mcp === null || Array.isArray(root.mcp))) {
throw new Error(`${configPath} mcp must be a JSON object; refusing to overwrite it`);
}
const providers = root.provider && typeof root.provider === "object" && !Array.isArray(root.provider) ? root.provider as Record<string, unknown> : {};
const previousRoutes: Record<string, unknown> = {};
const base = appendUrlPath(gw, "/w/opencode");
const routes = { openai: appendUrlPath(base, "/openai/v1"), anthropic: appendUrlPath(base, "/anthropic/v1") };
for (const [providerID, route] of Object.entries(routes)) {
const provider = providers[providerID] && typeof providers[providerID] === "object" && !Array.isArray(providers[providerID]) ? providers[providerID] as Record<string, unknown> : {};
const options = provider.options && typeof provider.options === "object" && !Array.isArray(provider.options) ? provider.options as Record<string, unknown> : {};
previousRoutes[providerID] = options.baseURL ?? null;
options.baseURL = route;
provider.options = options;
providers[providerID] = provider;
}
root.provider = providers;
const mcp = root.mcp && typeof root.mcp === "object" && !Array.isArray(root.mcp) ? root.mcp as Record<string, unknown> : {};
const previousMcp = mcp.caveman;
const installedMcp = { type: "local", command: [mcpBinary], enabled: true };
mcp.caveman = installedMcp;View on GitHub (pinned to 27d5a3981a)
Solutions
- Convert `mcp` to an object mapping server names to configs, or remove the key
- Verify `jq '.mcp | type' ~/.config/opencode/opencode.json` yields "object" or the key is absent
- Re-run the opencode native install
Example fix
// before
{ "mcp": [ { "caveman": {} } ] }
// after
{ "mcp": { "caveman": { "type": "local", "command": ["caveman-mcp"], "enabled": true } } } Defensive patterns
Strategy: validation
Validate before calling
import { readFileSync } from "node:fs";
function opencodeMcpShapeOk(path: string): boolean {
try {
const m = JSON.parse(readFileSync(path, "utf8")).mcp;
return m === undefined || (typeof m === "object" && m !== null && !Array.isArray(m));
} 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 { nativeInstallOpencode(); } catch (e) {
if (e instanceof Error && /mcp must be a JSON object/.test(e.message)) {
rewriteMcpAsObject(); nativeInstallOpencode();
} else throw e;
} Prevention
- Use opencode's native MCP object format ({ name: { type, command, enabled } })
- Don't paste Claude-style array MCP configs into opencode.json
- Check shape with jq before invoking installers
When it happens
Trigger: Native opencode install while opencode.json has "mcp": ["server"] , "mcp": true, "mcp": "caveman", or null.
Common situations: Copied an MCP config snippet using an array syntax from another tool (Claude-style mcpServers arrays); hand-edited boolean toggles; stale config from an older opencode schema.
Related errors
- ${mcpPath} mcpServers must be a JSON object; refusing to ove
- ${settingsPath} mcpServers must be a JSON object; refusing t
- ${configPath} provider must be a JSON object; refusing to ov
- ${settingsPath} env must be a JSON object; refusing to overw
- caveman build: invalid .caveman/provider.json
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/1db70389765ab2b4.
Report an issue: GitHub.