From 13241a58ba4fab1b178306c125b97e0709add6c7 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Tue, 24 Mar 2026 09:22:45 -0400 Subject: [PATCH] fix(coderd/x/chatd/mcpclient): use dedicated HTTP transport per MCP connection (#23494) ## Problem `TestConnectAll_MultipleServers` flakes with: ``` net/http: HTTP/1.x transport connection broken: http: CloseIdleConnections called ``` Each MCP client connection implicitly uses `http.DefaultTransport`. When `httptest.Server.Close()` runs during parallel test cleanup, it calls `CloseIdleConnections` on `http.DefaultTransport`, breaking in-flight connections from other goroutines or parallel tests sharing that transport. ## Fix Clone the default transport for each MCP connection via `http.DefaultTransport.(*http.Transport).Clone()`, passed through `WithHTTPBasicClient` (StreamableHTTP) and `WithHTTPClient` (SSE). This scopes idle connection cleanup to a single MCP server so it cannot disrupt unrelated connections. Fixes coder/internal#1420 --- coderd/x/chatd/mcpclient/mcpclient.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/coderd/x/chatd/mcpclient/mcpclient.go b/coderd/x/chatd/mcpclient/mcpclient.go index cce959f2a7..cbb698e038 100644 --- a/coderd/x/chatd/mcpclient/mcpclient.go +++ b/coderd/x/chatd/mcpclient/mcpclient.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "net/http" "net/url" "strings" "sync" @@ -214,17 +215,30 @@ func createTransport( cfg database.MCPServerConfig, headers map[string]string, ) (transport.Interface, error) { + // Each connection gets its own HTTP client with a dedicated + // transport so that httptest.Server.Close() (which calls + // CloseIdleConnections on http.DefaultTransport) does not + // disrupt unrelated connections during parallel tests. + var httpClient *http.Client + if dt, ok := http.DefaultTransport.(*http.Transport); ok { + httpClient = &http.Client{Transport: dt.Clone()} + } else { + httpClient = &http.Client{} + } + switch cfg.Transport { case "sse": return transport.NewSSE( cfg.Url, transport.WithHeaders(headers), + transport.WithHTTPClient(httpClient), ) case "", "streamable_http": // Default to streamable HTTP, the newer transport. return transport.NewStreamableHTTP( cfg.Url, transport.WithHTTPHeaders(headers), + transport.WithHTTPBasicClient(httpClient), ) default: return nil, xerrors.Errorf(