Hmbown/CodeWhale · error · anyhow::Error

Source has no supported MCP server map

Error message

Source has no supported MCP server map

What it means

After parsing, checked_source requires the JSON to contain an object under "mcpServers" or "servers", or to be a top-level array of server entries. Valid JSON lacking any of these shapes is rejected — there is no server map to import.

Solutions

  1. Wrap server entries under a "mcpServers" (or "servers") object in the source file, or use a top-level array of entries
  2. Verify you selected the right source file — one that actually exports MCP servers under a supported key
  3. Rename the root key in the source to mcpServers if the exporting tool used a custom key

Example fix

// before (valid JSON, no server map)
{ "myTools": { "fs": { "command": "npx" } } }
// after
{ "mcpServers": { "fs": { "command": "npx" } } }
Defensive patterns

Strategy: validation

Validate before calling

fn has_supported_server_map(path: &std::path::Path) -> bool {
    std::fs::read_to_string(path).ok()
        .and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok())
        .map(|v| v.get("mcpServers").or_else(|| v.get("servers"))
            .is_some_and(|m| m.is_object()) || v.is_array())
        .unwrap_or(false)
}

Type guard

fn extract_server_map<'a>(v: &'a serde_json::Value) -> Option<&'a serde_json::Map<String, serde_json::Value>> {
    v.get("mcpServers").or_else(|| v.get("servers"))?.as_object()
}

Try / catch

match discover(&path) {
    Err(e) if e.to_string().contains("no supported MCP server map") => {
        eprintln!("source has no mcpServers/servers object and is not an array; fix the root shape");
    }
    r => r?,
}

Prevention

When it happens

Trigger: discover / discover_from_json_file on a file that is valid JSON but has no mcpServers/servers object and is not an array — e.g. {"foo": 1}, a settings file with a different schema, or a JSONL/other format.

Common situations: Pointing the importer at the wrong tool's config that uses a different root key; importing a partial fragment like a single server object {"command": ...} without the wrapping map; Claude/Codex-style configs whose root key was renamed.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/2afc07c96635e7ed. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/mcp/external_import.rs:136

    out
}

fn discover_from_json_file(path: &Path, kind: ExternalMcpSourceKind) -> Vec<ImportCandidate> {
    checked_source(path, kind).unwrap_or_default()
}

fn checked_source(
    path: &Path,
    kind: ExternalMcpSourceKind,
) -> anyhow::Result<Vec<ImportCandidate>> {
    super::validate_mcp_config_path(path)?;
    let Some(raw) = super::read_mcp_config_file(path)? else {
        return Ok(Vec::new());
    };
    let hash = hex_sha256(raw.as_bytes());
    let value: Value = serde_json::from_str(&raw)
        .map_err(|_| anyhow::anyhow!("Source is not valid JSON; contents omitted"))?;
    anyhow::ensure!(
        value
            .get("mcpServers")
            .or_else(|| value.get("servers"))
            .is_some_and(Value::is_object)
            || value.is_array(),
        "Source has no supported MCP server map"
    );
    let mut out = Vec::new();
    for (name, mut config) in extract_servers_map(&value) {
        anyhow::ensure!(
            super::mcp_name_is_command_safe(&name) && name.len() <= 128,
            "Source contains an unsupported server name"
        );
        if value.is_array()
            && let Some(map) = config.as_object_mut()
        {
            map.remove("name");
        }

View on GitHub (pinned to 73e0f67d83)