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.commandView on GitHub (pinned to 0c42157ee5)
Solutions
- Set command to the actual executable path in the server's config entry
- Validate configs before spawn: reject entries whose command trims to empty at load time, listing the offending server name
- If the command comes from an env var, verify the var is set in the environment that spawns the process
- 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
- Reject empty commands at config load with the server name, before any spawn
- Fail deserialization on unknown keys so typos like 'cmd' surface immediately
- Resolve env-var-backed commands explicitly and error when the variable is unset
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
- MCP server '{server}': process closed stdin before answering
- OAuth login is only supported for URL-based MCP servers
- OAuth logout is only supported for URL-based MCP servers
- app-server auth token cannot be empty
- MCP server '{server}': {method} timed out after {timeout:?}
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/4b2b5b271e6c5149.
Report an issue: GitHub.