affaan-m/ECC · error · Error

Invalid ${label} at ${filePath}: expected a JSON object

Error message

Invalid ${label} at ${filePath}: expected a JSON object

What it means

Thrown by readJsonObject in scripts/lib/install-targets/kimi-project.js after JSON.parse succeeds but the value is not a plain object — i.e. it is null, an array, or a primitive. The Kimi MCP merge logic iterates object keys via deepMergeJson, so a non-object top level would crash later; this guard fails fast. Same file/label as error 201 (the .mcp.json being merged).

Source

Thrown at scripts/lib/install-targets/kimi-project.js:19

const fs = require('fs');
const path = require('path');

const {
  createInstallTargetAdapter,
  createManagedOperation,
  isForeignPlatformPath,
} = require('./helpers');

function readJsonObject(filePath, label) {
  let parsed;
  try {
    parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
  } catch (error) {
    throw new Error(`Failed to parse ${label} at ${filePath}: ${error.message}`);
  }

  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    throw new Error(`Invalid ${label} at ${filePath}: expected a JSON object`);
  }

  return parsed;
}

function createMcpMergeOperation(moduleId, repoRoot, targetRoot) {
  if (!repoRoot) {
    throw new Error('repoRoot is required to plan Kimi MCP configuration');
  }

  const sourceRelativePath = '.mcp.json';
  const sourcePath = path.join(repoRoot, sourceRelativePath);
  if (!fs.existsSync(sourcePath) || !fs.statSync(sourcePath).isFile()) {
    return null;
  }

  return createManagedOperation({
    kind: 'merge-json',

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Open the named file and ensure the top level is a JSON object literal { ... }.
  2. Use the standard MCP shape { "mcpServers": { ... } }.
  3. If the intent is an empty config, write {} rather than [] or null.
  4. Re-run the Kimi install plan.

Example fix

// before (<repoRoot>/.mcp.json)
[
  { "name": "context7", "command": "npx" }
]

// after
{
  "mcpServers": {
    "context7": { "command": "npx" }
  }
}
Defensive patterns

Strategy: type-guard

Validate before calling

const v = JSON.parse(fs.readFileSync('.mcp.json', 'utf8'));
if (!(v !== null && typeof v === 'object' && !Array.isArray(v))) {
  throw new Error('.mcp.json top level must be a JSON object');
}

Type guard

function isJsonObject(value) {
  return value !== null && typeof value === 'object' && !Array.isArray(value);
}

Try / catch

try {
  readJsonObject(file, label);
} catch (err) {
  if (/expected a JSON object/.test(err.message)) {
    console.error('Top-level JSON must be an object, not an array or primitive:', file);
  }
  throw err;
}

Prevention

When it happens

Trigger: <repoRoot>/.mcp.json contains a JSON array (e.g. [ { ... } ]), a bare string/number/boolean, or null at the top level, but is syntactically valid JSON.

Common situations: User wrapped server definitions in [ ] thinking the file is a list; an empty config was serialized as null instead of {}; a generator emitted a scalar value; merge with a wrong-shape template.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/b19cb379b99c1be4. Report an issue: GitHub.