diff --git a/go.mod b/go.mod index f7782280..9627c2c0 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.0 require ( github.com/BurntSushi/toml v1.6.0 - github.com/modelcontextprotocol/go-sdk v1.4.1 + github.com/modelcontextprotocol/go-sdk v1.5.0 github.com/spf13/cobra v1.10.2 golang.org/x/term v0.41.0 ) diff --git a/go.sum b/go.sum index 8c290f57..0647d3d6 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -34,8 +34,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/modelcontextprotocol/go-sdk v1.4.1 h1:M4x9GyIPj+HoIlHNGpK2hq5o3BFhC+78PkEaldQRphc= -github.com/modelcontextprotocol/go-sdk v1.4.1/go.mod h1:Bo/mS87hPQqHSRkMv4dQq1XCu6zv4INdXnFZabkNU6s= +github.com/modelcontextprotocol/go-sdk v1.5.0 h1:CHU0FIX9kpueNkxuYtfYQn1Z0slhFzBZuq+x6IiblIU= +github.com/modelcontextprotocol/go-sdk v1.5.0/go.mod h1:gggDIhoemhWs3BGkGwd1umzEXCEMMvAnhTrnbXJKKKA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= diff --git a/internal/mcp/connection.go b/internal/mcp/connection.go index abf68b02..57374691 100644 --- a/internal/mcp/connection.go +++ b/internal/mcp/connection.go @@ -248,10 +248,10 @@ func NewHTTPConnection(ctx context.Context, serverID, url string, headers map[st conn, err = trySSETransport(ctx, cancel, serverID, url, headers, headerClient, keepAlive) if err == nil { logger.LogWarn("backend", "⚠️ MCP over SSE has been deprecated. Connected using SSE transport for url=%s. Please migrate to streamable HTTP transport (2025-03-26 spec).", url) - log.Printf("⚠️ WARNING: MCP over SSE (2024-11-05 spec) has been DEPRECATED") - log.Printf("⚠️ The server at %s is using the deprecated SSE transport", url) - log.Printf("⚠️ Please migrate to streamable HTTP transport (2025-03-26 spec)") - log.Printf("Configured HTTP MCP server with SSE transport: %s", url) + logger.LogWarn("backend", "⚠️ WARNING: MCP over SSE (2024-11-05 spec) has been DEPRECATED") + logger.LogWarn("backend", "⚠️ The server at %s is using the deprecated SSE transport", url) + logger.LogWarn("backend", "⚠️ Please migrate to streamable HTTP transport (2025-03-26 spec)") + logger.LogWarn("backend", "Configured HTTP MCP server with SSE transport: %s", url) return conn, nil } logConn.Printf("SSE transport failed: %v", err) diff --git a/internal/mcp/http_transport.go b/internal/mcp/http_transport.go index ab4516fd..3c7bb4d4 100644 --- a/internal/mcp/http_transport.go +++ b/internal/mcp/http_transport.go @@ -67,12 +67,17 @@ func isHTTPConnectionError(err error) bool { return false } -// isSessionNotFoundError checks if an error message indicates a backend MCP session has expired +// isSessionNotFoundError checks if an error indicates a backend MCP session has expired // or is not found. This is used to detect when automatic reconnection to the backend is needed. +// It uses errors.Is to check for sdk.ErrSessionMissing (the typed sentinel introduced in v1.5.0), +// and also falls back to string-matching for non-SDK transports that return plain error messages. func isSessionNotFoundError(err error) bool { if err == nil { return false } + if errors.Is(err, sdk.ErrSessionMissing) { + return true + } return strings.Contains(strings.ToLower(err.Error()), "session not found") } diff --git a/internal/mcp/http_transport_test.go b/internal/mcp/http_transport_test.go index e2f0c592..05322092 100644 --- a/internal/mcp/http_transport_test.go +++ b/internal/mcp/http_transport_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/github/gh-aw-mcpg/internal/oidc" + sdk "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -875,6 +876,8 @@ func TestIsSessionNotFoundError(t *testing.T) { {name: "uppercase returns true", err: fmt.Errorf("Session Not Found"), want: true}, {name: "embedded in longer message returns true", err: fmt.Errorf("Streamable HTTP error: Error POSTing to endpoint: session not found"), want: true}, {name: "session expired message returns false", err: fmt.Errorf("session expired"), want: false}, + {name: "sdk ErrSessionMissing sentinel returns true", err: sdk.ErrSessionMissing, want: true}, + {name: "wrapped sdk ErrSessionMissing returns true", err: fmt.Errorf("transport failure: %w", sdk.ErrSessionMissing), want: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/server/tool_registry.go b/internal/server/tool_registry.go index eff7c00b..65ce9ce0 100644 --- a/internal/server/tool_registry.go +++ b/internal/server/tool_registry.go @@ -44,9 +44,12 @@ type launchResult struct { // The wrapper in this function adapts the three-argument form back to the SDK's two-argument // form when registering with the SDK server. // -// NOTE: The Server.AddTool method (used here) skips JSON Schema validation whereas the -// sdk.AddTool function validates the schema. This distinction relies on internal SDK -// behaviour and must be re-verified on every SDK upgrade. +// NOTE: The Server.AddTool method (used here) does not validate tool arguments against the +// input schema at call time, whereas the package-level sdk.AddTool function does. The method +// does require that InputSchema is non-nil and has type "object" (enforced since v1.5.0), but +// it does not validate the argument values — that responsibility belongs to the caller. +// This distinction relies on internal SDK behaviour and must be re-verified on every SDK upgrade. +// Verified correct for go-sdk v1.5.0 (see server.go:Server.AddTool vs AddTool[In,Out]). func registerToolWithoutValidation(server *sdk.Server, tool *sdk.Tool, handler func(context.Context, *sdk.CallToolRequest, interface{}) (*sdk.CallToolResult, interface{}, error)) { server.AddTool(tool, func(ctx context.Context, req *sdk.CallToolRequest) (*sdk.CallToolResult, error) { result, _, err := handler(ctx, req, nil)