charmbracelet/crush · error
invalid mcp command: %w
Error message
invalid mcp command: %w
What it means
createTransport resolves the MCP stdio 'command' field through the variable resolver (supporting $VAR and $(cmd) substitution). If the resolver fails, the error is wrapped as 'invalid mcp command'. This happens before any process is spawned.
Source
Thrown at internal/agent/tools/mcp/init.go:1040
if err2 := stdioCheck(ct.Command); err2 != nil {
err = errors.Join(err, err2)
}
return err
}
func maybeTimeoutErr(err error, timeout time.Duration) error {
if errors.Is(err, context.Canceled) {
return fmt.Errorf("timed out after %s", timeout)
}
return err
}
func createTransport(ctx context.Context, cfg *config.ConfigStore, name string, m config.MCPConfig, resolver config.VariableResolver) (mcp.Transport, *mcpoauth.Handler, error) {
switch m.Type {
case config.MCPStdio:
command, err := resolver.ResolveValue(m.Command)
if err != nil {
return nil, nil, fmt.Errorf("invalid mcp command: %w", err)
}
if strings.TrimSpace(command) == "" {
return nil, nil, fmt.Errorf("mcp stdio config requires a non-empty 'command' field")
}
args, err := m.ResolvedArgs(resolver)
if err != nil {
return nil, nil, err
}
envs, err := m.ResolvedEnv(resolver)
if err != nil {
return nil, nil, err
}
cmd := exec.CommandContext(ctx, home.Long(command), args...)
cmd.Env = append(os.Environ(), envs...)
// Run the child in its own process group and kill the whole group when
// the session context is cancelled. A stdio server often spawns its own
// children (signal-mcp launches signal-cli); os/exec's default
// cancellation kills only the direct child, orphaning the rest withView on GitHub (pinned to 7944b8e522)
Solutions
- Check the resolver error wrapped in the message for the exact failing variable/command
- Ensure every $VAR in the command is set in the environment where crush runs
- Verify $(cmd) subcommands exit 0 and the binaries exist on PATH
- Simplify to a plain absolute path if dynamic resolution is unnecessary
Example fix
// before (crushrc) mcp myserver command '$(which missing-tool) serve' // after mcp myserver command '/usr/local/bin/myserver' args 'serve'
Defensive patterns
Strategy: validation
Validate before calling
cmd := os.ExpandEnv(m.Command)
if strings.TrimSpace(cmd) == "" {
return errors.New("mcp stdio command resolves to empty; check referenced env vars")
}
if _, err := exec.LookPath(cmd); err != nil {
return fmt.Errorf("command %q not found on PATH", cmd)
} Prevention
- Use absolute paths instead of $(which ...) substitutions
- Verify all $VARs in the command are exported in the runtime environment
- Test $(cmd) substitutions manually before putting them in config
- Prefer plain static commands for MCP stdio servers
When it happens
Trigger: The command string in an MCP stdio config contains an unresolvable $VAR, a failing $(cmd) substitution, or the resolver otherwise errors on the command value.
Common situations: Referencing an env var that is not set (e.g. $HOME-based paths in service contexts); command using $(which tool) where the tool is not installed; quoting mistakes in the config.
Related errors
- oauth_client_id: %w
- arg %d: %w
- url: %w
- header %s: %w
- mcp stdio config requires a non-empty 'command' field
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/277fb4ec473d9bd2.
Report an issue: GitHub.