fix(mcp): make transport cross-compile on Windows
`internal/mcp/transport.go` used syscall.Setpgid and syscall.Kill unconditionally, both Unix-only. Split the platform bits into `transport_unix.go` (build tag `!windows`) keeping the existing process-group semantics, and `transport_windows.go` (build tag `windows`) falling back to `os.Process.Kill` (kills only the immediate process — full process-tree kill on Windows would need golang.org/x/sys/windows + job objects, deferred). Caught by `goreleaser release --snapshot` cross-compiling for windows/amd64 and windows/arm64.
This commit is contained in:
@@ -12,7 +12,6 @@ import (
|
||||
"os/exec"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -49,8 +48,9 @@ func NewTransport(command string, args []string, env map[string]string, logger *
|
||||
func (t *Transport) Start(ctx context.Context) error {
|
||||
t.cmd = exec.CommandContext(ctx, t.command, t.args...)
|
||||
t.cmd.Env = t.buildEnv()
|
||||
// Create a new process group so Close can kill the entire tree.
|
||||
t.cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
// Platform-specific: on Unix, isolate the child in a new process group
|
||||
// so Close can kill the entire tree.
|
||||
setProcessGroup(t.cmd)
|
||||
t.stderr = limitedWriter{max: maxStderrCapture}
|
||||
t.cmd.Stderr = &t.stderr
|
||||
|
||||
@@ -146,10 +146,10 @@ func (t *Transport) Close() error {
|
||||
case <-time.After(2 * time.Second):
|
||||
}
|
||||
|
||||
// Graceful didn't work — kill the entire process group.
|
||||
t.logger.Warn("mcp: server did not exit, killing process group", "command", t.command)
|
||||
if err := syscall.Kill(-t.cmd.Process.Pid, syscall.SIGKILL); err != nil {
|
||||
t.logger.Warn("mcp: SIGKILL to process group failed",
|
||||
// Graceful didn't work — kill the process (Unix: whole group; Windows: just the process).
|
||||
t.logger.Warn("mcp: server did not exit, killing", "command", t.command)
|
||||
if err := killProcessTree(t.cmd.Process); err != nil {
|
||||
t.logger.Warn("mcp: kill failed",
|
||||
"command", t.command, "pid", t.cmd.Process.Pid, "error", err)
|
||||
}
|
||||
return <-done
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
//go:build !windows
|
||||
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func setProcessGroup(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
}
|
||||
|
||||
func killProcessTree(p *os.Process) error {
|
||||
// Negative PID targets the process group created via Setpgid.
|
||||
return syscall.Kill(-p.Pid, syscall.SIGKILL)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//go:build windows
|
||||
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// setProcessGroup is a no-op on Windows; we rely on os.Process.Kill in Close.
|
||||
// Pulling in golang.org/x/sys/windows + job objects to kill the full process
|
||||
// tree would be the upgrade path if MCP servers ever spawn children we need
|
||||
// to reap.
|
||||
func setProcessGroup(cmd *exec.Cmd) {}
|
||||
|
||||
func killProcessTree(p *os.Process) error {
|
||||
return p.Kill()
|
||||
}
|
||||
Reference in New Issue
Block a user