diff --git a/gopls/internal/cmd/mcp_test.go b/gopls/internal/cmd/mcp_test.go
index b19df82..3576d42 100644
--- a/gopls/internal/cmd/mcp_test.go
+++ b/gopls/internal/cmd/mcp_test.go
@@ -10,13 +10,11 @@
"context"
"encoding/json"
"fmt"
- "net"
"os"
"os/exec"
"path/filepath"
"runtime"
"slices"
- "strconv"
"strings"
"testing"
"time"
@@ -205,9 +203,7 @@
func MyFun() {}
`)
- port := strconv.Itoa(getRandomPort())
- addr := "localhost:" + port
- goplsCmd := exec.Command(os.Args[0], "-v", "mcp", "-listen="+addr)
+ goplsCmd := exec.Command(os.Args[0], "-v", "mcp", "-listen=localhost:0")
goplsCmd.Env = append(os.Environ(), "ENTRYPOINT=goplsMain")
goplsCmd.Dir = tree
goplsCmd.Stdout = os.Stderr
@@ -232,19 +228,19 @@
goplsCmd.Wait()
}()
- // Wait for the MCP server to start listening. The referenced log occurs
- // after the connection is opened via net.Listen and the HTTP handlers are
- // set up.
- ready := make(chan bool, 1)
+ // Wait for the MCP server to start listening and send the address through
+ // the channel. The referenced log occurs after the connection is opened via
+ // net.Listen and the HTTP handlers are set up.
+ ready := make(chan string, 1)
go func() {
defer close(ready)
- // Copy from the pipe to stderr, keeping an eye out for the "mcp http
- // server listening" string.
+ // Copy from the pipe to stderr, keeping an eye out for the "Gopls MCP
+ // server: listening on <addr>" string.
scan := bufio.NewScanner(stderr)
for scan.Scan() {
line := scan.Text()
- if strings.Contains(line, "mcp http server listening") {
- ready <- true
+ if _, after, ok := strings.Cut(line, "Gopls MCP server: listening on "); ok {
+ ready <- strings.TrimSpace(after)
}
fmt.Fprintln(os.Stderr, line)
}
@@ -253,9 +249,10 @@
}
}()
+ var addr string
select {
- case ok := <-ready:
- if !ok {
+ case addr = <-ready:
+ if addr == "" {
t.Fatalf("gopls mcp server exited without starting")
}
case <-time.After(30 * time.Minute):
@@ -430,18 +427,6 @@
return buf.String()
}
-// getRandomPort returns the number of a random available port. Inherently racy:
-// nothing stops another process from listening on it - but this should be fine
-// for testing purposes.
-func getRandomPort() int {
- listener, err := net.Listen("tcp", "localhost:0")
- if err != nil {
- panic(err)
- }
- defer listener.Close()
- return listener.Addr().(*net.TCPAddr).Port
-}
-
// supportsFsnotify returns true if fsnotify supports the os.
func supportsFsnotify(os string) bool {
return os == "darwin" || os == "linux" || os == "windows"
diff --git a/gopls/internal/mcp/mcp.go b/gopls/internal/mcp/mcp.go
index 988e206..b2f017b 100644
--- a/gopls/internal/mcp/mcp.go
+++ b/gopls/internal/mcp/mcp.go
@@ -56,18 +56,22 @@
if strings.HasPrefix(address, ":") {
return fmt.Errorf("address %s implicitly binds all network interfaces; please use an explicit host such as 0.0.0.0 (all interfaces) or localhost (safer)", address)
}
- log.Printf("Gopls MCP server: starting up on http")
+
listener, err := net.Listen("tcp", address)
if err != nil {
return err
}
defer listener.Close()
- // TODO(hxjiang): expose the MCP server address to the LSP client.
- if isDaemon {
- log.Printf("Gopls MCP daemon: listening on address %s...", listener.Addr())
+ {
+ kind := "server"
+ if isDaemon {
+ kind = "daemon"
+ }
+
+ log.Printf("Gopls MCP %s: listening on %s", kind, listener.Addr())
+ defer log.Printf("Gopls MCP %s: exiting", kind)
}
- defer log.Printf("Gopls MCP server: exiting")
svr := http.Server{
Handler: HTTPHandler(sessions, isDaemon, rootsHandler),
@@ -81,7 +85,6 @@
<-ctx.Done()
svr.Close() // ignore error
}()
- log.Printf("mcp http server listening")
return svr.Serve(listener)
}