From 01033d683bff1d24f97e9af5b4adcc3d3dd8d05c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:14:51 +0000 Subject: [PATCH 1/2] Initial plan From 15bb898264b6099e86e10d56a5b93a0fec2e5465 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:20:08 +0000 Subject: [PATCH 2/2] Fix isHTTPConnectionError with errors.As and update MCPProtocolVersion to 2025-11-25 Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- internal/mcp/connection_test.go | 26 +++++++++++++++++++------- internal/mcp/http_transport.go | 20 +++++++++++--------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/internal/mcp/connection_test.go b/internal/mcp/connection_test.go index 04e8624db..c0c4e8b22 100644 --- a/internal/mcp/connection_test.go +++ b/internal/mcp/connection_test.go @@ -3,8 +3,10 @@ package mcp import ( "context" "encoding/json" + "errors" "fmt" "io" + "net" "net/http" "net/http/httptest" "strings" @@ -639,22 +641,32 @@ func TestIsHTTPConnectionError(t *testing.T) { expected: false, }, { - name: "connection refused error", - err: fmt.Errorf("dial tcp: connection refused"), + name: "net.OpError dial connection refused", + err: &net.OpError{Op: "dial", Net: "tcp", Err: errors.New("connection refused")}, expected: true, }, { - name: "no such host error", - err: fmt.Errorf("dial tcp: lookup example.invalid: no such host"), + name: "net.OpError dial no such host", + err: &net.OpError{Op: "dial", Net: "tcp", Err: &net.DNSError{Err: "no such host", IsNotFound: true}}, expected: true, }, { - name: "network is unreachable error", - err: fmt.Errorf("dial tcp: network is unreachable"), + name: "net.OpError dial network unreachable", + err: &net.OpError{Op: "dial", Net: "tcp", Err: errors.New("network is unreachable")}, expected: true, }, { - name: "other error", + name: "net.OpError non-dial (read) is not a connection error", + err: &net.OpError{Op: "read", Net: "tcp", Err: errors.New("broken pipe")}, + expected: false, + }, + { + name: "wrapped net.OpError dial via fmt.Errorf", + err: fmt.Errorf("Post %q: %w", "http://example.com", &net.OpError{Op: "dial", Net: "tcp", Err: errors.New("connection refused")}), + expected: true, + }, + { + name: "plain string error", err: fmt.Errorf("some other error"), expected: false, }, diff --git a/internal/mcp/http_transport.go b/internal/mcp/http_transport.go index 6c04377d3..933df4dca 100644 --- a/internal/mcp/http_transport.go +++ b/internal/mcp/http_transport.go @@ -4,9 +4,11 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "log/slog" + "net" "net/http" "strings" "sync/atomic" @@ -31,7 +33,7 @@ const ( ) // MCPProtocolVersion is the MCP protocol version used in initialization requests. -const MCPProtocolVersion = "2024-11-05" +const MCPProtocolVersion = "2025-11-25" // requestIDCounter is used to generate unique request IDs for HTTP requests var requestIDCounter uint64 @@ -46,18 +48,18 @@ type httpRequestResult struct { // transportConnector is a function that creates an SDK transport for a given URL and HTTP client type transportConnector func(url string, httpClient *http.Client) sdk.Transport -// isHTTPConnectionError checks if an error is a network connection error -// This helper reduces code duplication for checking common connection error patterns. -// Note: Uses string matching which is fragile but consistent with existing patterns in the codebase. -// TODO: Consider using errors.Is() or type assertions (*net.OpError) for more robust error classification. +// isHTTPConnectionError checks if an error is a network connection error. +// It uses errors.As to inspect the underlying *net.OpError for dial operations, +// which covers connection refused, no such host, and network unreachable errors. func isHTTPConnectionError(err error) bool { if err == nil { return false } - errMsg := err.Error() - return strings.Contains(errMsg, "connection refused") || - strings.Contains(errMsg, "no such host") || - strings.Contains(errMsg, "network is unreachable") + var opErr *net.OpError + if errors.As(err, &opErr) { + return opErr.Op == "dial" + } + return false } // parseSSEResponse extracts JSON data from SSE-formatted response