From 84c73bf32e6d7df01bad2f9c67c9622c211cf849 Mon Sep 17 00:00:00 2001 From: Juan Antonio Osorio Date: Mon, 27 Jul 2026 17:48:13 +0000 Subject: [PATCH] Bind the vMCP test server's port directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The vmcp integration test helper picked a port by binding 127.0.0.1:0, reading the port back, and closing the listener — then handed that port to the vMCP server to bind later. Closing the probe listener releases the port, so between the close and the server's own net.Listen any of the ten t.Parallel() tests in this package (each also starting httptest backends) could be handed the same port by the OS. The result was an intermittent "bind: address already in use", which most recently failed CI on a PR whose diff was unrelated. Set ServerConfig.Port to 0 instead and read the bound address back from Address() after Ready(), which the helper already awaits. The server's listener is created and stored before the ready channel closes, so Address() reports the OS-assigned port to every caller. This removes the window rather than narrowing it: the port is never unbound between being chosen and being served. Closes #6034 --- test/integration/vmcp/helpers/vmcp_server.go | 26 +++++++------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/test/integration/vmcp/helpers/vmcp_server.go b/test/integration/vmcp/helpers/vmcp_server.go index 7805e1047a..3416b7c65c 100644 --- a/test/integration/vmcp/helpers/vmcp_server.go +++ b/test/integration/vmcp/helpers/vmcp_server.go @@ -5,7 +5,6 @@ package helpers import ( "context" - "net" "testing" "time" @@ -122,25 +121,18 @@ func WithSessionTTL(ttl time.Duration) VMCPServerOption { } } -// getFreePort returns an available TCP port on localhost. -func getFreePort(tb testing.TB) int { - tb.Helper() - - listener, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(tb, err, "failed to get free port") - defer func() { _ = listener.Close() }() - - addr, ok := listener.Addr().(*net.TCPAddr) - if !ok { - tb.Fatalf("failed to get TCP address from listener") - } - return addr.Port -} - // NewVMCPServer creates a vMCP server for testing using the Serve path (core.New + // Serve). The server is automatically started and ready when this function returns. // Use functional options to customize behavior. // +// The server binds an OS-assigned port (ServerConfig.Port is 0) and holds that +// listener for its lifetime, so callers MUST read the address back from the +// returned server's Address() rather than assuming a port. Address() reports the +// bound port only once the server is ready, which this function waits for before +// returning. Do not pre-select a port here by binding and closing a probe +// listener: that releases the port, and any of the parallel tests in this suite +// can claim it before the server rebinds (#6034). +// // The Serve path is used (rather than the legacy server.New path) so that // integration tests exercise the production code path that routes tool/resource // calls through core.VMCP. This is required for testing per-request @@ -197,7 +189,7 @@ func NewVMCPServer( Name: "test-vmcp", Version: "1.0.0", Host: "127.0.0.1", - Port: getFreePort(tb), + Port: 0, // OS-assigned; see the note on NewVMCPServer EndpointPath: "/mcp", SessionTTL: 30 * time.Minute, AuthMiddleware: auth.AnonymousMiddleware,