BigPizzaV3/CodexPlusPlus · error · anyhow::Error

JSON 顶层必须是对象

Error message

JSON 顶层必须是对象

What it means

After JSON syntax validation, parse_mcp_servers_json requires the top-level JSON value to be an object, because all supported wrapper shapes ({mcpServers:...}, {servers:...}, bare {id:cfg}, single server object) are objects. Arrays, strings, numbers or booleans at top level are rejected with this message.

Solutions

  1. Wrap the array in an object: {"mcpServers": {...entries...}}.
  2. If you have a list of server configs, key each one by its id inside an object before importing.
  3. Validate with JSON.parse and check typeof parsed === 'object' && !Array.isArray(parsed) before calling.

Example fix

// before
parse_mcp_servers_json("[{\"command\":\"npx\"}]")
// after
parse_mcp_servers_json("{\"mcpServers\":{\"my-server\":{\"command\":\"npx\"}}}")
Defensive patterns

Strategy: validation

Validate before calling

function isJsonObject(text) {
  try {
    const v = JSON.parse(text);
    return typeof v === 'object' && v !== null && !Array.isArray(v);
  } catch { return false; }
}

Type guard

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

Try / catch

match parse_mcp_servers_json(input) {
    Ok(import) => apply(import),
    Err(e) if e.to_string().contains("顶层必须是对象") => ui.hint("JSON 顶层需为对象,如 {\"mcpServers\":{...}}"),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Passing input like "[{...}]" (array of servers), "\"text\"", "123", or "true" to parse_mcp_servers_json.

Common situations: User pastes a JSON array exported by another tool instead of the object form; a script serializes only the servers list; accidental double-selection pastes a fragment starting with '['.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/73e9b3471c81b87e. Report an issue: GitHub.

Appendix: source

Thrown at crates/codex-plus-core/src/mcp_config.rs:265

    Ok(if text.trim().is_empty() {
        String::new()
    } else {
        ensure_trailing_newline(text)
    })
}

/// 解析社区文档里常见的 MCP JSON。
///
/// 接受四种外层形状:`{"mcpServers":{…}}`(Claude 标准)、`{"servers":{…}}`
/// (VS Code)、裸的 `{id: cfg}`,以及单个 server 对象(此时 id 留空由调用方填)。
pub fn parse_mcp_servers_json(json: &str) -> anyhow::Result<McpJsonImport> {
    let trimmed = json.trim();
    if trimmed.is_empty() {
        anyhow::bail!("请先粘贴 MCP 配置 JSON");
    }
    let root: Value = serde_json::from_str(trimmed).context("不是合法的 JSON")?;
    let Value::Object(root) = root else {
        anyhow::bail!("JSON 顶层必须是对象");
    };

    let servers = if let Some(Value::Object(map)) = root.get("mcpServers") {
        map.clone()
    } else if let Some(Value::Object(map)) = root.get("servers") {
        map.clone()
    } else if looks_like_single_server(&root) {
        // 单个 server 对象,没有 id,用占位 id 让用户在界面上改
        let mut map = Map::new();
        map.insert("mcp-server".to_string(), Value::Object(root));
        map
    } else {
        root
    };

    if servers.is_empty() {
        anyhow::bail!("JSON 里没有找到任何 MCP 服务器");
    }

View on GitHub (pinned to b1ed92e5e4)