coleam00/Archon · error

MCP server "${serverName}" must be a JSON object (got ${desc

Error message

MCP server "${serverName}" must be a JSON object (got ${describeJsonType(serverConfig)})

What it means

expandEnvVars iterates the top-level server map and requires each server entry to be a JSON object (not null, not an array, not a primitive). This fails fast because per-server handling (env/headers expansion) only makes sense for object-shaped server configs.

Source

Thrown at packages/providers/src/mcp/config.ts:61

        return envVal ?? '';
      }
    );
  }
  return result;
}

function expandEnvVars(
  config: Record<string, unknown>,
  envSource: EnvSource
): {
  expanded: Record<string, unknown>;
  missingVars: string[];
} {
  const result: Record<string, unknown> = {};
  const missingVars: string[] = [];
  for (const [serverName, serverConfig] of Object.entries(config)) {
    if (typeof serverConfig !== 'object' || serverConfig === null || Array.isArray(serverConfig)) {
      throw new Error(
        `MCP server "${serverName}" must be a JSON object (got ${describeJsonType(serverConfig)})`
      );
    }
    const server = { ...(serverConfig as Record<string, unknown>) };
    if (server.env !== undefined) {
      if (typeof server.env !== 'object' || server.env === null || Array.isArray(server.env)) {
        throw new Error(
          `MCP config ${serverName}.env must be a JSON object of string values (got ${describeJsonType(server.env)})`
        );
      }
      server.env = expandEnvVarsInRecord(
        server.env as Record<string, unknown>,
        missingVars,
        envSource,
        `${serverName}.env`
      );
    }
    if (server.headers !== undefined) {

View on GitHub (pinned to 0773b97458)

Solutions

  1. Make each top-level key map to an object: "filesystem": {"command": "npx", "args": [...]}
  2. If you intended the wrapper form, put servers under "mcpServers": { ... } as the only top-level key.
  3. Remove non-server keys (comments, pins, metadata) from the file.

Example fix

// before
{"filesystem": "npx -y @modelcontextprotocol/server-filesystem"}
// after
{"filesystem": {"command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]}}
Defensive patterns

Strategy: type-guard

Validate before calling

const cfg = JSON.parse(readFileSync(mcpPath, 'utf-8'));
for (const [name, val] of Object.entries(cfg)) {
  if (typeof val !== 'object' || val === null || Array.isArray(val)) {
    throw new Error(`server "${name}" must be an object`);
  }
}

Type guard

const isServerObject = (v: unknown): v is Record<string, unknown> =>
  typeof v === 'object' && v !== null && !Array.isArray(v);

Prevention

When it happens

Trigger: loadMcpConfig with a config where a key maps to a string/number/array/null, e.g. {"filesystem": "/usr/bin"} or {"servers": [...]} left un-nested after a bad mcpServers migration.

Common situations: Copying a Claude Desktop config that nests differently; pasting a command string as the server value; an editor collapsing a nested object; tools like version-pins written at the same level as servers.

Related errors


AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01). Data as JSON: /api/errors/f82a260ff63251d1. Report an issue: GitHub.