Hmbown/CodeWhale · error · anyhow::Error

MCP server '{name}' config must have either 'command' or 'ur

Error message

MCP server '{name}' config must have either 'command' or 'url'

What it means

McpServerConfig must describe either a local subprocess (command, with optional args/env/cwd) or a remote endpoint (url). connect_with_policy tries url first, then command, and when both are absent there is nothing to build a transport from (crates/tui/src/mcp.rs:1639-1648). add_server_config pre-rejects the same shape at config-write time with its own message.

Source

Thrown at crates/tui/src/mcp.rs:1647

                if let Err(e) = http.try_establish_session().await {
                    tracing::debug!(
                        target: "mcp",
                        server = %name,
                        error = %e,
                        "session-establishment GET skipped; proceeding with POST initialize"
                    );
                }
                Box::new(http)
            }
        } else if let Some(command) = &config.command {
            Box::new(StdioTransport::spawn(
                &name,
                command,
                &config,
                cancel_token.clone(),
            )?)
        } else {
            anyhow::bail!("MCP server '{name}' config must have either 'command' or 'url'");
        };
        // Revalidate after transport construction as well: remote setup may
        // await DNS/TLS/SSE preflight, and a concurrent process can revoke the
        // receipt during that interval. Initialization and catalog discovery
        // never start under a stale generation.
        if let Some(source) = config.reviewed_plugin.as_ref() {
            source.validate_before_use(&name, "initialize")?;
        }
        let authority_watch = authority_watch.map(PendingAuthorityWatch::disarm);

        let mut conn = Self {
            name: name.clone(),
            transport,
            tools: Vec::new(),
            resources: Vec::new(),
            resource_templates: Vec::new(),
            prompts: Vec::new(),
            request_id: AtomicU64::new(1),

View on GitHub (pinned to 8880682c63)

Solutions

  1. Add "command": "..." (plus args) for a stdio server, or "url": "https://..." for a remote server, to the entry.
  2. Check key spelling exactly - command and url; unrecognized alternatives silently deserialize to None.
  3. Validate the file shape against a freshly generated template (mcp init/config template) after editing.

Example fix

// before
"fetch": { "args": ["-y", "mcp-server-fetch"] }
// after
"fetch": { "command": "npx", "args": ["-y", "mcp-server-fetch"] }
Defensive patterns

Strategy: validation

Validate before calling

fn server_config_has_transport(cfg: &McpServerConfig) -> bool {
    cfg.command.is_some() || cfg.url.is_some()
}

// Run over the parsed config before handing it to the connection manager:
for (name, cfg) in &config.servers {
    assert!(server_config_has_transport(cfg), "server '{name}' needs 'command' or 'url'");
}

Prevention

When it happens

Trigger: A servers entry containing only args/env/headers with no command or url; serde defaults leaving both None after a typo'd key like "cmd" or "executable" (unknown keys are ignored).

Common situations: Hand-edited MCP config with key typos; config ported from another client's schema where the fields nest differently; partial edits that deleted the command line.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/1eeefce162ddcf19. Report an issue: GitHub.