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
This commit is contained in:
Kyle Carberry
2026-03-24 09:22:45 -04:00
committed by GitHub
parent 631e4449bb
commit 13241a58ba
+14
View File
@@ -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(