mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: isolate MCP HTTP transports from DefaultTransport in tests (#25821)
Use testing.Testing() inside createTransport to automatically clone http.DefaultTransport when running in tests. In production, DefaultTransport is used as-is (efficient connection pooling). This fixes the CloseIdleConnections flake class: httptest.Server.Close() calls http.DefaultTransport.CloseIdleConnections(), which disrupts any MCP client sharing that transport. The testing.Testing() check means every MCP transport created during tests gets isolation automatically, with no caller changes needed. Closes coder/internal#1016 Closes PLAT-291
This commit is contained in:
@@ -285,31 +285,24 @@ 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{}
|
||||
}
|
||||
httpClient := mcpHTTPClient()
|
||||
|
||||
switch cfg.Transport {
|
||||
case "sse":
|
||||
return transport.NewSSE(
|
||||
cfg.Url,
|
||||
transport.WithHeaders(headers),
|
||||
transport.WithHTTPClient(httpClient),
|
||||
)
|
||||
var opts []transport.ClientOption
|
||||
opts = append(opts, transport.WithHeaders(headers))
|
||||
if httpClient != nil {
|
||||
opts = append(opts, transport.WithHTTPClient(httpClient))
|
||||
}
|
||||
return transport.NewSSE(cfg.Url, opts...)
|
||||
case "", "streamable_http":
|
||||
// Default to streamable HTTP, the newer transport.
|
||||
return transport.NewStreamableHTTP(
|
||||
cfg.Url,
|
||||
transport.WithHTTPHeaders(headers),
|
||||
transport.WithHTTPBasicClient(httpClient),
|
||||
)
|
||||
var opts []transport.StreamableHTTPCOption
|
||||
opts = append(opts, transport.WithHTTPHeaders(headers))
|
||||
if httpClient != nil {
|
||||
opts = append(opts, transport.WithHTTPBasicClient(httpClient))
|
||||
}
|
||||
return transport.NewStreamableHTTP(cfg.Url, opts...)
|
||||
default:
|
||||
return nil, xerrors.Errorf(
|
||||
"unsupported transport %q", cfg.Transport,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package mcpclient
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// mcpHTTPClient returns an isolated *http.Client when running
|
||||
// inside tests, or nil for production. During tests,
|
||||
// httptest.Server.Close() calls
|
||||
// http.DefaultTransport.CloseIdleConnections(), which disrupts
|
||||
// any MCP client sharing that transport. When DefaultTransport
|
||||
// is a *http.Transport it is cloned; otherwise a minimal
|
||||
// transport with ProxyFromEnvironment is created as a fallback.
|
||||
func mcpHTTPClient() *http.Client {
|
||||
if !testing.Testing() {
|
||||
return nil
|
||||
}
|
||||
if dt, ok := http.DefaultTransport.(*http.Transport); ok {
|
||||
return &http.Client{Transport: dt.Clone()}
|
||||
}
|
||||
return &http.Client{Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}}
|
||||
}
|
||||
Reference in New Issue
Block a user