Hmbown/CodeWhale · error · anyhow::Error

MCP server '{server_name}' has no command configured

Error message

MCP server '{server_name}' has no command configured

What it means

StdioMcpClient::spawn() refuses to start an MCP server whose McpServerConfig.command is empty or only whitespace. The command is the executable that will be spawned over stdio JSON-RPC, so an empty value would only produce a confusing OS-level spawn failure later; the crate fails fast with the server name attached.

Source

Thrown at crates/mcp/src/stdio_client.rs:94

impl ChildProcessMcpClient {
    /// Spawn `config.command` with `config.args`/`config.env` and complete the
    /// MCP handshake.
    ///
    /// Returns `Err` — never a degraded-but-usable client — when the command
    /// cannot be executed, exits immediately, or does not answer `initialize`
    /// within `HANDSHAKE_TIMEOUT`.
    pub fn spawn(config: &McpServerConfig) -> Result<Self> {
        Self::spawn_with_timeouts(config, HANDSHAKE_TIMEOUT, REQUEST_TIMEOUT)
    }

    fn spawn_with_timeouts(
        config: &McpServerConfig,
        handshake_timeout: Duration,
        request_timeout: Duration,
    ) -> Result<Self> {
        let server_name = config.name.clone();
        if config.command.trim().is_empty() {
            bail!("MCP server '{server_name}' has no command configured");
        }

        let mut command = Command::new(&config.command);
        command
            .args(&config.args)
            .envs(&config.env)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            // The child's diagnostics belong on our stderr: stdout is the
            // JSON-RPC channel and must not be polluted, and swallowing the
            // child's stderr is how a misconfigured server becomes a silent
            // one.
            .stderr(Stdio::inherit());

        let mut child = command.spawn().with_context(|| {
            format!(
                "MCP server '{server_name}': failed to spawn command '{}'",
                config.command

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Set command to the actual executable path in the server's config entry
  2. Validate configs before spawn: reject entries whose command trims to empty at load time, listing the offending server name
  3. If the command comes from an env var, verify the var is set in the environment that spawns the process
  4. Check for key typos against the McpServerConfig field names so the command is not silently defaulted

Example fix

// before
let config = McpServerConfig {
    name: "github".into(),
    command: std::env::var("MCP_GITHUB_BIN").unwrap_or_default(), // '' when unset
    ..Default::default()
};
let client = StdioMcpClient::spawn(&config)?;

// after
let command = std::env::var("MCP_GITHUB_BIN")
    .context("MCP_GITHUB_BIN must point at the github MCP server executable")?;
let config = McpServerConfig {
    name: "github".into(),
    command,
    ..Default::default()
};
let client = StdioMcpClient::spawn(&config)?;
Defensive patterns

Strategy: validation

Validate before calling

for server in &config.servers {
    if server.command.trim().is_empty() {
        bail!("server '{}' has no command configured", server.name);
    }
}

Prevention

When it happens

Trigger: A config entry with an empty "command" string; a JSON/TOML key typo (e.g. "cmd" or "executable") that deserializes command to its empty default; a command built from an env var (e.g. ${MCP_SERVER_BIN}) that is unset, leaving an empty string; a trimmed string of spaces.

Common situations: Hand-edited MCP config files; CI environments missing the env var that supplies the command; config schemas that accept unknown keys silently instead of rejecting them.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/4b2b5b271e6c5149. Report an issue: GitHub.