fix(mcp): expand home paths for local stdio server commands

This commit is contained in:
afjcjsbx 2026-04-24 07:47:26 +02:00
parent 2da05c2ad3
commit f4dbac0dcf
4 changed files with 60 additions and 1 deletions

View file

@ -223,6 +223,9 @@ func buildServerConfig(target string, args []string, opts addOptions) (config.MC
if err := validateLocalCommandPath(target); err != nil {
return config.MCPServerConfig{}, err
}
if isLocalCommandPath(command) {
command = expandHomePath(command)
}
server.Command = command
server.Args = commandArgs

View file

@ -152,6 +152,28 @@ func TestMCPAddRejectsNonExecutableLocalCommand(t *testing.T) {
assert.Contains(t, err.Error(), "not executable")
}
func TestMCPAddExpandsHomeInSavedLocalCommand(t *testing.T) {
configPath := setupMCPConfigEnv(t)
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
t.Setenv("USERPROFILE", homeDir)
localCmd := filepath.Join(homeDir, "bin", "my-mcp")
require.NoError(t, os.MkdirAll(filepath.Dir(localCmd), 0o755))
require.NoError(t, os.WriteFile(localCmd, []byte("#!/bin/sh\nexit 0\n"), 0o755))
tildeCmd := "~" + string(os.PathSeparator) + filepath.Join("bin", "my-mcp")
cmd := NewMCPCommand()
_, err := executeCommand(cmd, []string{"add", "local-home", tildeCmd}, "")
require.NoError(t, err)
cfg := readMCPConfig(t, configPath)
server := cfg.Tools.MCP.Servers["local-home"]
assert.Equal(t, localCmd, server.Command)
}
func TestMCPAddShowsClearErrorForRemoteURLWithoutTransport(t *testing.T) {
setupMCPConfigEnv(t)

View file

@ -25,6 +25,24 @@ type headerTransport struct {
headers map[string]string
}
func expandHomeCommandPath(command string) string {
if command == "" || command[0] != '~' {
return command
}
home, err := os.UserHomeDir()
if err != nil {
return command
}
if command == "~" {
return home
}
if strings.HasPrefix(command, "~/") || strings.HasPrefix(command, "~\\") {
return filepath.Join(home, command[2:])
}
return command
}
func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// Clone the request to avoid modifying the original
req = req.Clone(req.Context())
@ -324,7 +342,7 @@ func (m *Manager) ConnectServer(
"command": cfg.Command,
})
// Create command with context
cmd := exec.CommandContext(ctx, cfg.Command, cfg.Args...)
cmd := exec.CommandContext(ctx, expandHomeCommandPath(cfg.Command), cfg.Args...)
// Build environment variables with proper override semantics
// Use a map to ensure config variables override file variables

View file

@ -136,6 +136,22 @@ func TestLoadEnvFileNotFound(t *testing.T) {
}
}
func TestExpandHomeCommandPath(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
t.Setenv("USERPROFILE", homeDir)
want := filepath.Join(homeDir, "bin", "my-mcp")
got := expandHomeCommandPath("~" + string(os.PathSeparator) + filepath.Join("bin", "my-mcp"))
if got != want {
t.Fatalf("expandHomeCommandPath() = %q, want %q", got, want)
}
if got := expandHomeCommandPath("npx"); got != "npx" {
t.Fatalf("expandHomeCommandPath() should leave bare commands unchanged, got %q", got)
}
}
func TestEnvFilePriority(t *testing.T) {
// Create a temporary .env file
tmpDir := t.TempDir()