sipeed/picoclaw · error
local command %q is a directory
Error message
local command %q is a directory
What it means
Thrown by validateLocalCommandPath in picoclaw's MCP helpers. When a configured local (stdio) MCP server command looks like a path (absolute, ./, ../, or containing a path separator), picoclaw expands ~ and stats it; this error means the path exists but is a directory. picoclaw needs an executable file to spawn as the server process, so it rejects a folder before any connection attempt. Bare command names like npx are never stat-ed and cannot produce this error.
Source
Thrown at cmd/picoclaw/internal/mcp/helpers.go:322
}
return path
}
func validateLocalCommandPath(command string) error {
if !isLocalCommandPath(command) {
return nil
}
path := expandHomePath(command)
info, err := os.Stat(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("local command %q does not exist", command)
}
return fmt.Errorf("failed to stat local command %q: %w", command, err)
}
if info.IsDir() {
return fmt.Errorf("local command %q is a directory", command)
}
if runtime.GOOS != "windows" && info.Mode()&0o111 == 0 {
return fmt.Errorf("local command %q is not executable", command)
}
return nil
}
func defaultServerProbe(
ctx context.Context,
name string,
server config.MCPServerConfig,
workspacePath string,
) (probeResult, error) {
mgr := picomcp.NewManager()
defer func() { _ = mgr.Close() }()
server.Enabled = true
mcpCfg := config.MCPConfig{View on GitHub (pinned to 49183d7e8d)
Solutions
- Point command at the executable file itself (e.g. /usr/local/bin/weather-server, not /usr/local/bin) via picoclaw mcp remove + picoclaw mcp add or by editing the config
- Verify first: ls -ld <command> — a leading d in the mode column means it is a directory
- For package-run servers prefer wrapper commands such as npx -y <package> or uvx <package>, which need no path
- If the server is a script inside a folder, reference the script file plus its args, not the folder
Example fix
# before (config tools.mcp.servers.weather) command: /home/dev/.local/share/mcp-servers # after command: /home/dev/.local/share/mcp-servers/weather-server
Defensive patterns
Strategy: validation
Validate before calling
func validateCommandPath(command string) error {
if command == "" || !strings.ContainsRune(command, os.PathSeparator) {
return nil // bare command names are not stat-ed by picoclaw either
}
path := command
if strings.HasPrefix(path, "~") {
home, _ := os.UserHomeDir()
path = filepath.Join(home, strings.TrimPrefix(path, "~"))
}
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("command %s does not resolve: %w", command, err)
}
if info.IsDir() {
return fmt.Errorf("command %s is a directory; point it at the executable file", command)
}
return nil
}
// call before saving the server config or running picoclaw mcp add Type guard
func isCommandDirError(err error) bool {
return err != nil && strings.Contains(err.Error(), "local command") && strings.Contains(err.Error(), "is a directory")
} Try / catch
if err := runMCPAdd(cfg); err != nil {
if isCommandDirError(err) {
// prompt the user for the full executable path instead of the folder, then retry
}
return err
} Prevention
- Always configure command as a file path, never a folder; verify with ls -ld before saving
- Prefer absolute paths so behavior does not depend on the working directory
- For package-run servers use npx/uvx wrappers, which bypass path validation
When it happens
Trigger: An MCP server entry whose command is a directory, e.g. command: /usr/local/bin or command: ~/tools/mcp-server/; isLocalCommandPath returns true (path-like), os.Stat succeeds, and info.IsDir() is true.
Common situations: Pointing command at an install or bin directory (npm global bin dir, ~/.local/bin) instead of the binary inside it; pasting the folder path of a cloned/downloaded server; scaffolding a directory whose name collides with a relative command used from inside it.
Related errors
- local command %q does not exist
- local command %q is not executable
- ${label} must be a JSON object.
- ${label}.${key} must be a string.
- MCP discovery requires at least one search method (BM25 or r
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/6bf8f181a54677a9.
Report an issue: GitHub.