{"record":{"id":"1adfd78aa3eb1ca2","repo":"nexu-io/open-design","slug":"existing-config-at-where-is-not-a-json-object","errorCode":null,"errorMessage":"existing config at ${where} is not a JSON object","messagePattern":"existing config at (.+?) is not a JSON object","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/mcp-agent-install.ts","lineNumber":426,"sourceCode":"    cursor = next as Record<string, unknown>;\n  }\n  if (!(plan.serverKey in cursor)) return null;\n  delete cursor[plan.serverKey];\n  return `${JSON.stringify(root, null, 2)}\\n`;\n}\n\nfunction parseJsonObject(text: string | null, where: string): Record<string, unknown> {\n  if (text == null || text.trim() === '') return {};\n  let parsed: unknown;\n  try {\n    parsed = JSON.parse(text);\n  } catch (err) {\n    throw new Error(\n      `existing config at ${where} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n    );\n  }\n  if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n    throw new Error(`existing config at ${where} is not a JSON object`);\n  }\n  return parsed as Record<string, unknown>;\n}\n\n// --- Snippets for the manual (print-only) strategy ----------------------\n\nfunction genericMcpServersSnippet(spec: McpLaunchSpec, name: string): string {\n  const server: Record<string, unknown> = {\n    command: spec.command,\n    args: spec.args,\n  };\n  if (Object.keys(spec.env).length > 0) server.env = spec.env;\n  return JSON.stringify({ mcpServers: { [name]: server } }, null, 2);\n}\n\nfunction hermesYamlSnippet(spec: McpLaunchSpec, name: string): string {\n  const lines = [\n    'mcp_servers:',","sourceCodeStart":408,"sourceCodeEnd":444,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/mcp-agent-install.ts#L408-L444","documentation":"Thrown by parseJsonObject() after JSON.parse() succeeds but the parsed value is not a JSON object — i.e. it is null, an array, or a primitive (string/number/boolean). MCP client config roots must be objects (so a server entry can be inserted at a key path like `mcpServers.<name>`); a non-object root cannot host the nested key path the installer needs.","triggerScenarios":"Target config file contains a bare array (`[{...}]`), a bare string, a bare number, or the literal `null` at its root. Example: a codex config that was accidentally written as an array of server entries.","commonSituations":"User restructured their config file to a list format; another tool overwrote the config with a non-object schema; file was hand-edited to `null` to 'disable' it.","solutions":["Rewrite the file so its root is a JSON object `{ ... }` with the expected key path (commonly `mcpServers`).","If you genuinely need a list format for another tool, point the installer at a different config file or migrate the data into object form.","Confirm with `jq -e 'type == \"object\"' <path>` that the root is an object."],"exampleFix":"// before (config.json)\n[\n  { \"name\": \"foo\", \"command\": \"foo\" }\n]\n// after\n{\n  \"mcpServers\": {\n    \"foo\": { \"command\": \"foo\" }\n  }\n}","handlingStrategy":"validation","validationCode":"function isJsonObjectRoot(text: string | null): boolean {\n  if (text == null || text.trim() === '') return true; // empty is treated as {}\n  let v: unknown;\n  try { v = JSON.parse(text); } catch { return false; }\n  return v !== null && typeof v === 'object' && !Array.isArray(v);\n}\n\nif (!isJsonObjectRoot(existingText)) {\n  throw new Error(`Config root must be a JSON object; migrate or pick another file.`);\n}","typeGuard":"function isPlainJsonObject(value: unknown): value is Record<string, unknown> {\n  return value !== null && typeof value === 'object' && !Array.isArray(value);\n}","tryCatchPattern":"try {\n  applyJsonInstall(existingText, plan);\n} catch (error) {\n  if (error instanceof Error && error.message.includes('is not a JSON object')) {\n    throw new Error(`Config root at ${plan.configPath} is not an object; restructure it as { ... } first.`);\n  }\n  throw error;\n}","preventionTips":["Maintain MCP client config roots as objects keyed by `mcpServers` (or the host's convention).","Do not let other tools overwrite the config with a list or scalar.","Validate root shape with `jq -e 'type == \"object\"'` after edits."],"tags":["mcp","agent-install","json","config","validation","schema"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}