From 3acef0c31c3a7a5e6c1680149dce34d6b4a9a883 Mon Sep 17 00:00:00 2001 From: Nathan Cochran Date: Tue, 6 Oct 2026 17:11:56 -0400 Subject: [PATCH] Remove port-finding logic for MCP server's HTTP transport --- docs/cli/tiger_mcp_start_http.md | 2 +- internal/cmd/mcp_start_http.go | 36 +++++------------------------ internal/cmd/mcp_start_http_test.go | 28 ++++++---------------- internal/cmd/mcp_test.go | 14 ----------- 4 files changed, 14 insertions(+), 66 deletions(-) diff --git a/docs/cli/tiger_mcp_start_http.md b/docs/cli/tiger_mcp_start_http.md index 5344b53c..e1b7b556 100644 --- a/docs/cli/tiger_mcp_start_http.md +++ b/docs/cli/tiger_mcp_start_http.md @@ -6,7 +6,7 @@ Start MCP server with HTTP transport Start the MCP server using HTTP transport. -The server will automatically find an available port if the specified port is busy. +The server listens on the given port and fails if that port is already in use. ``` tiger mcp start http [flags] diff --git a/internal/cmd/mcp_start_http.go b/internal/cmd/mcp_start_http.go index d76c51fc..82fab138 100644 --- a/internal/cmd/mcp_start_http.go +++ b/internal/cmd/mcp_start_http.go @@ -7,6 +7,7 @@ import ( "log/slog" "net" "net/http" + "strconv" "github.com/spf13/cobra" @@ -24,7 +25,7 @@ func buildMCPHTTPCmd(app *common.App) *cobra.Command { Short: "Start MCP server with HTTP transport", Long: `Start the MCP server using HTTP transport. -The server will automatically find an available port if the specified port is busy.`, +The server listens on the given port and fails if that port is already in use.`, Example: ` # Start HTTP server on default port 8080 tiger mcp start http @@ -64,27 +65,14 @@ func startHTTPServer(cmd *cobra.Command, app *common.App, host string, port int) } defer server.Close() - // Find available port and get the listener - listener, actualPort, err := getListener(host, port) + address := net.JoinHostPort(host, strconv.Itoa(port)) + listener, err := net.Listen("tcp", address) if err != nil { - logger.Error("Failed to get listener", - slog.String("host", host), - slog.Int("port", port), - slog.Any("error", err), - ) - return fmt.Errorf("failed to get listener: %w", err) + logger.Error("Failed to listen", slog.Any("error", err)) + return fmt.Errorf("failed to listen: %w", err) } defer listener.Close() - if actualPort != port { - logger.Info("Specified port was busy, using alternative port", - slog.Int("requested_port", port), - slog.Int("actual_port", actualPort), - ) - } - - address := fmt.Sprintf("%s:%d", host, actualPort) - // Create HTTP server httpServer := &http.Server{ Handler: server.HTTPHandler(), @@ -128,15 +116,3 @@ func startHTTPServer(cmd *cobra.Command, app *common.App, host string, port int) } return nil } - -// getListener finds an available port starting from the specified port and returns the listener -func getListener(host string, startPort int) (net.Listener, int, error) { - for port := startPort; port < startPort+100; port++ { - address := fmt.Sprintf("%s:%d", host, port) - listener, err := net.Listen("tcp", address) - if err == nil { - return listener, port, nil - } - } - return nil, 0, fmt.Errorf("no available port found in range %d-%d", startPort, startPort+99) -} diff --git a/internal/cmd/mcp_start_http_test.go b/internal/cmd/mcp_start_http_test.go index d3404035..7b6a9756 100644 --- a/internal/cmd/mcp_start_http_test.go +++ b/internal/cmd/mcp_start_http_test.go @@ -12,7 +12,7 @@ import ( ) // freePort returns a port nothing is listening on, and (when hold is true) -// keeps it occupied for the rest of the test so the command has to fall back. +// keeps it occupied for the rest of the test so binding it fails. func freePort(t *testing.T, hold bool) int { t.Helper() listener, err := net.Listen("tcp", "localhost:0") @@ -56,27 +56,13 @@ INFO Tiger MCP server started address=localhost:%d `, port) + shutdown), }, { - // The port scan walks forward from the requested one. Which port - // it lands on depends on what else is running, so only the - // fallback itself is asserted. - name: "falls back to another port when the requested one is busy", - args: []string{"mcp", "start", "http", "--port", fmt.Sprint(busyPort)}, - opts: append(noDocsProxy(nil), withContext(cancelled)), - wantStderr: matchLogPort(fmt.Sprintf(`INFO Docs MCP proxy is disabled -INFO Specified port was busy, using alternative port requested_port=%d actual_port= -INFO Tiger MCP server started address=localhost: -`, busyPort) + shutdown), - }, - { - // 192.0.2.1 is TEST-NET-1: it resolves to nothing local, so - // every port in the scan range fails to bind. - name: "reports when no port in range is available", - args: []string{"mcp", "start", "http", "--host", "192.0.2.1"}, + name: "fails when the requested port is busy", + args: []string{"mcp", "start", "http", "--port", fmt.Sprint(busyPort)}, opts: append(noDocsProxy(nil), withContext(cancelled)), - wantErr: "failed to get listener: no available port found in range 8080-8179", - wantStderr: matchLog(`INFO Docs MCP proxy is disabled -ERROR Failed to get listener host=192.0.2.1 port=8080 error="no available port found in range 8080-8179" -`), + wantErr: fmt.Sprintf("failed to listen: listen tcp 127.0.0.1:%d: bind: address already in use", busyPort), + wantStderr: matchLog(fmt.Sprintf(`INFO Docs MCP proxy is disabled +ERROR Failed to listen error="listen tcp 127.0.0.1:%d: bind: address already in use" +`, busyPort)), }, { name: "rejects positional args", diff --git a/internal/cmd/mcp_test.go b/internal/cmd/mcp_test.go index 8a6bddc2..652d3cde 100644 --- a/internal/cmd/mcp_test.go +++ b/internal/cmd/mcp_test.go @@ -3,7 +3,6 @@ package cmd import ( "maps" "regexp" - "strings" "testing" ) @@ -31,16 +30,3 @@ func matchLog(want string) matcher { assertOutput(t, logTimestamp.ReplaceAllString(got, ""), want) }) } - -// matchLogPort is matchLog for output naming a port the OS picked rather than -// one the test chose. The want text is matched literally except for "", -// which stands in for any port number. -func matchLogPort(want string) matcher { - re := regexp.MustCompile("^" + strings.ReplaceAll(regexp.QuoteMeta(want), "", `\d+`) + "$") - return matchFunc(func(t *testing.T, got string) { - t.Helper() - if stripped := logTimestamp.ReplaceAllString(got, ""); !re.MatchString(stripped) { - t.Errorf("log = %q, want %q ( matching any port)", stripped, want) - } - }) -}